@Override
  public synchronized void onFailure(
      InsertException e, BaseClientPutter state, ObjectContainer container) {
    try {
      if (e.getMode() == InsertException.COLLISION) {
        Logger.warning(
            this, "WoTOwnMessageList insert collided, trying to insert with higher index ...");
        try {
          synchronized (mMessageManager) {
            // We must call getOwnMessageList() before calling onMessageListInsertFailed() because
            // the latter will increment the message list's
            // index, resulting in the ID of the message list changing - getIDFromURI would fail
            // with the old state.getURI() if we called it after
            // onMessageListInsertFailed()
            WoTOwnMessageList list =
                (WoTOwnMessageList)
                    mMessageManager.getOwnMessageList(
                        MessageListID.construct(state.getURI()).toString());
            mMessageManager.onMessageListInsertFailed(state.getURI(), true);
            insertMessageList(list);
          }
        } catch (Exception ex) {
          Logger.error(this, "Inserting WoTOwnMessageList with higher index failed", ex);
        }
      } else {
        if (e.isFatal()) Logger.error(this, "WoTOwnMessageList insert failed", e);
        else Logger.warning(this, "WoTOwnMessageList insert failed non-fatally", e);

        mMessageManager.onMessageListInsertFailed(state.getURI(), false);
      }
    } catch (Exception ex) {
      Logger.error(this, "WoTOwnMessageList insert failed and failure handling threw", ex);
    } finally {
      removeInsert(state);
      Closer.close(((ClientPutter) state).getData());
    }
  }
 private void fail(
     InsertException e, boolean forceFatal, ObjectContainer container, ClientContext context) {
   synchronized (this) {
     if (finished) return;
     finished = true;
   }
   if (persistent) container.store(this);
   if (e.isFatal() || forceFatal) parent.fatallyFailedBlock(container, context);
   else parent.failedBlock(container, context);
   unregister(container, context, getPriorityClass(container));
   if (freeData) {
     if (persistent) container.activate(sourceData, 1);
     sourceData.free();
     if (persistent) sourceData.removeFrom(container);
     sourceData = null;
     if (persistent) container.store(this);
   }
   if (persistent) container.activate(cb, 1);
   cb.onFailure(e, this, container, context);
 }
    public boolean send(
        NodeClientCore core, RequestScheduler sched, final ClientContext context, ChosenBlock req) {
      // Ignore keyNum, key, since we're only sending one block.
      ClientKeyBlock b;
      ClientKey key = null;
      if (SingleBlockInserter.logMINOR)
        Logger.minor(this, "Starting request: " + SingleBlockInserter.this);
      BlockItem block = (BlockItem) req.token;
      try {
        try {
          b =
              innerEncode(
                  context.random,
                  block.uri,
                  block.copyBucket,
                  block.isMetadata,
                  block.compressionCodec,
                  block.sourceLength,
                  compressorDescriptor);
        } catch (CHKEncodeException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (SSKEncodeException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (MalformedURLException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (InsertException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (IOException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (InvalidCompressionCodecException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        }
        if (b == null) {
          Logger.error(
              this,
              "Asked to send empty block on " + SingleBlockInserter.this,
              new Exception("error"));
          return false;
        }
        key = b.getClientKey();
        final ClientKey k = key;
        if (block.persistent) {
          context.jobRunner.queue(
              new DBJob() {

                public boolean run(ObjectContainer container, ClientContext context) {
                  if (!container.ext().isStored(SingleBlockInserter.this)) return false;
                  container.activate(SingleBlockInserter.this, 1);
                  onEncode(k, container, context);
                  container.deactivate(SingleBlockInserter.this, 1);
                  return false;
                }
              },
              NativeThread.NORM_PRIORITY + 1,
              false);
        } else {
          context.mainExecutor.execute(
              new Runnable() {

                public void run() {
                  onEncode(k, null, context);
                }
              },
              "Got URI");
        }
        if (req.localRequestOnly)
          try {
            core.node.store(b, false, req.canWriteClientCache, true, false);
          } catch (KeyCollisionException e) {
            throw new LowLevelPutException(LowLevelPutException.COLLISION);
          }
        else core.realPut(b, req.canWriteClientCache, req.forkOnCacheable);
      } catch (LowLevelPutException e) {
        if (e.code == LowLevelPutException.COLLISION) {
          // Collision
          try {
            ClientSSKBlock collided =
                (ClientSSKBlock)
                    core.node.fetch((ClientSSK) key, true, true, req.canWriteClientCache);
            byte[] data = collided.memoryDecode(true);
            byte[] inserting = BucketTools.toByteArray(block.copyBucket);
            if (collided.isMetadata() == block.isMetadata
                && collided.getCompressionCodec() == block.compressionCodec
                && Arrays.equals(data, inserting)) {
              if (SingleBlockInserter.logMINOR)
                Logger.minor(this, "Collided with identical data: " + SingleBlockInserter.this);
              req.onInsertSuccess(context);
              return true;
            }
          } catch (KeyVerifyException e1) {
            Logger.error(this, "Caught " + e1 + " when checking collision!", e1);
          } catch (KeyDecodeException e1) {
            Logger.error(this, "Caught " + e1 + " when checking collision!", e1);
          } catch (IOException e1) {
            Logger.error(this, "Caught " + e1 + " when checking collision!", e1);
          }
        }
        req.onFailure(e, context);
        if (SingleBlockInserter.logMINOR)
          Logger.minor(this, "Request failed: " + SingleBlockInserter.this + " for " + e);
        return true;
      } catch (DatabaseDisabledException e) {
        // Impossible, and nothing to do.
        Logger.error(this, "Running persistent insert but database is disabled!");
      } finally {
        block.copyBucket.free();
      }
      if (SingleBlockInserter.logMINOR)
        Logger.minor(this, "Request succeeded: " + SingleBlockInserter.this);
      req.onInsertSuccess(context);
      return true;
    }
  @Override
  public void onFailure(
      LowLevelPutException e, Object keyNum, ObjectContainer container, ClientContext context) {
    synchronized (this) {
      if (finished) return;
    }
    if (persistent) container.activate(errors, 1);
    if (parent.isCancelled()) {
      fail(new InsertException(InsertException.CANCELLED), container, context);
      return;
    }
    if (logMINOR) Logger.minor(this, "onFailure() on " + e + " for " + this);

    switch (e.code) {
      case LowLevelPutException.COLLISION:
        fail(new InsertException(InsertException.COLLISION), container, context);
        return;
      case LowLevelPutException.INTERNAL_ERROR:
        fail(new InsertException(InsertException.INTERNAL_ERROR), container, context);
        return;
      case LowLevelPutException.REJECTED_OVERLOAD:
        errors.inc(InsertException.REJECTED_OVERLOAD);
        break;
      case LowLevelPutException.ROUTE_NOT_FOUND:
        errors.inc(InsertException.ROUTE_NOT_FOUND);
        break;
      case LowLevelPutException.ROUTE_REALLY_NOT_FOUND:
        errors.inc(InsertException.ROUTE_REALLY_NOT_FOUND);
        break;
      default:
        Logger.error(this, "Unknown LowLevelPutException code: " + e.code);
        errors.inc(InsertException.INTERNAL_ERROR);
    }
    if (persistent) container.activate(ctx, 1);
    if (e.code == LowLevelPutException.ROUTE_NOT_FOUND
        || e.code == LowLevelPutException.ROUTE_REALLY_NOT_FOUND) {
      consecutiveRNFs++;
      if (logMINOR)
        Logger.minor(
            this,
            "Consecutive RNFs: " + consecutiveRNFs + " / " + ctx.consecutiveRNFsCountAsSuccess);
      if (consecutiveRNFs == ctx.consecutiveRNFsCountAsSuccess) {
        if (logMINOR)
          Logger.minor(this, "Consecutive RNFs: " + consecutiveRNFs + " - counting as success");
        onSuccess(keyNum, container, context);
        return;
      }
    } else consecutiveRNFs = 0;
    if (logMINOR) Logger.minor(this, "Failed: " + e);
    retries++;
    if ((retries > ctx.maxInsertRetries) && (ctx.maxInsertRetries != -1)) {
      fail(InsertException.construct(persistent ? errors.clone() : errors), container, context);
      if (persistent) container.deactivate(ctx, 1);
      return;
    }
    if (persistent) {
      container.store(this);
      container.deactivate(ctx, 1);
    }
    getScheduler(context).registerInsert(this, persistent, false, container);
  }
  public static void main(String[] args)
      throws InvalidThresholdException, IOException, NodeInitException, InterruptedException {
    Node node = null;
    Node secondNode = null;
    try {
      String ipOverride = null;
      if (args.length > 0) ipOverride = args[0];
      File dir = new File("bootstrap-push-pull-test");
      FileUtil.removeAll(dir);
      RandomSource random =
          NodeStarter.globalTestInit(dir.getPath(), false, LogLevel.ERROR, "", false);
      File seednodes = new File("seednodes.fref");
      if (!seednodes.exists() || seednodes.length() == 0 || !seednodes.canRead()) {
        System.err.println("Unable to read seednodes.fref, it doesn't exist, or is empty");
        System.exit(EXIT_NO_SEEDNODES);
      }
      File innerDir = new File(dir, Integer.toString(DARKNET_PORT1));
      innerDir.mkdir();
      FileInputStream fis = new FileInputStream(seednodes);
      FileUtil.writeTo(fis, new File(innerDir, "seednodes.fref"));
      fis.close();
      // Create one node
      Executor executor = new PooledExecutor();
      node =
          NodeStarter.createTestNode(
              DARKNET_PORT1,
              OPENNET_PORT1,
              dir.getPath(),
              false,
              Node.DEFAULT_MAX_HTL,
              0,
              random,
              executor,
              1000,
              5 * 1024 * 1024,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              12 * 1024,
              false,
              true,
              false,
              false,
              ipOverride);
      // NodeCrypto.DISABLE_GROUP_STRIP = true;
      // Logger.setupStdoutLogging(LogLevel.MINOR,
      // "freenet:NORMAL,freenet.node.NodeDispatcher:MINOR,freenet.node.FNPPacketMangler:MINOR");
      Logger.getChain().setThreshold(LogLevel.ERROR); // kill logging
      // Start it
      node.start(true);
      if (!TestUtil.waitForNodes(node)) {
        node.park();
        System.exit(EXIT_FAILED_TARGET);
      }
      System.err.println("Creating test data: " + TEST_SIZE + " bytes.");
      Bucket data = node.clientCore.tempBucketFactory.makeBucket(TEST_SIZE);
      OutputStream os = data.getOutputStream();
      byte[] buf = new byte[4096];
      for (long written = 0; written < TEST_SIZE; ) {
        node.fastWeakRandom.nextBytes(buf);
        int toWrite = (int) Math.min(TEST_SIZE - written, buf.length);
        os.write(buf, 0, toWrite);
        written += toWrite;
      }
      os.close();
      System.err.println("Inserting test data.");
      HighLevelSimpleClient client = node.clientCore.makeClient((short) 0);
      InsertBlock block = new InsertBlock(data, new ClientMetadata(), FreenetURI.EMPTY_CHK_URI);
      long startInsertTime = System.currentTimeMillis();
      FreenetURI uri;
      try {
        uri = client.insert(block, false, null);
      } catch (InsertException e) {
        System.err.println("INSERT FAILED: " + e);
        e.printStackTrace();
        System.exit(EXIT_INSERT_FAILED);
        return;
      }
      long endInsertTime = System.currentTimeMillis();
      System.err.println(
          "RESULT: Insert took "
              + (endInsertTime - startInsertTime)
              + "ms ("
              + TimeUtil.formatTime(endInsertTime - startInsertTime)
              + ") to "
              + uri
              + " .");
      node.park();

      // Bootstrap a second node.
      File secondInnerDir = new File(dir, Integer.toString(DARKNET_PORT2));
      secondInnerDir.mkdir();
      fis = new FileInputStream(seednodes);
      FileUtil.writeTo(fis, new File(secondInnerDir, "seednodes.fref"));
      fis.close();
      executor = new PooledExecutor();
      secondNode =
          NodeStarter.createTestNode(
              DARKNET_PORT2,
              OPENNET_PORT2,
              dir.getPath(),
              false,
              Node.DEFAULT_MAX_HTL,
              0,
              random,
              executor,
              1000,
              5 * 1024 * 1024,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              12 * 1024,
              false,
              true,
              false,
              false,
              ipOverride);
      secondNode.start(true);
      if (!TestUtil.waitForNodes(secondNode)) {
        secondNode.park();
        System.exit(EXIT_FAILED_TARGET);
      }

      // Fetch the data
      long startFetchTime = System.currentTimeMillis();
      client = secondNode.clientCore.makeClient((short) 0);
      try {
        client.fetch(uri);
      } catch (FetchException e) {
        System.err.println("FETCH FAILED: " + e);
        e.printStackTrace();
        System.exit(EXIT_FETCH_FAILED);
        return;
      }
      long endFetchTime = System.currentTimeMillis();
      System.err.println(
          "RESULT: Fetch took "
              + (endFetchTime - startFetchTime)
              + "ms ("
              + TimeUtil.formatTime(endFetchTime - startFetchTime)
              + ") of "
              + uri
              + " .");
      secondNode.park();
      System.exit(0);
    } catch (Throwable t) {
      System.err.println("CAUGHT: " + t);
      t.printStackTrace();
      try {
        if (node != null) node.park();
      } catch (Throwable t1) {
      }
      ;
      try {
        if (secondNode != null) secondNode.park();
      } catch (Throwable t1) {
      }
      ;

      System.exit(EXIT_THREW_SOMETHING);
    }
  }
  public static void main(String[] args) {
    if (args.length < 1 || args.length > 2) {
      System.err.println(
          "Usage: java freenet.node.simulator.LongTermPushPullTest <unique identifier>");
      System.exit(1);
    }
    String uid = args[0];

    List<String> csvLine = new ArrayList<String>();
    System.out.println("DATE:" + dateFormat.format(today.getTime()));
    csvLine.add(dateFormat.format(today.getTime()));

    System.out.println("Version:" + Version.buildNumber());
    csvLine.add(String.valueOf(Version.buildNumber()));

    int exitCode = 0;
    Node node = null;
    Node node2 = null;
    FileInputStream fis = null;
    File file = new File("many-single-blocks-test-" + uid + ".csv");
    long t1, t2;

    try {

      // INSERT STUFF

      final File dir = new File("longterm-mhk-test-" + uid);
      FileUtil.removeAll(dir);
      RandomSource random =
          NodeStarter.globalTestInit(dir.getPath(), false, LogLevel.ERROR, "", false);
      File seednodes = new File("seednodes.fref");
      if (!seednodes.exists() || seednodes.length() == 0 || !seednodes.canRead()) {
        System.err.println("Unable to read seednodes.fref, it doesn't exist, or is empty");
        System.exit(EXIT_NO_SEEDNODES);
      }

      final File innerDir = new File(dir, Integer.toString(DARKNET_PORT1));
      innerDir.mkdir();
      fis = new FileInputStream(seednodes);
      FileUtil.writeTo(fis, new File(innerDir, "seednodes.fref"));
      fis.close();

      // Create one node
      node =
          NodeStarter.createTestNode(
              DARKNET_PORT1,
              OPENNET_PORT1,
              dir.getPath(),
              false,
              Node.DEFAULT_MAX_HTL,
              0,
              random,
              new PooledExecutor(),
              1000,
              4 * 1024 * 1024,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              12 * 1024,
              true,
              true,
              false,
              false,
              null);
      Logger.getChain().setThreshold(LogLevel.ERROR);

      // Start it
      node.start(true);
      t1 = System.currentTimeMillis();
      if (!TestUtil.waitForNodes(node)) {
        exitCode = EXIT_FAILED_TARGET;
        return;
      }

      t2 = System.currentTimeMillis();
      System.out.println("SEED-TIME:" + (t2 - t1));
      csvLine.add(String.valueOf(t2 - t1));

      HighLevelSimpleClient client = node.clientCore.makeClient((short) 0, false, false);

      int successes = 0;

      long startInsertsTime = System.currentTimeMillis();

      InsertBatch batch = new InsertBatch(client);

      // Inserts are sloooooow so do them in parallel.

      for (int i = 0; i < INSERTED_BLOCKS; i++) {

        System.err.println("Inserting block " + i);

        RandomAccessBucket single = randomData(node);

        InsertBlock block = new InsertBlock(single, new ClientMetadata(), FreenetURI.EMPTY_CHK_URI);

        batch.startInsert(block);
      }

      batch.waitUntilFinished();
      FreenetURI[] uris = batch.getURIs();
      long[] times = batch.getTimes();
      InsertException[] errors = batch.getErrors();

      for (int i = 0; i < INSERTED_BLOCKS; i++) {
        if (uris[i] != null) {
          csvLine.add(String.valueOf(times[i]));
          csvLine.add(uris[i].toASCIIString());
          System.out.println("Pushed block " + i + " : " + uris[i] + " in " + times[i]);
          successes++;
        } else {
          csvLine.add(InsertException.getShortMessage(errors[i].getMode()));
          csvLine.add("N/A");
          System.out.println("Failed to push block " + i + " : " + errors[i]);
        }
      }

      long endInsertsTime = System.currentTimeMillis();

      System.err.println(
          "Succeeded inserts: "
              + successes
              + " of "
              + INSERTED_BLOCKS
              + " in "
              + (endInsertsTime - startInsertsTime)
              + "ms");

      FetchContext fctx = client.getFetchContext();
      fctx.maxNonSplitfileRetries = 0;
      fctx.maxSplitfileBlockRetries = 0;
      RequestClient requestContext = new RequestClientBuilder().build();

      // PARSE FILE AND FETCH OLD STUFF IF APPROPRIATE

      FreenetURI[] mhkURIs = new FreenetURI[3];
      fis = new FileInputStream(file);
      BufferedReader br = new BufferedReader(new InputStreamReader(fis, ENCODING));
      String line = null;
      GregorianCalendar target = (GregorianCalendar) today.clone();
      target.set(Calendar.HOUR_OF_DAY, 0);
      target.set(Calendar.MINUTE, 0);
      target.set(Calendar.MILLISECOND, 0);
      target.set(Calendar.SECOND, 0);
      GregorianCalendar[] targets = new GregorianCalendar[MAX_N + 1];
      for (int i = 0; i < targets.length; i++) {
        targets[i] = ((GregorianCalendar) target.clone());
        targets[i].add(Calendar.DAY_OF_MONTH, -((1 << i) - 1));
        targets[i].getTime();
      }
      int[] totalFetchesByDelta = new int[MAX_N + 1];
      int[] totalSuccessfulFetchesByDelta = new int[MAX_N + 1];
      long[] totalFetchTimeByDelta = new long[MAX_N + 1];

      loopOverLines:
      while ((line = br.readLine()) != null) {

        for (int i = 0; i < mhkURIs.length; i++) mhkURIs[i] = null;
        // System.out.println("LINE: "+line);
        String[] split = line.split("!");
        Date date = dateFormat.parse(split[0]);
        GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
        calendar.setTime(date);
        System.out.println("Date: " + dateFormat.format(calendar.getTime()));
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.getTime();
        FreenetURI[] insertedURIs = new FreenetURI[INSERTED_BLOCKS];
        int[] insertTimes = new int[INSERTED_BLOCKS];
        if (split.length < 3) continue;
        int seedTime = Integer.parseInt(split[2]);
        System.out.println("Seed time: " + seedTime);
        if (split.length < 4) continue;

        int token = 3;

        if (split.length < token + INSERTED_BLOCKS * 2) continue;

        for (int i = 0; i < INSERTED_BLOCKS; i++) {
          try {
            insertTimes[i] = Integer.parseInt(split[token]);
          } catch (NumberFormatException e) {
            insertTimes[i] = -1;
          }
          token++;
          try {
            insertedURIs[i] = new FreenetURI(split[token]);
          } catch (MalformedURLException e) {
            insertedURIs[i] = null;
          }
          token++;
          System.out.println("Key insert " + i + " : " + insertedURIs[i] + " in " + insertTimes[i]);
        }
        for (int i = 0; i < targets.length; i++) {
          if (Math.abs(targets[i].getTimeInMillis() - calendar.getTimeInMillis())
              < HOURS.toMillis(12)) {
            System.out.println("Found row for target date " + ((1 << i) - 1) + " days ago.");
            System.out.println("Version: " + split[1]);
            csvLine.add(Integer.toString(i));
            int pulled = 0;
            int inserted = 0;
            for (int j = 0; j < INSERTED_BLOCKS; j++) {
              if (insertedURIs[j] == null) {
                csvLine.add("INSERT FAILED");
                continue;
              }
              inserted++;
              try {
                t1 = System.currentTimeMillis();
                FetchWaiter fw = new FetchWaiter(requestContext);
                client.fetch(insertedURIs[j], 32768, fw, fctx);
                fw.waitForCompletion();
                t2 = System.currentTimeMillis();

                System.out.println("PULL-TIME FOR BLOCK " + j + ": " + (t2 - t1));
                csvLine.add(String.valueOf(t2 - t1));
                pulled++;
              } catch (FetchException e) {
                if (e.getMode() != FetchExceptionMode.ALL_DATA_NOT_FOUND
                    && e.getMode() != FetchExceptionMode.DATA_NOT_FOUND) e.printStackTrace();
                csvLine.add(FetchException.getShortMessage(e.getMode()));
                System.err.println("FAILED PULL FOR BLOCK " + j + ": " + e);
              }
            }
            System.out.println(
                "Pulled "
                    + pulled
                    + " blocks of "
                    + inserted
                    + " from "
                    + ((1 << i) - 1)
                    + " days ago.");
          }
        }

        while (split.length > token + INSERTED_BLOCKS) {
          int delta;
          try {
            delta = Integer.parseInt(split[token]);
          } catch (NumberFormatException e) {
            System.err.println("Unable to parse token " + token + " = \"" + token + "\"");
            System.err.println("This is supposed to be a delta");
            System.err.println(
                "Skipping the rest of the line for date " + dateFormat.format(calendar.getTime()));
            continue loopOverLines;
          }
          System.out.println("Delta: " + ((1 << delta) - 1) + " days");
          token++;
          int totalFetchTime = 0;
          int totalSuccesses = 0;
          int totalFetches = 0;
          for (int i = 0; i < INSERTED_BLOCKS; i++) {
            if (split[token].equals("")) continue;
            int mhkFetchTime = -1;
            totalFetches++;
            try {
              mhkFetchTime = Integer.parseInt(split[token]);
              System.out.println(
                  "Fetched block #" + i + " on " + date + " in " + mhkFetchTime + "ms");
              totalSuccesses++;
              totalFetchTime += mhkFetchTime;
            } catch (NumberFormatException e) {
              System.out.println("Failed block #" + i + " on " + date + " : " + split[token]);
            }
            token++;
          }
          totalFetchesByDelta[delta] += totalFetches;
          totalSuccessfulFetchesByDelta[delta] += totalSuccesses;
          totalFetchTimeByDelta[delta] += totalFetchTime;
          System.err.println(
              "Succeeded: "
                  + totalSuccesses
                  + " of "
                  + totalFetches
                  + " average "
                  + ((double) totalFetchTime) / ((double) totalSuccesses)
                  + "ms for delta "
                  + delta
                  + " on "
                  + dateFormat.format(date));
        }
      }

      System.out.println();
      System.out.println();

      for (int i = 0; i < MAX_N + 1; i++) {
        System.out.println(
            "DELTA: "
                + i
                + " days: Total fetches: "
                + totalFetchesByDelta[i]
                + " total successes "
                + totalSuccessfulFetchesByDelta[i]
                + " = "
                + ((totalSuccessfulFetchesByDelta[i] * 100.0) / totalFetchesByDelta[i])
                + "% in "
                + (totalFetchTimeByDelta[i] * 1.0) / totalSuccessfulFetchesByDelta[i]
                + "ms");
      }

      fis.close();
      fis = null;

    } catch (Throwable t) {
      t.printStackTrace();
      exitCode = EXIT_THREW_SOMETHING;
    } finally {
      try {
        if (node != null) node.park();
      } catch (Throwable tt) {
      }
      try {
        if (node2 != null) node2.park();
      } catch (Throwable tt) {
      }
      Closer.close(fis);
      writeToStatusLog(file, csvLine);

      System.out.println("Exiting with status " + exitCode);
      System.exit(exitCode);
    }
  }