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
core: Avoid locks in SynchronizationContext #5504
Conversation
Similar to what's done in SerializingExecutor, it's easy to make SynchronizationContext non-blocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@zhangkun83 PTAL
I know this isn't super performance critical, but this has the nice benefit that the app thread can no longer block the network thread.
synchronized (lock) { | ||
queue.add(checkNotNull(runnable, "runnable is null")); | ||
} | ||
queue.add(checkNotNull(runnable, "runnable is null")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may change the current behavior because the ordering of the runnables may be changed. There seems no harm for the ordering change (May need confirm from @zhangkun83 ). This change seems better, because executeLater
becomes always not blocking after the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline, the behavior is more or less the same. So no concern here.
} | ||
} | ||
} finally { | ||
drainingThread.set(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a race:
T1: call drain(): set drainingThread. Finish all tasks, exit loop
T2: call executeLater(): add a new task
T2: call drain(): try to set drainingThread, but is still set, give up
T1: still in drain(): reset drainingThread, then exit
End result: the task added by T2 will not be run unless someone calls drain() again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nm, I see you have another loop to catch that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
T1: still in drain(): reset drainingThread, then exit
For T1, because there is while (!queue.isEmpty())
in the end, it will drain again, not exit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree without lock it's harder to prove the correctness and easier to break the correctness in the future though. But this seems to have the same complexity as the SerializingExecutor
.
Thanks @carl-mastrangelo @dapengzhang0 @zhangkun83. A possible variation would be to use an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Similar to what's done in
SerializingExecutor
, it's easy to makeSynchronizationContext
non-blocking.