https://github.com/elastic/elasticsearch/blob/099fb84cfcf87be6689c3516674f45438bafa767/server/src/main/java/org/elasticsearch/action/ActionListener.java#L420-L432

Rather than keeping hold of the `delegate` and using an `AtomicBoolean` to track the completion, we could use an `AtomicReference` and drop the whole listener on completion, which might free up heap in some fail-fast cases.
server/src/main/java/org/elasticsearch/action/StepListener.java
StepListener<OtherResponse> other,
        BiFunction<Response, OtherResponse, OuterResponse> fn
    ) {
        final StepListener<OuterResponse> combined = new StepListener<>();
        whenComplete(r1 -> other.whenComplete(r2 -> combined.onResponse(fn.apply(r1, r2)), combined::onFailure), combined::onFailure);
        return combined;
    }
    /**
     * Returns the future associated with the given step listener
     */
    public Future<Response> asFuture() {
        return delegate;
    }
    /**
     * Gets the result of this step. This method will throw {@link IllegalStateException} if this step is not completed yet.
     */
    public Response result() {
        if (delegate.isDone() == false) {
            throw new IllegalStateException("step is not completed yet");
        }
        return FutureUtils.get(delegate, 0L, TimeUnit.NANOSECONDS); 
    }
    /**
     * Registers the given listener to be notified with the result of this step.
     */
    public void addListener(ActionListener<Response> listener) {
        delegate.addListener(listener);
    }
}