public CompletableFuture<T> select(ClientRequestContext ctx, ScheduledExecutorService executor,
                                       long selectionTimeoutMillis) {
        checkArgument(selectionTimeoutMillis >= 0, "selectionTimeoutMillis: %s (expected: >= 0)",
                      selectionTimeoutMillis);
        T selected = selectNow(ctx);
        if (selected != null) {
            return UnmodifiableFuture.completedFuture(selected);
        }

        final ListeningFuture listeningFuture = new ListeningFuture(ctx, executor);
        addPendingFuture(listeningFuture);

        // The EndpointGroup have just been updated after adding ListeningFuture.
        if (listeningFuture.isDone()) {
            return listeningFuture;
        }

        selected = selectNow(ctx);
        if (selected != null) {
            // The state has just been updated before adding ListeningFuture.
            listeningFuture.complete(selected);
            return listeningFuture;
        }

        if (selectionTimeoutMillis == 0) {
            return UnmodifiableFuture.completedFuture(null);
        }

        // Schedule the timeout task.
        if (selectionTimeoutMillis < Long.MAX_VALUE) {
            final ScheduledFuture<?> timeoutFuture = executor.schedule(() -> {
                onTimeout(ctx, selectionTimeoutMillis);
                // Don't complete exceptionally so that the throwable
                // can be handled after executing the attached decorators
                listeningFuture.complete(null);
            }, selectionTimeoutMillis, TimeUnit.MILLISECONDS);
            listeningFuture.timeoutFuture = timeoutFuture;

            // Cancel the timeout task if listeningFuture is done already.
            // This guards against the following race condition:
            // 1) (Current thread) Timeout task is scheduled.
            // 2) ( Other thread ) listeningFuture is completed, but the timeout task is not cancelled
            // 3) (Current thread) timeoutFuture is assigned to listeningFuture.timeoutFuture, but it's too
            // late.
            if (listeningFuture.isDone()) {
                timeoutFuture.cancel(false);
            }
        }

        return listeningFuture;
    }
Select an element asynchronously. If an element is readily available, this method will return
immediately. Otherwise, a future will be queued and wait for the next {@link #refresh()} invocation.
If the specified timeout is passed, the future will complete with {@code null}.

@param executor the executor which will schedule a timeout
@param selectionTimeoutMillis the amount of time to wait before completing a pending future
core/src/main/java/com/linecorp/armeria/internal/client/AbstractAsyncSelector.java