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/test/java/org/elasticsearch/action/ActionListenerTests.java
throw (IllegalArgumentException) e;
                }
            }
        };
        AssertionError assertionError = expectThrows(AssertionError.class, () -> ActionListener.completeWith(listener, () -> null));
        assertThat(assertionError.getCause(), instanceOf(IllegalArgumentException.class));
        assertNull(exReference.get());
        assertionError = expectThrows(
            AssertionError.class,
            () -> ActionListener.completeWith(listener, () -> { throw new IllegalArgumentException(); })
        );
        assertThat(assertionError.getCause(), instanceOf(IllegalArgumentException.class));
        assertThat(exReference.get(), instanceOf(IllegalArgumentException.class));
    }
    /**
     * Test that map passes the output of the function to its delegate listener and that exceptions in the function are propagated to the
     * onFailure handler. Also verify that exceptions from ActionListener.onResponse does not invoke onFailure, since it is the
     * responsibility of the ActionListener implementation (the client of the API) to handle exceptions in onResponse and onFailure.
     */
    public void testMap() {
        AtomicReference<Exception> exReference = new AtomicReference<>();
        ActionListener<String> listener = new ActionListener<>() {
            @Override
            public void onResponse(String s) {
                if (s == null) {
                    throw new IllegalArgumentException("simulate onResponse exception");
                }
            }
            @Override
            public void onFailure(Exception e) {