Add missing transition AsyncBackpressuredStream state machine #27
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation
Some of our bidirectional streaming tests were failing intermittently. When they failed, the symptom was that more bytes were received by the end user (in the test) than the server sent.
For example, in the test
testStreamingDownload_1kChunk_10kChunks_100BDownloadWatermark
, we expect:✅ The server sends 10,000 chunks of 1024 bytes:
✅ URLSession
didReceive data
callback called a non-deterministic number of times because it may re-chunk the bytes internally, but the total number of bytes through the delegate calls is 10,240,000:❌ The response body chunks emitted by the
AsyncBackpressuredStream
to the user (in the test) match 1:1 those received by the delegate callback:❌ The total number of bytes emitted by the
AsyncBackpressuredStream
to the user (in the test) match is 10,240,000 and it can then reconstruct 10,000 chunks of 1024 to match what the server sent:So we see that there was one more element emitted from the
AsyncBackpressuredStream
than the delegate callback wrote, which meant the test saw an additional 40960 bytes than it expected to and consequently reconstructed an additional 40 chunks of size 1024 over what the server sent.We can see that the
AsyncBackpressuredStream
duplicates an element along the way, of 40960 bytes,After some investigation, it turned out there was a missing transition in the state machine that underlies the
AsyncBackpressuredStream
. When callingsuspendNext
when there are buffered elements, but we are above the watermark, we popped the first item from the buffer and returned it without updating the state (with the new buffer, without the popped element).Consequently, this meant that the next call to
next()
would return the same element again.Modifications
The following modifications have been made in separate commits to aid review:
Result
Duplicate elements are no longer emitted from the response body.
Test Plan
Additional notes
The implementation we are using for
AsyncBackpressuredStream
was taken from an early draft of SE-0406. We should probably move to using something closer matching that of the current PR to the Swift tree, or that used by swift-grpc, which has also adopted this code and cleaned it up to remove the dependencies on the standard library internals. Additionally, that implementation does not have this missing state transition and also adds an intermediate state to the state machine to avoid unintended copy-on-write.