Skip to content

AbstractListenableFuture#addListener does not execute listener when the listener is added after the future completion and when the execution list is not yet initialized #1128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nithril opened this issue Apr 4, 2016 · 6 comments
Assignees
Labels
Milestone

Comments

@nithril
Copy link

nithril commented Apr 4, 2016

You could find below two test cases to add to org.asynchttpclient.ListenableFutureTest:

  • ListenableFutureTest#testListenableFutureAfterCompletion with the issue when the listener is added after the completion
  • ListenableFutureTest#testListenableFutureBeforeAndAfterCompletion without the issue because the execution list is initialized prior the future completion
    @Test(groups = "standalone")
    public void testListenableFutureAfterCompletion() throws Exception {

        AtomicInteger counter = new AtomicInteger(1);

        try (AsyncHttpClient ahc = asyncHttpClient()) {
            final ListenableFuture<Response> future = ahc.prepareGet(getTargetUrl()).execute();
            Response response = future.get();
            future.addListener(() -> counter.decrementAndGet(), Runnable::run);
        }
        Assert.assertEquals(0 , counter.get());
    }


    @Test(groups = "standalone")
    public void testListenableFutureBeforeAndAfterCompletion() throws Exception {

        AtomicInteger counter = new AtomicInteger(2);

        try (AsyncHttpClient ahc = asyncHttpClient()) {
            final ListenableFuture<Response> future = ahc.prepareGet(getTargetUrl()).execute();

            future.addListener(() -> counter.decrementAndGet(), Runnable::run);

            Response response = future.get();
            future.addListener(() -> counter.decrementAndGet(), Runnable::run);
        }
        Assert.assertEquals(0 , counter.get());
    }
@nithril
Copy link
Author

nithril commented Apr 4, 2016

For instance, I do not see a fix that does not imply to add an executed boolean field to AbstractListenableFuture. It will duplicate a part of the ExecutionList code

@slandelle
Copy link
Contributor

Yep. It seems I broker it when I tried to lazy load the ExecutionList.
Working on a fix.

Thanks for reporting.

@slandelle slandelle added this to the 2.0.0 milestone Apr 4, 2016
@slandelle slandelle self-assigned this Apr 4, 2016
@nithril
Copy link
Author

nithril commented Apr 4, 2016

Thanks for this fix.

If I understand correctly the fix, just before the completion, more than one threads can execute concurrently the methods executionList().run() and thus the thread executor/context may be different.

  1. The thread that complete the future will set the hasRun flag and calls runListeners
  2. All threads that call addListener before 1. runListeners complete will process the runnables list too

Does it look like an issue for you?

@slandelle
Copy link
Contributor

Runnables will be processed only once, as they're stored in a LinkedBlockingQueue.
But you have no guarantee about the running thread and the ordering when you register listeners after completion because calling thread might be "stealing" work from the IO thread.

@nithril
Copy link
Author

nithril commented Apr 4, 2016

I agree with your first statement. My comment is about your second statement, maybe updating the javadoc could help to understand this side effect?

I wonder if AbstractListenableFuture#hasRun can now supersede ExecutionList#executed. And thus if the ExecutionList synchronized blocks can be removed ?

Another way: the ExecutionList#add method can return a boolean to the caller to mark the Runnable as executed and avoid a runListeners.

Whatever thanks for this fix. 👍

@slandelle
Copy link
Contributor

I wonder if AbstractListenableFuture#hasRun can now supersede ExecutionList#executed. And thus if the ExecutionList synchronized blocks can be removed ?

Not sure. And I'd rather avoid changing things here as this class is a fork from Guava.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants