@@ -24,21 +24,32 @@ final class KafkaPollingSystem<Element>: Sendable {
24
24
typealias Producer = NIOAsyncSequenceProducer < Element , HighLowWatermark , KafkaPollingSystem >
25
25
26
26
/// The state machine that manages the system's state transitions.
27
- let stateMachineLock : NIOLockedValueBox < StateMachine >
27
+ private let stateMachine : NIOLockedValueBox < StateMachine >
28
28
29
- /// Initializes the ``KafkaBackPressurePollingSystem``.
30
- /// Private initializer. The ``KafkaBackPressurePollingSystem`` is not supposed to be initialized directly.
31
- /// It must rather be initialized using the ``KafkaBackPressurePollingSystem.createSystemAndSequence`` function.
29
+ /// Allows the producer to synchronously `yield` new elements to the ``NIOAsyncSequenceProducer``
30
+ /// and to `finish` the sequence.
31
+ var source : Producer . Source ? {
32
+ get {
33
+ stateMachine. withLockedValue { $0. source }
34
+ }
35
+ set {
36
+ stateMachine. withLockedValue { $0. source = newValue }
37
+ }
38
+ }
39
+
40
+ /// Initializes the ``KafkaPollingSystem``.
41
+ /// Private initializer. The ``KafkaPollingSystem`` is not supposed to be initialized directly.
42
+ /// It must rather be initialized using the ``KafkaPollingSystem.createSystemAndSequence`` function.
32
43
init ( pollClosure: @escaping ( ) -> Void ) {
33
- self . stateMachineLock = NIOLockedValueBox ( StateMachine ( pollClosure: pollClosure) )
44
+ self . stateMachine = NIOLockedValueBox ( StateMachine ( pollClosure: pollClosure) )
34
45
}
35
46
36
47
/// Runs the poll loop with the specified poll interval.
37
48
///
38
49
/// - Parameter pollInterval: The desired time interval between two consecutive polls.
39
50
/// - Returns: An awaitable task representing the execution of the poll loop.
40
- func run( pollInterval: Duration ) async {
41
- switch self . stateMachineLock . withLockedValue ( { $0. run ( ) } ) {
51
+ func run( pollInterval: Duration ) async throws {
52
+ switch self . stateMachine . withLockedValue ( { $0. run ( ) } ) {
42
53
case . alreadyClosed:
43
54
return
44
55
case . alreadyRunning:
@@ -48,7 +59,7 @@ final class KafkaPollingSystem<Element>: Sendable {
48
59
}
49
60
50
61
while true {
51
- let action = self . stateMachineLock . withLockedValue { $0 . nextPollLoopAction ( ) }
62
+ let action = self . nextPollLoopAction ( )
52
63
53
64
switch action {
54
65
case . pollAndSleep( let pollClosure) :
@@ -58,31 +69,32 @@ final class KafkaPollingSystem<Element>: Sendable {
58
69
do {
59
70
try await Task . sleep ( for: pollInterval)
60
71
} catch {
61
- let action = self . stateMachineLock . withLockedValue { $0. terminate ( ) }
72
+ let action = self . stateMachine . withLockedValue { $0. terminate ( ) }
62
73
self . handleTerminateAction ( action)
74
+ throw error
63
75
}
64
76
case . suspendPollLoop:
65
77
// The downstream consumer asked us to stop sending new messages.
66
78
// We therefore await until we are unsuspended again.
67
- await withTaskCancellationHandler {
68
- await withCheckedContinuation { continuation in
69
- self . stateMachineLock . withLockedValue { $0. suspendLoop ( continuation: continuation) }
79
+ try await withTaskCancellationHandler {
80
+ try await withCheckedThrowingContinuation { continuation in
81
+ self . stateMachine . withLockedValue { $0. suspendLoop ( continuation: continuation) }
70
82
}
71
83
} onCancel: {
72
- let action = self . stateMachineLock . withLockedValue { $0. terminate ( ) }
84
+ let action = self . stateMachine . withLockedValue { $0. terminate ( CancellationError ( ) ) }
73
85
self . handleTerminateAction ( action)
74
86
}
75
87
case . shutdownPollLoop:
76
88
// We have been asked to close down the poll loop.
77
- let action = self . stateMachineLock . withLockedValue { $0. terminate ( ) }
89
+ let action = self . stateMachine . withLockedValue { $0. terminate ( ) }
78
90
self . handleTerminateAction ( action)
79
91
}
80
92
}
81
93
}
82
94
83
95
/// Yield new elements to the underlying `NIOAsyncSequenceProducer`.
84
96
func yield( _ element: Element ) {
85
- self . stateMachineLock . withLockedValue { stateMachine in
97
+ self . stateMachine . withLockedValue { stateMachine in
86
98
switch stateMachine. state {
87
99
case . idle( let source, _, _) , . producing( let source, _, _) , . stopProducing( let source, _, _, _) :
88
100
// We can also yield when in .stopProducing,
@@ -111,6 +123,19 @@ final class KafkaPollingSystem<Element>: Sendable {
111
123
}
112
124
}
113
125
126
+ /// Determines the next action to be taken in the poll loop based on the current state.
127
+ ///
128
+ /// - Returns: The next action for the poll loop.
129
+ func nextPollLoopAction( ) -> KafkaPollingSystem . StateMachine . PollLoopAction {
130
+ return self . stateMachine. withLockedValue { $0. nextPollLoopAction ( ) }
131
+ }
132
+
133
+ /// Stop producing new elements to the
134
+ /// `source` ``NIOAsyncSequenceProducer``.
135
+ func stopProducing( ) {
136
+ stateMachine. withLockedValue { $0. stopProducing ( ) }
137
+ }
138
+
114
139
/// Invokes the desired action after ``KafkaPollingSystem/StateMachine/terminate()``
115
140
/// has been invoked.
116
141
func handleTerminateAction( _ action: StateMachine . TerminateAction ? ) {
@@ -120,6 +145,9 @@ final class KafkaPollingSystem<Element>: Sendable {
120
145
case . finishSequenceSourceAndResume( let source, let continuation) :
121
146
source? . finish ( )
122
147
continuation? . resume ( )
148
+ case . finishSequenceSourceAndResumeWithError( let source, let continuation, let error) :
149
+ source? . finish ( )
150
+ continuation? . resume ( throwing: error)
123
151
case . none:
124
152
break
125
153
}
@@ -128,7 +156,7 @@ final class KafkaPollingSystem<Element>: Sendable {
128
156
129
157
extension KafkaPollingSystem : NIOAsyncSequenceProducerDelegate {
130
158
func produceMore( ) {
131
- let action = self . stateMachineLock . withLockedValue { $0. produceMore ( ) }
159
+ let action = self . stateMachine . withLockedValue { $0. produceMore ( ) }
132
160
switch action {
133
161
case . resume( let continuation) :
134
162
continuation? . resume ( )
@@ -138,13 +166,13 @@ extension KafkaPollingSystem: NIOAsyncSequenceProducerDelegate {
138
166
}
139
167
140
168
func didTerminate( ) {
141
- let action = self . stateMachineLock . withLockedValue { $0. terminate ( ) }
169
+ let action = self . stateMachine . withLockedValue { $0. terminate ( ) }
142
170
self . handleTerminateAction ( action)
143
171
}
144
172
}
145
173
146
174
extension KafkaPollingSystem {
147
- /// The state machine used by the ``KafkaBackPressurePollingSystem ``.
175
+ /// The state machine used by the ``KafkaPollingSystem ``.
148
176
struct StateMachine {
149
177
/// The possible states of the state machine.
150
178
enum State {
@@ -164,7 +192,7 @@ extension KafkaPollingSystem {
164
192
/// of `produceMore()` to continue producing messages.
165
193
case stopProducing(
166
194
source: Producer . Source ? ,
167
- continuation: CheckedContinuation < Void , Never > ? ,
195
+ continuation: CheckedContinuation < Void , Error > ? ,
168
196
pollClosure: ( ) -> Void ,
169
197
running: Bool
170
198
)
@@ -274,7 +302,7 @@ extension KafkaPollingSystem {
274
302
/// Actions to take after ``produceMore()`` has been invoked on the ``KafkaPollingSystem/StateMachine``.
275
303
enum ProduceMoreAction {
276
304
/// Resume the given `continuation`.
277
- case resume( CheckedContinuation < Void , Never > ? )
305
+ case resume( CheckedContinuation < Void , Error > ? )
278
306
}
279
307
280
308
/// Our downstream consumer allowed us to produce more elements.
@@ -305,7 +333,7 @@ extension KafkaPollingSystem {
305
333
///
306
334
/// - Parameter continuation: The continuation that will be resumed once we are allowed to produce again.
307
335
/// After resuming the continuation, our poll loop will start running again.
308
- mutating func suspendLoop( continuation: CheckedContinuation < Void , Never > ) {
336
+ mutating func suspendLoop( continuation: CheckedContinuation < Void , Error > ) {
309
337
switch self . state {
310
338
case . finished:
311
339
return
@@ -324,12 +352,19 @@ extension KafkaPollingSystem {
324
352
/// and resume the given `continuation`.
325
353
case finishSequenceSourceAndResume(
326
354
source: Producer . Source ? ,
327
- continuation: CheckedContinuation < Void , Never > ?
355
+ continuation: CheckedContinuation < Void , Error > ?
356
+ )
357
+ /// Invoke `finish()` on the given `NIOAsyncSequenceProducer.Source`
358
+ /// and resume the given `continuation` with an error.
359
+ case finishSequenceSourceAndResumeWithError(
360
+ source: Producer . Source ? ,
361
+ continuation: CheckedContinuation < Void , Error > ? ,
362
+ error: Error
328
363
)
329
364
}
330
365
331
366
/// Terminate the state machine and finish producing elements.
332
- mutating func terminate( ) -> TerminateAction ? {
367
+ mutating func terminate( _ error : Error ? = nil ) -> TerminateAction ? {
333
368
switch self . state {
334
369
case . finished:
335
370
return nil
@@ -338,7 +373,15 @@ extension KafkaPollingSystem {
338
373
return . finishSequenceSource( source: source)
339
374
case . stopProducing( let source, let continuation, _, _) :
340
375
self . state = . finished
341
- return . finishSequenceSourceAndResume( source: source, continuation: continuation)
376
+ if let error = error {
377
+ return . finishSequenceSourceAndResumeWithError(
378
+ source: source,
379
+ continuation: continuation,
380
+ error: error
381
+ )
382
+ } else {
383
+ return . finishSequenceSourceAndResume( source: source, continuation: continuation)
384
+ }
342
385
}
343
386
}
344
387
}
0 commit comments