Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5df04c2

Browse files
committed
buffer: unify suspending and non-suspending algorithms
1 parent 247274b commit 5df04c2

File tree

7 files changed

+120
-359
lines changed

7 files changed

+120
-359
lines changed

Sources/AsyncAlgorithms/Buffer/AsyncBufferSequence.swift

+38-33
Original file line numberDiff line numberDiff line change
@@ -9,53 +9,58 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
@rethrows
13-
public protocol BufferStorage: Sendable {
14-
associatedtype Base: AsyncSequence
15-
func send(element: Base.Element) async throws
16-
func finish(error: Error?)
17-
func next() async throws -> Base.Element?
18-
}
19-
2012
extension AsyncSequence where Self: Sendable {
21-
/// Creates an asynchronous sequence that buffers elements. When the number of buffered elements reaches the limit
22-
/// the upstream sequence is suspended until new slots are available.
13+
/// Creates an asynchronous sequence that buffers elements.
2314
///
24-
/// - Parameter limit: A limit that drives the suspending behaviour of the `AsyncBufferSequence`
25-
/// - Returns: An asynchronous sequence that buffers elements up to a given limit.
26-
public func buffer(
27-
suspendingLimit limit: Int
28-
) -> AsyncBufferSequence<Self, SuspendingBufferStorage<Self>> {
29-
AsyncBufferSequence(base: self, storage: SuspendingBufferStorage(limit: limit))
30-
}
31-
32-
/// Creates an asynchronous sequence that buffers elements using a specific policy to limit the number of
33-
/// elements that are buffered.
15+
/// The buffering behaviour is dictated by the policy:
16+
/// - bounded: will buffer elements until the limit is reached. Then it will suspend the upstream async sequence.
17+
/// - unbounded: will buffer elements without limit.
18+
/// - bufferingNewest: will buffer elements until the limit is reached. Then it will discard the oldest elements.
19+
/// - bufferingOldest: will buffer elements until the limit is reached. Then it will discard the newest elements.
3420
///
35-
/// - Parameter policy: A limiting policy behavior on the buffering behavior of the `AsyncBufferSequence`
36-
/// - Returns: An asynchronous sequence that buffers elements by applying a policy.
21+
/// - Parameter policy: A policy that drives the behaviour of the `AsyncBufferSequence`
22+
/// - Returns: An asynchronous sequence that buffers elements up to a given limit.
3723
public func buffer(
38-
queuePolicy policy: QueuedBufferStorage<Self>.Policy
39-
) -> AsyncBufferSequence<Self, QueuedBufferStorage<Self>> {
40-
AsyncBufferSequence(base: self, storage: QueuedBufferStorage(policy: policy))
24+
policy: AsyncBufferSequence<Self>.Policy
25+
) -> AsyncBufferSequence<Self> {
26+
AsyncBufferSequence(base: self, policy: policy)
4127
}
4228
}
4329

44-
/// An `AsyncSequence` that buffers elements utilizing a `BufferStorage`.
45-
public struct AsyncBufferSequence<Base: AsyncSequence & Sendable, Storage: BufferStorage>: AsyncSequence, Sendable
46-
where Storage.Base == Base {
30+
/// An `AsyncSequence` that buffers elements in regard to a policy.
31+
public struct AsyncBufferSequence<Base: AsyncSequence & Sendable>: AsyncSequence, Sendable {
4732
public typealias Element = Base.Element
4833
public typealias AsyncIterator = Iterator
4934

35+
public enum Policy: Sendable {
36+
/// A policy for buffering elements until the limit is reached. Then it will suspend the upstream async sequence.
37+
case bounded(Int)
38+
/// A policy for buffering elements without limit.
39+
case unbounded
40+
/// A policy for buffering elements until the limit is reached. Then it will discard the oldest buffered elements.
41+
case bufferingNewest(Int)
42+
/// A policy for buffering elements until the limit is reached. Then it will discard the newest elements.
43+
case bufferingOldest(Int)
44+
45+
var isPositiveLimit: Bool {
46+
switch self {
47+
case .unbounded:
48+
return true
49+
case .bounded(let limit), .bufferingNewest(let limit), .bufferingOldest(let limit):
50+
return limit > 0
51+
}
52+
}
53+
}
54+
5055
let base: Base
51-
let storage: Storage
56+
let storage: BufferStorage<Base>
5257

5358
public init(
5459
base: Base,
55-
storage: Storage
60+
policy: Policy
5661
) {
5762
self.base = base
58-
self.storage = storage
63+
self.storage = BufferStorage<Base>(policy: policy)
5964
}
6065

6166
public func makeAsyncIterator() -> Iterator {
@@ -69,15 +74,15 @@ where Storage.Base == Base {
6974
var task: Task<Void, Never>? = nil
7075

7176
let base: Base
72-
let storage: Storage
77+
let storage: BufferStorage<Base>
7378

7479
public mutating func next() async rethrows -> Element? {
7580
if self.task == nil {
7681
self.task = Task { [base, storage] in
7782
var iterator = base.makeAsyncIterator()
7883
do {
7984
while let element = try await iterator.next() {
80-
try await storage.send(element: element)
85+
await storage.send(element: element)
8186
}
8287
storage.finish(error: nil)
8388
} catch {

Sources/AsyncAlgorithms/Buffer/SuspendingBufferStateMachine.swift renamed to Sources/AsyncAlgorithms/Buffer/BufferStateMachine.swift

+60-28
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
@_implementationOnly import DequeModule
1313

14-
struct SuspendingBufferStateMachine<Element> {
14+
struct BufferStateMachine<Base: AsyncSequence> {
15+
typealias Element = Base.Element
1516
typealias SuspendedProducer = (continuation: UnsafeContinuation<Void, Never>, element: Result<Element?, Error>)
1617
typealias SuspendedConsumer = UnsafeContinuation<Element?, Error>
1718

@@ -22,12 +23,12 @@ struct SuspendingBufferStateMachine<Element> {
2223
}
2324

2425
private var state: State
25-
private let limit: Int
26+
private let policy: AsyncBufferSequence<Base>.Policy
2627

27-
init(limit: Int) {
28-
assert(limit > 0, "The limit should be positive for the buffer operator to be efficient.")
28+
init(policy: AsyncBufferSequence<Base>.Policy) {
29+
assert(policy.isPositiveLimit, "The limit should be positive for the buffer operator to be efficient.")
2930
self.state = .buffering(buffer: [], suspendedProducer: nil, suspendedConsumer: nil)
30-
self.limit = limit
31+
self.policy = policy
3132
}
3233

3334
enum ElementIsProducedAction {
@@ -43,15 +44,37 @@ struct SuspendingBufferStateMachine<Element> {
4344
case .buffering(var buffer, .none, .none):
4445
// we are either idle or the buffer is already in use (no awaiting consumer)
4546
// we have to stack the new element or suspend the producer if the buffer is full
46-
if buffer.count < limit {
47-
self.state = .modifying
48-
// we can stack
49-
buffer.append(.success(element))
50-
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
51-
return .exit
52-
} else {
53-
// we have to suspend the producer
54-
return .suspend
47+
switch self.policy {
48+
case .bounded(let limit):
49+
if buffer.count < limit {
50+
self.state = .modifying
51+
// we can stack
52+
buffer.append(.success(element))
53+
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
54+
return .exit
55+
}
56+
// we have to suspend the producer
57+
return .suspend
58+
case .unbounded:
59+
self.state = .modifying
60+
buffer.append(.success(element))
61+
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
62+
return .exit
63+
case .bufferingNewest(let limit):
64+
self.state = .modifying
65+
if buffer.count >= limit {
66+
_ = buffer.popFirst()
67+
}
68+
buffer.append(.success(element))
69+
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
70+
return .exit
71+
case .bufferingOldest(let limit):
72+
self.state = .modifying
73+
if buffer.count < limit {
74+
buffer.append(.success(element))
75+
}
76+
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
77+
return .exit
5578
}
5679

5780
case .buffering(let buffer, .none, .some(let suspendedConsumer)):
@@ -85,20 +108,29 @@ struct SuspendingBufferStateMachine<Element> {
85108
case .buffering(var buffer, .none, .none):
86109
// we are either idle or the buffer is already in use (no awaiting consumer)
87110
// we have to stack the new element or confirm the suspension if the buffer is full
88-
if buffer.count < limit {
89-
self.state = .modifying
90-
// we can stack and resume the producer
91-
buffer.append(.success(element))
92-
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
93-
return .resumeProducer
94-
} else {
95-
// we confirm the suspension
96-
self.state = .buffering(
97-
buffer: buffer,
98-
suspendedProducer: SuspendedProducer(continuation: continuation, element: .success(element)),
99-
suspendedConsumer: nil
100-
)
101-
return .none
111+
switch self.policy {
112+
case .bounded(let limit):
113+
if buffer.count < limit {
114+
self.state = .modifying
115+
// we can stack and resume the producer
116+
buffer.append(.success(element))
117+
self.state = .buffering(buffer: buffer, suspendedProducer: nil, suspendedConsumer: nil)
118+
return .resumeProducer
119+
} else {
120+
// we confirm the suspension
121+
self.state = .buffering(
122+
buffer: buffer,
123+
suspendedProducer: SuspendedProducer(continuation: continuation, element: .success(element)),
124+
suspendedConsumer: nil
125+
)
126+
return .none
127+
}
128+
case .unbounded:
129+
preconditionFailure("Invalid state. A suspension cannot happen with this policy.")
130+
case .bufferingNewest:
131+
preconditionFailure("Invalid state. A suspension cannot happen with this policy.")
132+
case .bufferingOldest:
133+
preconditionFailure("Invalid state. A suspension cannot happen with this policy.")
102134
}
103135

104136
case .buffering(let buffer, .none, .some(let suspendedConsumer)):

Sources/AsyncAlgorithms/Buffer/SuspendingBufferStorage.swift renamed to Sources/AsyncAlgorithms/Buffer/BufferStorage.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
public struct SuspendingBufferStorage<Base: AsyncSequence>: BufferStorage {
13-
private let stateMachine: ManagedCriticalState<SuspendingBufferStateMachine<Base.Element>>
12+
public struct BufferStorage<Base: AsyncSequence>: Sendable {
13+
private let stateMachine: ManagedCriticalState<BufferStateMachine<Base>>
1414

15-
init(limit: Int) {
16-
self.stateMachine = ManagedCriticalState(SuspendingBufferStateMachine(limit: limit))
15+
init(policy: AsyncBufferSequence<Base>.Policy) {
16+
self.stateMachine = ManagedCriticalState(BufferStateMachine<Base>(policy: policy))
1717
}
1818

1919
public func send(element: Base.Element) async {
@@ -75,7 +75,7 @@ public struct SuspendingBufferStorage<Base: AsyncSequence>: BufferStorage {
7575
}
7676
}
7777

78-
public func finish(error: (some Error)?) {
78+
public func finish(error: Error?) {
7979
self.stateMachine.withCriticalRegion { stateMachine in
8080
let action = stateMachine.finish(error: error)
8181

0 commit comments

Comments
 (0)