private void testBadCallback(Callback<GetBlobResult> getBlobCallback, CountDownLatch getBlobCallbackCalled,
      Boolean checkBadCallbackBlob) throws Exception {
    router = getNonBlockingRouter();
    setOperationParams(LARGE_BLOB_SIZE, new GetBlobOptionsBuilder().build());
    final CountDownLatch getBlobInfoCallbackCalled = new CountDownLatch(1);
    String blobId =
        router.putBlob(putBlobProperties, putUserMetadata, putChannel, new PutBlobOptionsBuilder().build()).get();
    List<Future<GetBlobResult>> getBlobInfoFutures = new ArrayList<>();
    List<Future<GetBlobResult>> getBlobDataFutures = new ArrayList<>();
    GetBlobOptions infoOptions =
        new GetBlobOptionsBuilder().operationType(GetBlobOptions.OperationType.BlobInfo).build();
    GetBlobOptions dataOptions = new GetBlobOptionsBuilder().operationType(GetBlobOptions.OperationType.Data).build();
    for (int i = 0; i < 5; i++) {
      if (i == 1) {
        getBlobInfoFutures.add(router.getBlob(blobId, infoOptions, new Callback<GetBlobResult>() {
          @Override
          public void onCompletion(GetBlobResult result, Exception exception) {
            getBlobInfoCallbackCalled.countDown();
            throw new RuntimeException("Throwing an exception in the user callback");
          }
        }, quotaChargeCallback));
        getBlobDataFutures.add(router.getBlob(blobId, dataOptions, getBlobCallback, quotaChargeCallback));
      } else {
        getBlobInfoFutures.add(router.getBlob(blobId, infoOptions));
        getBlobDataFutures.add(router.getBlob(blobId, dataOptions));
      }
    }
    options = dataOptions;
    for (int i = 0; i < getBlobDataFutures.size(); i++) {
      if (i != 1 || checkBadCallbackBlob) {
        compareContent(getBlobDataFutures.get(i).get().getBlobDataChannel());
      }
    }
    options = infoOptions;
    for (Future<GetBlobResult> future : getBlobInfoFutures) {
      compareBlobInfo(future.get().getBlobInfo(), -1);
    }
    Assert.assertTrue("getBlobInfo callback not called.", getBlobInfoCallbackCalled.await(2, TimeUnit.SECONDS));
    Assert.assertTrue("getBlob callback not called.", getBlobCallbackCalled.await(2, TimeUnit.SECONDS));
    Assert.assertEquals("All operations should be finished.", 0, router.getOperationsCount());
    Assert.assertTrue("Router should not be closed", router.isOpen());

    // Test that GetManager is still operational
    setOperationParams(CHUNK_SIZE, new GetBlobOptionsBuilder().build());
    blobId = router.putBlob(putBlobProperties, putUserMetadata, putChannel, new PutBlobOptionsBuilder().build()).get();
    getBlobAndCompareContent(blobId, -1);
    this.options = infoOptions;
    getBlobAndCompareContent(blobId, -1);
    router.close();
  }
Test that a bad user defined callback will not crash the router.
@param getBlobCallback User defined callback to be called after getBlob operation.
@param getBlobCallbackCalled This latch should be at 0 after {@code getBlobCallback} has been called.
@param checkBadCallbackBlob {@code true} if the blob contents provided by the getBlob operation with the bad
                            callback should be inspected for correctness.
@throws Exception
ambry-router/src/test/java/com/github/ambry/router/GetManagerTest.java