Skip to content

Commit ed0b086

Browse files
authored
Remove Sendable from AsyncBufferedByteIterator (#220)
# Motivation I raise on the pitch of `AsyncBufferedByteIterator` on the forums that I think the iterator must not be `Sendable`. The reasoning for this is twofold. First, an iterator is the connection of a consumer to the `AsyncSequence`; therefore, iterators should not be shared since it breaks that assumption. Secondly, the implementation of the `AsyncBufferedByteIterator` can be more straight forward since we don't have to check for unique ownership of the storage. # Modification Remove the `Sendable` conformances from `AsyncBufferedByteIterator`.
1 parent 8f5e8ce commit ed0b086

File tree

1 file changed

+7
-22
lines changed

1 file changed

+7
-22
lines changed

Sources/AsyncAlgorithms/AsyncBufferedByteIterator.swift

+7-22
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
/// }
4040
///
4141
///
42-
public struct AsyncBufferedByteIterator: AsyncIteratorProtocol, Sendable {
42+
public struct AsyncBufferedByteIterator: AsyncIteratorProtocol {
4343
public typealias Element = UInt8
4444
@usableFromInline var buffer: _AsyncBytesBuffer
4545

@@ -64,10 +64,13 @@ public struct AsyncBufferedByteIterator: AsyncIteratorProtocol, Sendable {
6464
}
6565
}
6666

67+
@available(*, unavailable)
68+
extension AsyncBufferedByteIterator: Sendable { }
69+
6770
@frozen @usableFromInline
68-
internal struct _AsyncBytesBuffer: @unchecked Sendable {
71+
internal struct _AsyncBytesBuffer {
6972
@usableFromInline
70-
final class Storage: Sendable {
73+
final class Storage {
7174
fileprivate let buffer: UnsafeMutableRawBufferPointer
7275

7376
init(
@@ -85,7 +88,7 @@ internal struct _AsyncBytesBuffer: @unchecked Sendable {
8588
}
8689
}
8790

88-
@usableFromInline internal var storage: Storage
91+
@usableFromInline internal let storage: Storage
8992
@usableFromInline internal var nextPointer: UnsafeRawPointer
9093
@usableFromInline internal var endPointer: UnsafeRawPointer
9194

@@ -110,24 +113,6 @@ internal struct _AsyncBytesBuffer: @unchecked Sendable {
110113
}
111114
try Task.checkCancellation()
112115
do {
113-
// If two tasks have access to this iterator then the references on
114-
// the storage will be non uniquely owned. This means that any reload
115-
// must happen into its own fresh buffer. The consumption of those
116-
// bytes between two tasks are inherently defined as potential
117-
// duplication by the nature of sending that buffer across the two
118-
// tasks - this means that the brief period in which they may be
119-
// sharing non reloaded bytes is to be expected; basically in that
120-
// edge case of making the iterator and sending that across to two
121-
// places to iterate is asking for something bizzare and the answer
122-
// should not be crash, but it definitely cannot be consistent.
123-
//
124-
// The unique ref check is here to prevent the potentials of a crashing
125-
// scenario.
126-
if !isKnownUniquelyReferenced(&storage) {
127-
// The count is not mutated across invocations so the access is safe.
128-
let capacity = storage.buffer.count
129-
storage = Storage(capacity: capacity)
130-
}
131116
let readSize: Int = try await readFunction(storage.buffer)
132117
if readSize == 0 {
133118
finished = true

0 commit comments

Comments
 (0)