-
Notifications
You must be signed in to change notification settings - Fork 128
Potential Crash: HTTPBody.collect(upTo:) is not thread safe #502
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
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 <software@lpeters.net>
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: