Skip to content

Commit 41e53ad

Browse files
committed
simplify
1 parent a86f587 commit 41e53ad

File tree

1 file changed

+41
-62
lines changed

1 file changed

+41
-62
lines changed

Sources/AsyncHTTPClient/HTTPHandler.swift

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -53,63 +53,65 @@ extension HTTPClient {
5353
}
5454

5555
@inlinable
56-
@preconcurrency
5756
func writeChunks<Bytes: Collection>(
5857
of bytes: Bytes,
5958
maxChunkSize: Int
60-
) -> EventLoopFuture<Void> where Bytes.Element == UInt8, Bytes: Sendable, Bytes.SubSequence: Sendable {
59+
) -> EventLoopFuture<Void> where Bytes.Element == UInt8, Bytes: Sendable {
6160
// `StreamWriter` has design issues, for example
6261
// - https://github.com/swift-server/async-http-client/issues/194
6362
// - https://github.com/swift-server/async-http-client/issues/264
6463
// - We're not told the EventLoop the task runs on and the user is free to return whatever EL they
6564
// want.
6665
// One important consideration then is that we must lock around the iterator because we could be hopping
6766
// between threads.
68-
typealias Iterator = BodyStreamIterator<Bytes>
67+
typealias Iterator = EnumeratedSequence<ChunksOfCountCollection<Bytes>>.Iterator
6968
typealias Chunk = (offset: Int, element: ChunksOfCountCollection<Bytes>.Element)
7069

71-
func makeIteratorAndFirstChunk(
72-
bytes: Bytes
73-
) -> (
74-
iterator: NIOLockedValueBox<Iterator>,
75-
chunk: Chunk
76-
)? {
77-
var iterator = bytes.chunks(ofCount: maxChunkSize).enumerated().makeIterator()
78-
guard let chunk = iterator.next() else {
79-
return nil
70+
// HACK (again, we're not told the right EventLoop): Let's write 0 bytes to make the user tell us...
71+
return self.write(.byteBuffer(ByteBuffer())).flatMapWithEventLoop { (_, loop) in
72+
func makeIteratorAndFirstChunk(
73+
bytes: Bytes
74+
) -> (iterator: Iterator, chunk: Chunk)? {
75+
var iterator = bytes.chunks(ofCount: maxChunkSize).enumerated().makeIterator()
76+
guard let chunk = iterator.next() else {
77+
return nil
78+
}
79+
80+
return (iterator, chunk)
8081
}
8182

82-
return (NIOLockedValueBox(BodyStreamIterator(iterator)), chunk)
83-
}
84-
85-
guard let (iterator, chunk) = makeIteratorAndFirstChunk(bytes: bytes) else {
86-
return self.write(IOData.byteBuffer(.init()))
87-
}
83+
guard let iteratorAndChunk = makeIteratorAndFirstChunk(bytes: bytes) else {
84+
return loop.makeSucceededVoidFuture()
85+
}
8886

89-
@Sendable // can't use closure here as we recursively call ourselves which closures can't do
90-
func writeNextChunk(_ chunk: Chunk, allDone: EventLoopPromise<Void>) {
91-
if let (index, element) = iterator.withLockedValue({ $0.next() }) {
92-
self.write(.byteBuffer(ByteBuffer(bytes: chunk.element))).map {
93-
if (index + 1) % 4 == 0 {
94-
// Let's not stack-overflow if the futures insta-complete which they at least in HTTP/2
95-
// mode.
96-
// Also, we must frequently return to the EventLoop because we may get the pause signal
97-
// from another thread. If we fail to do that promptly, we may balloon our body chunks
98-
// into memory.
99-
allDone.futureResult.eventLoop.execute {
87+
var iterator = iteratorAndChunk.0
88+
let chunk = iteratorAndChunk.1
89+
90+
// can't use closure here as we recursively call ourselves which closures can't do
91+
func writeNextChunk(_ chunk: Chunk, allDone: EventLoopPromise<Void>) {
92+
let loop = allDone.futureResult.eventLoop
93+
loop.assertInEventLoop()
94+
95+
if let (index, element) = iterator.next() {
96+
self.write(.byteBuffer(ByteBuffer(bytes: chunk.element))).hop(to: loop).assumeIsolated().map {
97+
if (index + 1) % 4 == 0 {
98+
// Let's not stack-overflow if the futures insta-complete which they at least in HTTP/2
99+
// mode.
100+
// Also, we must frequently return to the EventLoop because we may get the pause signal
101+
// from another thread. If we fail to do that promptly, we may balloon our body chunks
102+
// into memory.
103+
allDone.futureResult.eventLoop.assumeIsolated().execute {
104+
writeNextChunk((offset: index, element: element), allDone: allDone)
105+
}
106+
} else {
100107
writeNextChunk((offset: index, element: element), allDone: allDone)
101108
}
102-
} else {
103-
writeNextChunk((offset: index, element: element), allDone: allDone)
104-
}
105-
}.cascadeFailure(to: allDone)
106-
} else {
107-
self.write(.byteBuffer(ByteBuffer(bytes: chunk.element))).cascade(to: allDone)
109+
}.nonisolated().cascadeFailure(to: allDone)
110+
} else {
111+
self.write(.byteBuffer(ByteBuffer(bytes: chunk.element))).cascade(to: allDone)
112+
}
108113
}
109-
}
110114

111-
// HACK (again, we're not told the right EventLoop): Let's write 0 bytes to make the user tell us...
112-
return self.write(.byteBuffer(ByteBuffer())).flatMapWithEventLoop { (_, loop) in
113115
let allDone = loop.makePromise(of: Void.self)
114116
writeNextChunk(chunk, allDone: allDone)
115117
return allDone.futureResult
@@ -189,7 +191,7 @@ extension HTTPClient {
189191
@preconcurrency
190192
@inlinable
191193
public static func bytes<Bytes>(_ bytes: Bytes) -> Body
192-
where Bytes: RandomAccessCollection, Bytes: Sendable, Bytes.SubSequence: Sendable, Bytes.Element == UInt8 {
194+
where Bytes: RandomAccessCollection, Bytes: Sendable, Bytes.Element == UInt8 {
193195
Body(contentLength: Int64(bytes.count)) { writer in
194196
if bytes.count <= bagOfBytesToByteBufferConversionChunkSize {
195197
return writer.write(.byteBuffer(ByteBuffer(bytes: bytes)))
@@ -1081,26 +1083,3 @@ extension RequestBodyLength {
10811083
self = .known(length)
10821084
}
10831085
}
1084-
1085-
@usableFromInline
1086-
struct BodyStreamIterator<
1087-
Bytes: Collection
1088-
>: IteratorProtocol, @unchecked Sendable where Bytes.Element == UInt8, Bytes: Sendable {
1089-
// @unchecked: swift-algorithms hasn't adopted Sendable yet. By inspection, the iterator
1090-
// is safe to annotate as sendable.
1091-
@usableFromInline
1092-
typealias Element = (offset: Int, element: Bytes.SubSequence)
1093-
1094-
@usableFromInline
1095-
var _backing: EnumeratedSequence<ChunksOfCountCollection<Bytes>>.Iterator
1096-
1097-
@inlinable
1098-
init(_ backing: EnumeratedSequence<ChunksOfCountCollection<Bytes>>.Iterator) {
1099-
self._backing = backing
1100-
}
1101-
1102-
@inlinable
1103-
mutating func next() -> Element? {
1104-
self._backing.next()
1105-
}
1106-
}

0 commit comments

Comments
 (0)