@Test
  public void testKill() throws Exception {
    ClusterService clusterService = mock(ClusterService.class);
    OperationRouting operationRouting = mock(OperationRouting.class);

    mockShard(operationRouting, 1);
    mockShard(operationRouting, 2);
    mockShard(operationRouting, 3);
    when(clusterService.operationRouting()).thenReturn(operationRouting);

    final AtomicReference<ActionListener<ShardUpsertResponse>> ref = new AtomicReference<>();
    SymbolBasedTransportShardUpsertActionDelegate transportShardUpsertActionDelegate =
        new SymbolBasedTransportShardUpsertActionDelegate() {
          @Override
          public void execute(
              SymbolBasedShardUpsertRequest request, ActionListener<ShardUpsertResponse> listener) {
            ref.set(listener);
          }
        };

    TransportActionProvider transportActionProvider =
        mock(TransportActionProvider.class, Answers.RETURNS_DEEP_STUBS.get());
    when(transportActionProvider.symbolBasedTransportShardUpsertActionDelegate())
        .thenReturn(transportShardUpsertActionDelegate);

    BulkRetryCoordinator bulkRetryCoordinator = new BulkRetryCoordinator(ImmutableSettings.EMPTY);
    BulkRetryCoordinatorPool coordinatorPool = mock(BulkRetryCoordinatorPool.class);
    when(coordinatorPool.coordinator(any(ShardId.class))).thenReturn(bulkRetryCoordinator);

    SymbolBasedShardUpsertRequest.Builder builder =
        new SymbolBasedShardUpsertRequest.Builder(
            TimeValue.timeValueMillis(10),
            false,
            false,
            null,
            new Reference[] {fooRef},
            UUID.randomUUID());

    final SymbolBasedBulkShardProcessor<SymbolBasedShardUpsertRequest, ShardUpsertResponse>
        bulkShardProcessor =
            new SymbolBasedBulkShardProcessor<>(
                clusterService,
                mock(TransportBulkCreateIndicesAction.class),
                ImmutableSettings.EMPTY,
                coordinatorPool,
                false,
                1,
                builder,
                transportShardUpsertActionDelegate,
                UUID.randomUUID());
    assertThat(bulkShardProcessor.add("foo", "1", new Object[] {"bar1"}, null, null), is(true));
    bulkShardProcessor.kill(new CancellationException());
    // A CancellationException is thrown
    expectedException.expect(CancellationException.class);
    bulkShardProcessor.result().get();
    // it's not possible to add more
    assertThat(bulkShardProcessor.add("foo", "1", new Object[] {"bar1"}, null, null), is(false));
  }
  @Test
  public void testNonEsRejectedExceptionDoesNotResultInRetryButAborts() throws Throwable {
    expectedException.expect(RuntimeException.class);
    expectedException.expectMessage("a random exception");

    final AtomicReference<ActionListener<ShardUpsertResponse>> ref = new AtomicReference<>();
    SymbolBasedTransportShardUpsertActionDelegate transportShardBulkActionDelegate =
        new SymbolBasedTransportShardUpsertActionDelegate() {
          @Override
          public void execute(
              SymbolBasedShardUpsertRequest request, ActionListener<ShardUpsertResponse> listener) {
            ref.set(listener);
          }
        };

    BulkRetryCoordinator bulkRetryCoordinator = new BulkRetryCoordinator(ImmutableSettings.EMPTY);
    BulkRetryCoordinatorPool coordinatorPool = mock(BulkRetryCoordinatorPool.class);
    when(coordinatorPool.coordinator(any(ShardId.class))).thenReturn(bulkRetryCoordinator);

    SymbolBasedShardUpsertRequest.Builder builder =
        new SymbolBasedShardUpsertRequest.Builder(
            TimeValue.timeValueMillis(10),
            false,
            false,
            null,
            new Reference[] {fooRef},
            UUID.randomUUID());
    final SymbolBasedBulkShardProcessor<SymbolBasedShardUpsertRequest, ShardUpsertResponse>
        bulkShardProcessor =
            new SymbolBasedBulkShardProcessor<>(
                clusterService,
                mock(TransportBulkCreateIndicesAction.class),
                ImmutableSettings.EMPTY,
                coordinatorPool,
                false,
                1,
                builder,
                transportShardBulkActionDelegate,
                UUID.randomUUID());
    bulkShardProcessor.add("foo", "1", new Object[] {"bar1"}, null, null);

    ActionListener<ShardUpsertResponse> listener = ref.get();
    listener.onFailure(new RuntimeException("a random exception"));

    assertFalse(bulkShardProcessor.add("foo", "2", new Object[] {"bar2"}, null, null));

    try {
      bulkShardProcessor.result().get();
    } catch (ExecutionException e) {
      throw e.getCause();
    } finally {
      bulkShardProcessor.close();
    }
  }
  @Test
  public void testThatAddAfterFailureBlocksDueToRetry() throws Exception {
    ClusterService clusterService = mock(ClusterService.class);
    OperationRouting operationRouting = mock(OperationRouting.class);

    mockShard(operationRouting, 1);
    mockShard(operationRouting, 2);
    mockShard(operationRouting, 3);
    when(clusterService.operationRouting()).thenReturn(operationRouting);

    // listener will be executed 2 times, once for the successfully added row and once for the
    // failure
    final CountDownLatch listenerLatch = new CountDownLatch(2);
    final AtomicReference<ActionListener<ShardUpsertResponse>> ref = new AtomicReference<>();
    SymbolBasedTransportShardUpsertActionDelegate transportShardUpsertActionDelegate =
        new SymbolBasedTransportShardUpsertActionDelegate() {
          @Override
          public void execute(
              SymbolBasedShardUpsertRequest request, ActionListener<ShardUpsertResponse> listener) {
            ref.set(listener);
            listenerLatch.countDown();
          }
        };

    TransportActionProvider transportActionProvider =
        mock(TransportActionProvider.class, Answers.RETURNS_DEEP_STUBS.get());
    when(transportActionProvider.symbolBasedTransportShardUpsertActionDelegate())
        .thenReturn(transportShardUpsertActionDelegate);

    BulkRetryCoordinator bulkRetryCoordinator = new BulkRetryCoordinator(ImmutableSettings.EMPTY);
    BulkRetryCoordinatorPool coordinatorPool = mock(BulkRetryCoordinatorPool.class);
    when(coordinatorPool.coordinator(any(ShardId.class))).thenReturn(bulkRetryCoordinator);

    SymbolBasedShardUpsertRequest.Builder builder =
        new SymbolBasedShardUpsertRequest.Builder(
            TimeValue.timeValueMillis(10),
            false,
            false,
            null,
            new Reference[] {fooRef},
            UUID.randomUUID());

    final SymbolBasedBulkShardProcessor<SymbolBasedShardUpsertRequest, ShardUpsertResponse>
        bulkShardProcessor =
            new SymbolBasedBulkShardProcessor<>(
                clusterService,
                mock(TransportBulkCreateIndicesAction.class),
                ImmutableSettings.EMPTY,
                coordinatorPool,
                false,
                1,
                builder,
                transportShardUpsertActionDelegate,
                UUID.randomUUID());
    bulkShardProcessor.add("foo", "1", new Object[] {"bar1"}, null, null);
    final ActionListener<ShardUpsertResponse> listener = ref.get();

    listener.onFailure(new EsRejectedExecutionException());
    // wait, failure retry lock is done in decoupled thread
    listenerLatch.await(10, TimeUnit.SECONDS);

    final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    try {
      final AtomicBoolean hadBlocked = new AtomicBoolean(false);
      final AtomicBoolean hasBlocked = new AtomicBoolean(true);
      final CountDownLatch latch = new CountDownLatch(1);
      scheduledExecutorService.execute(
          new Runnable() {
            @Override
            public void run() {
              scheduledExecutorService.schedule(
                  new Runnable() {
                    @Override
                    public void run() {
                      hadBlocked.set(hasBlocked.get());
                      latch.countDown();
                    }
                  },
                  10,
                  TimeUnit.MILLISECONDS);
              bulkShardProcessor.add("foo", "2", new Object[] {"bar2"}, null, null);
              hasBlocked.set(false);
            }
          });
      latch.await();
      assertTrue(hadBlocked.get());
    } finally {
      scheduledExecutorService.shutdownNow();
      bulkRetryCoordinator.close();
    }
  }