Skip to content
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

DefaultPromise may notify Listeners in wrong order #2157

Closed
wants to merge 3 commits into from

Conversation

normanmaurer
Copy link
Member

This pull-req is not complete yet but show a race in DefaultPromise which can lead to have FutureListener notified in a wrong order.

One way to fix this is to enlarge the scope of the synchronization but I would like to think about some better way to fix it.

This can happen if addListener is called from a different thread then the EventExecutor that is used by the DefaultPromise itself and the DefaultPromise is notified while adding these listeners.

@normanmaurer
Copy link
Member Author

@trustin any good idea ?

@ghost
Copy link

ghost commented Jan 27, 2014

Build result for #2157 at f60eeac: Success

@ghost
Copy link

ghost commented Feb 3, 2014

Build result for #2157 at 6ca8e87b461260f37f4ada9a4b48aac5d398c6ea: Failure

@normanmaurer
Copy link
Member Author

@trustin please review... we could also make use of Unsafe (if present) to replace Atomic_FieldUpdater as Unsafe is faster because it does not do any validation (which Atomic_FieldUpdater) does.

@normanmaurer
Copy link
Member Author

Here they also tell about the performance difference:
https://groups.google.com/forum/#!msg/mechanical-sympathy/X-GtLuG0ETo/_KcpM7T8ZX4J

@ghost
Copy link

ghost commented Feb 3, 2014

Build result for #2157 at 60489c1940fe3ff2e308bda4412c97efb51d993d: Success

@normanmaurer
Copy link
Member Author

@daschl please also review

@ghost
Copy link

ghost commented Feb 4, 2014

Build result for #2157 at 6ccad82e3d8de2a3b0ae49bfa3706b73a074707c: Success

@ghost
Copy link

ghost commented Feb 4, 2014

Build result for #2157 at 72482f0858673a4432a28e8bee8552bf4b3e105a: Success

@normanmaurer
Copy link
Member Author

Also about performance. With this branch I get slightly better performance (able to reproduce every time) , most likely because of the Atomic*FieldUpdater changes:

This branch:

[nmaurer@ear]~/wrk% ./wrk  -H 'Host: localhost' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Connection: keep-alive' -d 120 -c 256 -t 8 --pipeline 16 http://localhost:8080/plaintext
Running 2m test @ http://localhost:8080/plaintext
  8 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.09ms    2.96ms  55.53ms   89.68%
    Req/Sec   223.13k    77.56k  403.55k    64.58%
  199582048 requests in 2.00m, 26.95GB read
Requests/sec: 1663182.82
Transfer/sec:    229.99MB

4.0 branch:

[nmaurer@ear]~/wrk% ./wrk  -H 'Host: localhost' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Connection: keep-alive' -d 120 -c 256 -t 8 --pipeline 16 http://localhost:8080/plaintext
Running 2m test @ http://localhost:8080/plaintext
  8 threads and 256 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.08ms    2.86ms  52.51ms   89.43%
    Req/Sec   222.70k    77.23k  410.67k    64.37%
  199282272 requests in 2.00m, 26.91GB read
Requests/sec: 1660680.58
Transfer/sec:    229.64MB

@trustin
Copy link
Member

trustin commented Feb 4, 2014

UnsafeAtomic*FieldUpdater could be extracted out to separate .java files?

listeners.add(this);
}
};
new Thread(new Runnable() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use GlobalEventExecutor instead.

@trustin
Copy link
Member

trustin commented Feb 4, 2014

I wish it does not create a new entry when there's only one listener.

@normanmaurer
Copy link
Member Author

Will see what I can do... Rest looks good beside your comments and ready to pull in after addressing them?

Am 05.02.2014 um 00:36 schrieb Trustin Lee notifications@github.com:

I wish it does not create a new entry when there's only one listener.


Reply to this email directly or view it on GitHub.

@ghost
Copy link

ghost commented Feb 5, 2014

Build result for #2157 at 91d413322f407e9c49556289ce2a2d9d4f2f7c62: Success

@ghost
Copy link

ghost commented Feb 5, 2014

Build result for #2157 at 85a6b905f3589ac334257929e1b779e4f8ca57c7: Success

@normanmaurer
Copy link
Member Author

@trustin addressed all your comments. Please review again and let me know what you think now ;)

@ghost
Copy link

ghost commented Feb 5, 2014

Build result for #2157 at 7de37ca4c29d4ed8bdf59d2d7cc9403167bb3abb: Success

@normanmaurer
Copy link
Member Author

@trustin ok to merge?

@ghost
Copy link

ghost commented Feb 6, 2014

Build result for #2157 at a8e84db1eabcb2e6559a23c4f655ce411f163afc: Success

Norman Maurer added 2 commits February 6, 2014 08:03
This also remove some more synchronization and make heavy use of atomic operations now. The downside is that it creates more objects when FutureListeners are added. But this was the only performant way I could find to still make sure the order is correct while still not do heavy synchronization.
…r and AtomicReferenceFieldUpdater

Make use of these in the new DefaultPromise but also in other areas.
@ghost
Copy link

ghost commented Feb 6, 2014

Build result for #2157 at 2fbc42a: Success

@normanmaurer
Copy link
Member Author

Squashed into 2 commits

boolean added = false;
if (entry == null) {
added = ENTRY_UPDATER.compareAndSet(this, null, newEntry);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to wrap with if (entry == null) { } because compareAndSet() already does the same check?

PROGRESSIVE_SIZE_UPDATER.decrementAndGet(this);
}
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop looks weird to me. The first if (entry == null || ...) block and the second if (...) { if (...) { block are pretty much same except that the second one decreases the counter, which means the second block will never be evaluated.

}
}
return this;
entry = entry.next;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This loop is vulnerable to the race where more than on thread attempt to append a listener. When one thread succeeds to append a listener, the other thread can dereference it and thus we lose it.
  • Also, you are traversing the linked list sequentially, which is potentially slow. Perhaps we could maintain the reference to the last listener?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes... but this will make it even more hard to not use synchronized and I though it should not be a big problem. Maybe I could also just use synchronized and be happy ;)

@trustin
Copy link
Member

trustin commented Feb 6, 2014

How about this:

  1. Add a volatile field that tells if the primary notification is actually made. It will be set from the executor thread when all listeners added so far were notified (see notifyListeners()).
  2. In addListener() we check if the volatile field is set. If set, it is safe to use the current execution path. If not set, always use executor.execute() for notification.
  3. In the notification task we created in addListener(), we always check if the primary notification is made by checking the volatile field, and then re-schedule the task instead of making a notification. If the volatile field is set, we are safe to notify immediately here.

This basically uses executor's task queue for ordering.

@ghost
Copy link

ghost commented Feb 6, 2014

Build result for #2157 at d3a1274: Success

@normanmaurer
Copy link
Member Author

@trustin tried this and did not work out so far. Not sure if we are just trying to be too smart..

@normanmaurer
Copy link
Member Author

I will check again this evening about this issue..

@trustin
Copy link
Member

trustin commented Feb 7, 2014

#2186 shows an alternative fix

@normanmaurer
Copy link
Member Author

Was fixed by 309ee68

@normanmaurer normanmaurer deleted the promise_race branch February 8, 2014 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants