@Override
  public <S, R> CoprocessorServiceBuilder<S, R> coprocessorService(
    Function<RpcChannel, S> stubMaker, ServiceCaller<S, R> callable,
    PartialResultCoprocessorCallback<S, R> callback) {
    final Context context = Context.current();
    PartialResultCoprocessorCallback<S, R> wrappedCallback =
      new PartialResultCoprocessorCallback<S, R>() {

        private final Phaser regionCompletesInProgress = new Phaser(1);

        @Override
        public void onRegionComplete(RegionInfo region, R resp) {
          regionCompletesInProgress.register();
          pool.execute(context.wrap(() -> {
            try {
              callback.onRegionComplete(region, resp);
            } finally {
              regionCompletesInProgress.arriveAndDeregister();
            }
          }));
        }

        @Override
        public void onRegionError(RegionInfo region, Throwable error) {
          regionCompletesInProgress.register();
          pool.execute(context.wrap(() -> {
            try {
              callback.onRegionError(region, error);
            } finally {
              regionCompletesInProgress.arriveAndDeregister();
            }
          }));
        }

        @Override
        public void onComplete() {
          pool.execute(context.wrap(() -> {
            // Guarantee that onComplete() is called after all onRegionComplete()'s are called
            regionCompletesInProgress.arriveAndAwaitAdvance();
            callback.onComplete();
          }));
        }

        @Override
        public void onError(Throwable error) {
          pool.execute(context.wrap(() -> callback.onError(error)));
        }

        @Override
        public ServiceCaller<S, R> getNextCallable(R response, RegionInfo region) {
          return callback.getNextCallable(response, region);
        }

        @Override
        public Duration getWaitInterval(R response, RegionInfo region) {
          return callback.getWaitInterval(response, region);
        }

      };
    CoprocessorServiceBuilder<S, R> builder =
      rawTable.coprocessorService(stubMaker, callable, wrappedCallback);
    return new CoprocessorServiceBuilder<S, R>() {

      @Override
      public CoprocessorServiceBuilder<S, R> fromRow(byte[] startKey, boolean inclusive) {
        builder.fromRow(startKey, inclusive);
        return this;
      }

      @Override
      public CoprocessorServiceBuilder<S, R> toRow(byte[] endKey, boolean inclusive) {
        builder.toRow(endKey, inclusive);
        return this;
      }

      @Override
      public void execute() {
        builder.execute();
      }
    };
  }
Just a wrapper of {@link RawAsyncTableImpl}. The difference is that users need to provide a
thread pool when constructing this class, and the callback methods registered to the returned
{@link CompletableFuture} will be executed in this thread pool. So usually it is safe for users
to do anything they want in the callbacks without breaking the rpc framework.
hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncTableImpl.java