Fix a race in buffer usage of Http2Connection.ProcessPingFrame#39242
Fix a race in buffer usage of Http2Connection.ProcessPingFrame#39242geoffkizer merged 5 commits intodotnet:masterfrom
Conversation
…ing buffer might be reused before we've sent it. Resolve #38788.
|
/azp run |
|
Azure Pipelines successfully started running 4 pipeline(s). |
| // Send PING ACK | ||
| // Don't wait for completion, which could happen asynchronously. | ||
| LogExceptions(SendPingAckAsync(_incomingBuffer.ActiveMemory.Slice(0, FrameHeader.PingLength))); | ||
| byte[] pingContent = _incomingBuffer.ActiveMemory.Slice(0, FrameHeader.PingLength).ToArray(); |
There was a problem hiding this comment.
I think we could just do BinaryPrimitives.ReadInt64BigEndian or whatever it is, and avoid an alloc here.
There was a problem hiding this comment.
I considered it, but the rarity of PING makes me feel that this is not a worthwhile micro-optimization given that we'd then need to document endianness.
There was a problem hiding this comment.
we'd then need to document endianness.
Why? Wouldn't this just be roundtripping the data? As long as you used routines for converting to and from a long that employed the same endianness, it wouldn't be visible. You can also use BitConverter.ToInt64/TryWriteBytes, which just uses the machine's endianness and won't force any reversals.
There was a problem hiding this comment.
When it doesn't matter, like in this case, I'd rather reduce complexities and have obvious code than need to comment something.
As long as you used routines for converting to and from a long that employed the same endianness, it wouldn't be visible. You can also use BitConverter.
Either way, this is going to need comments. If you know HTTP2 and know these are just opaque bytes and not an integer, you'll find it suspicious to see an endian-specific read. Even if you didn't know HTTP2, if you see BitConverter being used to read an integer in networking code you'd immediately flag it as a possible bug.
There was a problem hiding this comment.
Either way, this is going to need comments
It needs comments even with ToArray. Someone reviewing the code in a year needs to know that this wasn't an oversight and what race conditions it's working around. Otherwise it's very likely to be reverted by someone trying to remove allocations.
When it doesn't matter, like in this case, I'd rather reduce complexities and have obvious code
It shouldn't be any more, or any more complicated, code. It's ToInt64 instead of ToArray, and TryWriteBytes instead of CopyTo. If anything it's arguably clearer code, because it makes the payload size crystal clear in the method contract, whereas today it appears to accept arbitrarily-sized data.
I assume pings are super uncommon, so from a perf perspective it probably doesn't matter. But at least from my perspective the code would be cleaner with a long, and so may as well get the perf benefit, too.
Up to you. If you choose not to switch, please do add a detailed comment about why ToArray is necessary.
There was a problem hiding this comment.
We already read a bunch of stuff out of frames as big endian, so I don't see that adding one more here is much different.
|
What's blocking this PR from merge? Can we get it done by EOW? |
It'll be ready today. |
| // Send PING ACK | ||
| // Don't wait for completion, which could happen asynchronously. | ||
| LogExceptions(SendPingAckAsync(_incomingBuffer.ActiveMemory.Slice(0, FrameHeader.PingLength))); | ||
| // We discard the the frame from the incoming buffer, possibly before |
There was a problem hiding this comment.
Generally speaking, all these routines read the frame completely before returning. So I don't think you need to comment specifically on that.
geoffkizer
left a comment
There was a problem hiding this comment.
Aside from clarifying the comment, LGTM
| // We discard the the frame from the incoming buffer, possibly before | ||
| // SendPingAckAsync is completed, so we need to save our own copy of the data. | ||
| // The data is not an integer in the RFC, but this will avoid an array allocation. | ||
| long pingContent = BinaryPrimitives.ReadInt64BigEndian(_incomingBuffer.ActiveSpan.Slice(0, FrameHeader.PingLength)); |
There was a problem hiding this comment.
If anything, move this line to above the "Send PING ACK" comment above, since that comment is specifically commenting on the call to SendPingAckAsync
3117da3 to
69b5793
Compare
| // Don't wait for completion, which could happen asynchronously. | ||
| LogExceptions(SendPingAckAsync(_incomingBuffer.ActiveMemory.Slice(0, FrameHeader.PingLength))); | ||
|
|
||
| // SendPingAckAsync copies the buffer for us, so it is safe to not wait for completion. |
There was a problem hiding this comment.
I think I read this code wrong when I initially reviewed it...
It seems better to just read the pingContent into a long here, in ProcessPingFrame, and then pass the resulting long into SendPingAckAsync. That's generally how these Process*Frame routines work, they read whatever data they need out of the frame. And it's more obviously correct.
There was a problem hiding this comment.
By reading the integer in SendPingAckAsync, it avoids depending on ProcessPingFrame (to have the same endianness scheme).
I agree that consistency with other Process*Frame is better here, so I've moved the read into ProcessPingFrame and added comments to note the dependency.
|
|
||
| // Copy pingContent before we go async so the caller can | ||
| // discard their buffer without waiting for us to complete. | ||
| long pingContentLong = BitConverter.ToInt64(pingContent.Span); |
There was a problem hiding this comment.
Is there a particular reason to use BitConverter here, as opposed to BinaryPrimitives.ReadUInt64BigEndian? We use BinaryPrimitives elsewhere in the code. Seems like we should be consistent.
There was a problem hiding this comment.
I actually haven't found any instances of BinaryPrimitives being used to shuttle opaque bytes, so this isn't a consistency issue. It made sense to avoid byte shuffling when endianness concerns could be kept to a single small method.
|
LGTM, thanks. |
Fix a race in buffer usage of Http2Connection.ProcessPingFrame Commit migrated from dotnet/corefx@1d03a91
Incoming buffer might be reused before we've sent it. Resolve #38788.