Skip to content

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

Merged
merged 1 commit into from
Mar 9, 2016

Conversation

ejona86
Copy link
Member

@ejona86 ejona86 commented Mar 5, 2016

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.

} else {
realStream.setAuthority(authority);
List<Runnable> toRun = new ArrayList<Runnable>();
while (true) {
Copy link
Contributor

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.

@ejona86 ejona86 force-pushed the delayed-no-lock-when-calling-stream branch from 2cd1b3a to a5fa38c Compare March 7, 2016 22:01
@ejona86
Copy link
Member Author

ejona86 commented Mar 7, 2016

@zhangkun83, PTAL; now with some more comments

if (!cancelNow) {
error = reason;
listenerToClose = listener;
// TODO(ejona): Close InputStreams?
Copy link
Contributor

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?

Copy link
Member Author

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.

@ejona86 ejona86 force-pushed the delayed-no-lock-when-calling-stream branch from 0bcdd7b to cde9fb8 Compare March 7, 2016 23:44
@ejona86
Copy link
Member Author

ejona86 commented Mar 7, 2016

@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?");
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

@zhangkun83
Copy link
Contributor

LGTM

if (passThrough) {
runNow = true;
} else {
pendingCalls.add(runnable);
Copy link
Contributor

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?

@ejona86
Copy link
Member Author

ejona86 commented Mar 8, 2016

@carl-mastrangelo, PTAL

delayOrExecute(new Runnable() {
@Override
public void run() {
realStream.flush();
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

@ejona86
Copy link
Member Author

ejona86 commented Mar 9, 2016

@carl-mastrangelo, thanks for the comments. PTAL

@carl-mastrangelo
Copy link
Contributor

@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
@ejona86 ejona86 force-pushed the delayed-no-lock-when-calling-stream branch from 7ac08c4 to eccd231 Compare March 9, 2016 23:15
@ejona86 ejona86 merged commit eccd231 into grpc:master Mar 9, 2016
@ejona86 ejona86 deleted the delayed-no-lock-when-calling-stream branch March 9, 2016 23:38
@lock lock bot locked as resolved and limited conversation to collaborators Jan 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants