-
Notifications
You must be signed in to change notification settings - Fork 126
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
Potential Crash: HTTPBody.collect(upTo:) is not thread safe #502
Comments
Hi @LarsPetersHH,
This part is unclear to me - if you create an HTTPBody with You're right that we're not holding the lock between the proactive check in |
Hi @czechboy0 I guess there are really two parts to this report for me: a) Intentional Crash
Why crash? Why not throw an error? What is the thing that is worse than a crash that the crash protects the user from in this case? The code comment says b) Thread safety Same test, but on a single thread passes:
So when the past passes on a single thread but doesn't in a concurrent situation, doesn't that mean that there is a thread safety issue? |
You're right - this needs a fix to avoid crashes when only an error should be thrown. Would you be interested in opening a PR for it, @LarsPetersHH? |
@czechboy0 Sure, I'll be happy to do that. Give me a few days to find the time. Maybe over the weekend. |
No rush, thanks for reporting, @LarsPetersHH! 🙏 |
@czechboy0 Here you go: apple/swift-openapi-runtime#95 |
### Motivation Fixes apple/swift-openapi-generator#502 - Ensure thread safety of `HTTPBody.collect(upTo)`. - `makeAsyncIterator()`: Instead of crashing, return AsyncSequence which throws `TooManyIterationsError` thereby honoring the contract for `IterationBehavior.single` (HTTPBody, MultipartBody) ### Modifications - HTTPBody, MultipartBody: `makeAsyncIterator()`: removed `try!`, catch error and create a sequence which throws the error on iteration. - This removed the need for `try checkIfCanCreateIterator()` in `HTTPBody.collect(upTo)`. **Note**: This creates a small change in behavior: There may be a `TooManyBytesError` thrown before the check for `iterationBehavior`. This approach uses the simplest code, IMO. If we want to keep that `iterationBehavior` is checked first and only after that for the length, then the code needs to be more complex. - Removed `try checkIfCanCreateIterator()` in both classes (only used in `HTTPBody`). ### Result - No intentional crash in `makeAsyncIterator()` anymore. - Tests supplied as example in apple/swift-openapi-generator#502 succeed. ### Test Plan - Added check in `Test_Body.testIterationBehavior_single()` to ensure that using `makeAsyncIterator()` directly yields the expected error. - Added tests to check iteration behavior of `MultipartBody`. --------- Co-authored-by: Lars Peters <[email protected]>
Description
The shared resource (
locked_iteratorCreated
) is protected by a lock in line 386 (try checkIfCanCreateIterator()
) but not protected after that – until it is protect again in line 395 (viamakeAsyncIterator()
).This can lead to incosistent state where the flow continues past 386 (
locked_iteratorCreated == false
) and the intentional crash inmakeAsyncIterator()
is triggered when another thread setslocked_iteratorCreated
totrue
in the meantime.With enough threads calling e.g.
HTTPBody.ByteChunk(collecting:, upTo:)
on the sameHTTPBody
object, it is pretty easy to produce a crash.In
collect(upTo:)
locked_iteratorCreated
needs to be protected until after the async sequence was created to ensure consistency (avoiding deadlocks, of course).My I also suggest instead of the intentional crash in
HTTPBody.makeAsyncIterator()
(line 346) to return an async throwing sequence which (re-)throws the error thrown fromtryToMarkIteratorCreated()
?I think that would honor both the intention that a
HTTPBody
withIterationBehavior.single
may be iterated only once and at the same time the fact thatHTTPBody
conforms toAsyncSequence
.The same applies to
MultipartBody.makeAsyncIterator()
Reproduction
My test case in Test_HTTPBody.swift crashes reliably on my M1 Pro:
Package version(s)
main
branch ofswift-openapi-runtime
:Expected behavior
No crash.
Environment
Additional information
I could provide a pull request with a fix (have to find a bit of time for that).
The text was updated successfully, but these errors were encountered: