Skip to content

HttpServerUpgradeHandler shouldn't wait for flush to reshape pipeline#7807

Merged
normanmaurer merged 1 commit into
netty:4.1from
bryce-anderson:issue_3689
Mar 28, 2018
Merged

HttpServerUpgradeHandler shouldn't wait for flush to reshape pipeline#7807
normanmaurer merged 1 commit into
netty:4.1from
bryce-anderson:issue_3689

Conversation

@bryce-anderson
Copy link
Copy Markdown
Contributor

@bryce-anderson bryce-anderson commented Mar 23, 2018

Motivation:

There is a race between both flushing the upgrade response and receiving
more data before the flush ChannelPromise can fire the listener and reshape
the pipeline. Since We have already committed to an upgrade by writing the
upgrade response, we should immediately prepare for handling the next
protocol.

Modifications:

The pipeline reshaping logic in HttpServerUpgradeHandler has been moved
out of the ChannelFutureListener attached to the write of the upgrade
response and happens immediately after the writeAndFlush call, but
before the method returns.

Result:

The pipeline is no longer subject to receiving more data before the
pipeline has been reformed.

@normanmaurer
Copy link
Copy Markdown
Member

normanmaurer commented Mar 23, 2018 via email

@bryce-anderson
Copy link
Copy Markdown
Contributor Author

bryce-anderson commented Mar 23, 2018

I think this should fix #7740, but am not 100% sure. Since the HttpServerUpgradeHandler is an HttpObjectAggregator, it should clear the inbound pipeline of the entire initial request, so unless the client sent and upgrade request and then tried to pipeline another (which is incorrect) the inbound socket channel should be clean at the point the pipeline is reshaped.

I'm also open to suggestions for tests. Currently there aren't any dedicated tests for HttpServerUpgradeHandler, but I may try and get one in.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please add a license header.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we use 4 spaces, please fix :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

add assert

@bryce-anderson
Copy link
Copy Markdown
Contributor Author

Thanks for the feedback @normanmaurer, I'll address it this afternoon. Cc'ing @ejona86 as he may have comments about correctness.

@ejona86
Copy link
Copy Markdown
Member

ejona86 commented Mar 23, 2018

Didn't look in great detail, but this makes me feel better. There's really no reason I can think of to delay until the write is complete and I agree it just seems to add a race.

@bryce-anderson
Copy link
Copy Markdown
Contributor Author

Thanks for the double check @ejona86. @normanmaurer, I think I addressed your comments. Thanks again for the review.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just use Collections.<CharSequence>emptyList() and so remove the dependency on the guava here ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Call super.write(....) to ensure we not "leak the message" and also to ensure the promise is notified.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was deliberate so that we could be confident that the pipeline reshaping happened in this call stack irrespective of the promise being satisfied.

That said, perhaps it's not something that is likely to regress. And we do need to do something with the outbound message, good catch.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just use Unpooled.copiedBuffer(upgradeString, CharsetUitl.US_ASCII)

@normanmaurer
Copy link
Copy Markdown
Member

@bryce-anderson #3698 does not seem to be the correct issue to reference here... Can you check ?

@bryce-anderson
Copy link
Copy Markdown
Contributor Author

@normanmaurer, sorry, you're right: the issue it attempts to fix is #7740.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there a reason to delay this until the write succeeds? (I can't think of any)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The write hasn't necessarily succeeded yet as writeComplete is a ChannelPromise. We should enqueue the write before reshaping the pipeline as the incoming codec may try to write something when it gets added to the pipeline.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe just delay the write by using ctx.eventLoop().execute(...) and schedule the write via it ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That did the trick, thanks @normanmaurer. I'd tried to defer the satisfaction of the promise via indirection through the executor, but the flush() call would run it. Your suggestion is much more reasonable anyway.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah, shoot, my comment was really unclear - I wanted to note that we fireUserEventTriggered before the write is complete and ask if there could be a reason to delay that event until the write finished instead. I'm not sure what the contract around that user event is, but maybe some folks rely on that happening after the write?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this test LGTM, but as a sanity check, does it fail without your change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, not anymore. Originally it did, but now that the write override on line 96 calls super.write as @normanmaurer requested, it will succeed either way. See his second round of comments.

I'm happy to take suggestions on how to test this cleanly, but the only thing I can think of is to drop the promise (what I had originally, and it did fail without my patch) or to store it somewhere else and fail it later with the former being bad form and the latter being a pretty ugly mess.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Using normans suggestion it now fails without the changes to HttpServerUpgradeHandler.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@bryce-anderson did you forget to push this change ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@normanmaurer yes, I had. Sorry about that.

@bryce-anderson bryce-anderson force-pushed the issue_3689 branch 2 times, most recently from 0149216 to e374c04 Compare March 27, 2018 17:36
@normanmaurer normanmaurer added this to the 4.1.23.Final milestone Mar 27, 2018
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

write and flush shouldn't throw ... but consider including it in the try block below just to have less assumptions about releasing the event relative to behavior of this method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: just put this on the previous line. line length of 120 is permitted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The bigger issue was that the comment was incomplete. Sorry about that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: remove throws Exception

@normanmaurer
Copy link
Copy Markdown
Member

@bryce-anderson please address @Scottmitch s comments and ping me once done. I will pull in then.

Motivation:

There is a race between both flushing the upgrade response and receiving
more data before the flush ChannelPromise can fire and reshape the
pipeline. Since We have already committed to an upgrade by writing the
upgrade response, we need to be immediately prepared for handling the
next protocol.

Modifications:

The pipeline reshaping logic in HttpServerUpgradeHandler has been moved
out of the ChannelFutureListener attached to the write of the upgrade
response and happens immediately after the writeAndFlush call, but
before the method returns.

Result:

The pipeline is no longer subject to receiving more data before the
pipeline has been reformed.
@bryce-anderson
Copy link
Copy Markdown
Contributor Author

Thanks for the feedback @Scottmitch. @normanmaurer, should be ready. Thanks again for the reviews folks.

@normanmaurer
Copy link
Copy Markdown
Member

Will merge once CI completes @bryce-anderson ... thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants