-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Don't hold a lock in DelayedStream when calling realStream #1526
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
Don't hold a lock in DelayedStream when calling realStream #1526
Conversation
} else { | ||
realStream.setAuthority(authority); | ||
List<Runnable> toRun = new ArrayList<Runnable>(); | ||
while (true) { |
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 part is pretty tricky! It's worth a comment to explain why the loop is necessary. IIUC, it's because 1) runnable has to be run outside the lock; 2) passThrough can only be set to true when pendingCalls is empty, and this must be done inside the lock. So while runnable are running outside the lock, new runnables may be added to pendingCalls.
2cd1b3a
to
a5fa38c
Compare
@zhangkun83, PTAL; now with some more comments |
if (!cancelNow) { | ||
error = reason; | ||
listenerToClose = listener; | ||
// TODO(ejona): Close InputStreams? |
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.
Do other stream implementations do so?
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.
Currently MessageFramer
doesn't. At present that doesn't hurt much, but we will need to fix places eventually.
0bcdd7b
to
cde9fb8
Compare
@zhangkun83, @carl-mastrangelo, now with immediate calling of setDecompressor |
startedRealStream.setDecompressor(decompressor); | ||
public void setDecompressor(final Decompressor decompressor) { | ||
checkNotNull(decompressor, "decompressor"); | ||
checkState(realStream != null, "How did we receive a reply before the request is sent?"); |
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.
Here realStream is accessed from a thread different from what it is set. Shouldn't realStream be volatile?
Or check passThrough first?
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 is only expected to be called after setStream, since this is for processing responses. If setDecompressor is called in the same thread as setStream, then there are no memory concerns. If it is called in a different thread then that means there was some lock/volatile after setStream in order to transfer work to that other thread.
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.
Please correct me if I misunderstood Java memory model. My understanding is, when a work is transferred across threads locally, there is always some lock/volatile which form memory barrier between the write on the first thread and the read on the second thread. In this case, although logically setStream()
"happens before" setDecompressor()
, there is a network round-trip in the middle. I doubt Java memory model could form a memory barrier for events across network.
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.
... Good point. I can't find much to define what happens due to I/O. I've added an empty synchronized block to trigger happens-before.
LGTM |
if (passThrough) { | ||
runNow = true; | ||
} else { | ||
pendingCalls.add(runnable); |
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.
Why not just return after this point, and not check runNow at the end of this func?
@carl-mastrangelo, PTAL |
delayOrExecute(new Runnable() { | ||
@Override | ||
public void run() { | ||
realStream.flush(); |
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 loses the original codes intent. Flushes were coalesced previously. Are you certain that is the behavior you want?
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 was okay with replaying each flush. I wasn't too concerned about that level of performance in the slow case. It should be possible to coalesce flushes, but is slightly non-trivial because of interactions with other methods like halfClose()
and cancel()
. I'm don't have much problem calling optimizing flush as future work, but I do have some trouble caring about the case at present because it will be no worse than when DelayedStream
is not being used.
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.
Ok, just wanted to make sure this was intentional, not a side effect.
@carl-mastrangelo, thanks for the comments. PTAL |
@ejona86 LGTM |
Our current lock ordering rules prohibit holding a lock when calling the channel and stream. This change avoids the lock for both DelayedClientTransport and DelayedStream. It is effectively a rewrite of DelayedStream. The fixes to ClientCallImpl were to ensure sane state in DelayedStream. Fixes grpc#1510
7ac08c4
to
eccd231
Compare
Our currently lock ordering rules prohibit holding a lock when calling
the channel and stream. This change avoids the lock for both
DelayedClientTransport and DelayedStream. It is effectively a rewrite of
DelayedStream.
The fixes to ClientCallImpl were to ensure sane state in DelayedStream.