Description
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 (via makeAsyncIterator()
).
This can lead to incosistent state where the flow continues past 386 (locked_iteratorCreated == false
) and the intentional crash in makeAsyncIterator()
is triggered when another thread sets locked_iteratorCreated
to true
in the meantime.
With enough threads calling e.g. HTTPBody.ByteChunk(collecting:, upTo:)
on the same HTTPBody
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 from tryToMarkIteratorCreated()
?
I think that would honor both the intention that a HTTPBody
with IterationBehavior.single
may be iterated only once and at the same time the fact that HTTPBody
conforms to AsyncSequence
.
The same applies to MultipartBody.makeAsyncIterator()
Reproduction
My test case in Test_HTTPBody.swift crashes reliably on my M1 Pro:
func testThreadSafety() async throws {
for testIteration in 0..<100 {
print("Starting test iteration \(testIteration)")
let sut = HTTPBody([HTTPBody.ByteChunk("")], length: .unknown, iterationBehavior: .single)
let collectSuccess = expectation(description: "collectSuccess")
let collectFailure = expectation(description: "collectFailure")
let taskCount = 100
collectFailure.expectedFulfillmentCount = taskCount - 1
for _ in 0..<taskCount {
Task(priority: .high) {
try? await Task.sleep(nanoseconds: 10_000_000) // makes collision a bit more likely from experience
do {
_ = try await HTTPBody.ByteChunk(collecting: sut, upTo: 99)
collectSuccess.fulfill()
} catch {
collectFailure.fulfill()
}
}
}
await fulfillment(of: [collectSuccess, collectFailure], timeout: 10, enforceOrder: false)
}
}
Package version(s)
main
branch of swift-openapi-runtime
:
.
├── swift-http-types<https://github.com/apple/swift-http-types@1.0.2>
└── swift-docc-plugin<https://github.com/apple/swift-docc-plugin@1.3.0>
└── swift-docc-symbolkit<https://github.com/apple/swift-docc-symbolkit@1.0.0>
Expected behavior
No crash.
Environment
swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-macosx14.0
Additional information
I could provide a pull request with a fix (have to find a bit of time for that).