private <S, R> void coprocessorServiceUntilComplete(Function<RpcChannel, S> stubMaker,
    ServiceCaller<S, R> callable, PartialResultCoprocessorCallback<S, R> callback,
    AtomicBoolean locateFinished, AtomicInteger unfinishedRequest, RegionInfo region, Span span) {
    addListener(coprocessorService(stubMaker, callable, region, region.getStartKey()), (r, e) -> {
      try (Scope ignored = span.makeCurrent()) {
        if (e != null) {
          callback.onRegionError(region, e);
        } else {
          callback.onRegionComplete(region, r);
        }

        ServiceCaller<S, R> updatedCallable;
        if (e == null && r != null) {
          updatedCallable = callback.getNextCallable(r, region);
        } else {
          updatedCallable = null;
        }

        // If updatedCallable is non-null, we will be sending another request, so no need to
        // decrement unfinishedRequest (recall that && short-circuits).
        // If updatedCallable is null, and unfinishedRequest decrements to 0, we're done with the
        // requests for this coprocessor call.
        if (
          updatedCallable == null && unfinishedRequest.decrementAndGet() == 0
            && locateFinished.get()
        ) {
          callback.onComplete();
        } else if (updatedCallable != null) {
          Duration waitInterval = callback.getWaitInterval(r, region);
          LOG.trace("Coprocessor returned incomplete result. "
            + "Sleeping for {} before making follow-up request.", waitInterval);
          if (!waitInterval.isZero()) {
            AsyncConnectionImpl.RETRY_TIMER.newTimeout(
              (timeout) -> coprocessorServiceUntilComplete(stubMaker, updatedCallable, callback,
                locateFinished, unfinishedRequest, region, span),
              waitInterval.toMillis(), TimeUnit.MILLISECONDS);
          } else {
            coprocessorServiceUntilComplete(stubMaker, updatedCallable, callback, locateFinished,
              unfinishedRequest, region, span);
          }
        }
      }
    });
  }
The implementation of RawAsyncTable.
<p/>
The word 'Raw' means that this is a low level class. The returned {@link CompletableFuture} will
be finished inside the rpc framework thread, which means that the callbacks registered to the
{@link CompletableFuture} will also be executed inside the rpc framework thread. So users who use
this class should not try to do time consuming tasks in the callbacks.
@since 2.0.0
@see AsyncTableImpl
hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncTableImpl.java