8
8
#include < cppcoro/fmap.hpp>
9
9
10
10
#include < exception>
11
- #include < atomic>
12
11
#include < iterator>
13
12
#include < type_traits>
14
13
#include < experimental/coroutine>
15
14
#include < functional>
16
- #include < cassert>
17
15
18
16
namespace cppcoro
19
17
{
@@ -32,8 +30,7 @@ namespace cppcoro
32
30
public:
33
31
34
32
async_generator_promise_base () noexcept
35
- : m_state(state::value_ready_producer_suspended)
36
- , m_exception(nullptr )
33
+ : m_exception(nullptr )
37
34
{
38
35
// Other variables left intentionally uninitialised as they're
39
36
// only referenced in certain states by which time they should
@@ -52,12 +49,7 @@ namespace cppcoro
52
49
53
50
void unhandled_exception () noexcept
54
51
{
55
- // Don't bother capturing the exception if we have been cancelled
56
- // as there is no consumer that will see it.
57
- if (m_state.load (std::memory_order_relaxed) != state::cancelled)
58
- {
59
- m_exception = std::current_exception ();
60
- }
52
+ m_exception = std::current_exception ();
61
53
}
62
54
63
55
void return_void () noexcept
@@ -81,30 +73,6 @@ namespace cppcoro
81
73
}
82
74
}
83
75
84
- // / Request that the generator cancel generation of new items.
85
- // /
86
- // / \return
87
- // / Returns true if the request was completed synchronously and the associated
88
- // / producer coroutine is now available to be destroyed. In which case the caller
89
- // / is expected to call destroy() on the coroutine_handle.
90
- // / Returns false if the producer coroutine was not at a suitable suspend-point.
91
- // / The coroutine will be destroyed when it next reaches a co_yield or co_return
92
- // / statement.
93
- bool request_cancellation () noexcept
94
- {
95
- const auto previousState = m_state.exchange (state::cancelled, std::memory_order_acq_rel);
96
-
97
- // Not valid to destroy async_generator<T> object if consumer coroutine still suspended
98
- // in a co_await for next item.
99
- assert (previousState != state::value_not_ready_consumer_suspended);
100
-
101
- // A coroutine should only ever be cancelled once, from the destructor of the
102
- // owning async_generator<T> object.
103
- assert (previousState != state::cancelled);
104
-
105
- return previousState == state::value_ready_producer_suspended;
106
- }
107
-
108
76
protected:
109
77
110
78
async_generator_yield_operation internal_yield_value () noexcept ;
@@ -114,38 +82,6 @@ namespace cppcoro
114
82
friend class async_generator_yield_operation ;
115
83
friend class async_generator_advance_operation ;
116
84
117
- // State transition diagram
118
- // VNRCA - value_not_ready_consumer_active
119
- // VNRCS - value_not_ready_consumer_suspended
120
- // VRPA - value_ready_consumer_active
121
- // VRPS - value_ready_consumer_suspended
122
- //
123
- // A +--- VNRCA --[C]--> VNRCS yield_value()
124
- // | | | A | A | .
125
- // | [C] [P] | [P] | | .
126
- // | | | [C] | [C] | .
127
- // | | V | V | | .
128
- // operator++/ | VRPS <--[P]--- VRPA V |
129
- // begin() | | | |
130
- // | [C] [C] |
131
- // | +----+ +---+ |
132
- // | | | |
133
- // | V V V
134
- // +--------> cancelled ~async_generator()
135
- //
136
- // [C] - Consumer performs this transition
137
- // [P] - Producer performs this transition
138
- enum class state
139
- {
140
- value_not_ready_consumer_active,
141
- value_not_ready_consumer_suspended,
142
- value_ready_producer_active,
143
- value_ready_producer_suspended,
144
- cancelled
145
- };
146
-
147
- std::atomic<state> m_state;
148
-
149
85
std::exception_ptr m_exception;
150
86
151
87
std::experimental::coroutine_handle<> m_consumerCoroutine;
@@ -157,27 +93,28 @@ namespace cppcoro
157
93
158
94
class async_generator_yield_operation final
159
95
{
160
- using state = async_generator_promise_base::state;
161
-
162
96
public:
163
97
164
- async_generator_yield_operation (async_generator_promise_base& promise, state initialState) noexcept
165
- : m_promise(promise)
166
- , m_initialState(initialState)
98
+ async_generator_yield_operation (std::experimental::coroutine_handle<> consumer) noexcept
99
+ : m_consumer(consumer)
167
100
{}
168
101
169
102
bool await_ready () const noexcept
170
103
{
171
- return m_initialState == state::value_not_ready_consumer_suspended ;
104
+ return false ;
172
105
}
173
106
174
- bool await_suspend (std::experimental::coroutine_handle<> producer) noexcept ;
107
+ std::experimental::coroutine_handle<>
108
+ await_suspend (std::experimental::coroutine_handle<> producer) noexcept
109
+ {
110
+ return m_consumer;
111
+ }
175
112
176
113
void await_resume () noexcept {}
177
114
178
115
private:
179
- async_generator_promise_base& m_promise;
180
- state m_initialState ;
116
+
117
+ std::experimental::coroutine_handle<> m_consumer ;
181
118
182
119
};
183
120
@@ -189,112 +126,11 @@ namespace cppcoro
189
126
190
127
inline async_generator_yield_operation async_generator_promise_base::internal_yield_value () noexcept
191
128
{
192
- state currentState = m_state.load (std::memory_order_acquire);
193
- assert (currentState != state::value_ready_producer_active);
194
- assert (currentState != state::value_ready_producer_suspended);
195
-
196
- if (currentState == state::value_not_ready_consumer_suspended)
197
- {
198
- // Only need relaxed memory order since we're resuming the
199
- // consumer on the same thread.
200
- m_state.store (state::value_ready_producer_active, std::memory_order_relaxed);
201
-
202
- // Resume the consumer.
203
- // It might ask for another value before returning, in which case it'll
204
- // transition to value_not_ready_consumer_suspended and we can return from
205
- // yield_value without suspending, otherwise we should try to suspend
206
- // the producer in which case the consumer will wake us up again
207
- // when it wants the next value.
208
- m_consumerCoroutine.resume ();
209
-
210
- // Need to use acquire semantics here since it's possible that the
211
- // consumer might have asked for the next value on a different thread
212
- // which executed concurrently with the call to m_consumerCoro on the
213
- // current thread above.
214
- currentState = m_state.load (std::memory_order_acquire);
215
- }
216
-
217
- return async_generator_yield_operation{ *this , currentState };
218
- }
219
-
220
- inline bool async_generator_yield_operation::await_suspend (
221
- std::experimental::coroutine_handle<> producer) noexcept
222
- {
223
- state currentState = m_initialState;
224
- if (currentState == state::value_not_ready_consumer_active)
225
- {
226
- bool producerSuspended = m_promise.m_state .compare_exchange_strong (
227
- currentState,
228
- state::value_ready_producer_suspended,
229
- std::memory_order_release,
230
- std::memory_order_acquire);
231
- if (producerSuspended)
232
- {
233
- return true ;
234
- }
235
-
236
- if (currentState == state::value_not_ready_consumer_suspended)
237
- {
238
- // Can get away with using relaxed memory semantics here since we're
239
- // resuming the consumer on the current thread.
240
- m_promise.m_state .store (state::value_ready_producer_active, std::memory_order_relaxed);
241
-
242
- m_promise.m_consumerCoroutine .resume ();
243
-
244
- // The consumer might have asked for another value before returning, in which case
245
- // it'll transition to value_not_ready_consumer_suspended and we can return without
246
- // suspending, otherwise we should try to suspend the producer, in which case the
247
- // consumer will wake us up again when it wants the next value.
248
- //
249
- // Need to use acquire semantics here since it's possible that the consumer might
250
- // have asked for the next value on a different thread which executed concurrently
251
- // with the call to m_consumerCoro.resume() above.
252
- currentState = m_promise.m_state .load (std::memory_order_acquire);
253
- if (currentState == state::value_not_ready_consumer_suspended)
254
- {
255
- return false ;
256
- }
257
- }
258
- }
259
-
260
- // By this point the consumer has been resumed if required and is now active.
261
-
262
- if (currentState == state::value_ready_producer_active)
263
- {
264
- // Try to suspend the producer.
265
- // If we failed to suspend then it's either because the consumer destructed, transitioning
266
- // the state to cancelled, or requested the next item, transitioning the state to value_not_ready_consumer_suspended.
267
- const bool suspendedProducer = m_promise.m_state .compare_exchange_strong (
268
- currentState,
269
- state::value_ready_producer_suspended,
270
- std::memory_order_release,
271
- std::memory_order_acquire);
272
- if (suspendedProducer)
273
- {
274
- return true ;
275
- }
276
-
277
- if (currentState == state::value_not_ready_consumer_suspended)
278
- {
279
- // Consumer has asked for the next value.
280
- return false ;
281
- }
282
- }
283
-
284
- assert (currentState == state::cancelled);
285
-
286
- // async_generator object has been destroyed and we're now at a
287
- // co_yield/co_return suspension point so we can just destroy
288
- // the coroutine.
289
- producer.destroy ();
290
-
291
- return true ;
129
+ return async_generator_yield_operation{ m_consumerCoroutine };
292
130
}
293
131
294
132
class async_generator_advance_operation
295
133
{
296
- using state = async_generator_promise_base::state;
297
-
298
134
protected:
299
135
300
136
async_generator_advance_operation (std::nullptr_t ) noexcept
@@ -308,88 +144,24 @@ namespace cppcoro
308
144
: m_promise(std::addressof(promise))
309
145
, m_producerCoroutine(producerCoroutine)
310
146
{
311
- state initialState = promise.m_state .load (std::memory_order_acquire);
312
- if (initialState == state::value_ready_producer_suspended)
313
- {
314
- // Can use relaxed memory order here as we will be resuming the producer
315
- // on the same thread.
316
- promise.m_state .store (state::value_not_ready_consumer_active, std::memory_order_relaxed);
317
-
318
- producerCoroutine.resume ();
319
-
320
- // Need to use acquire memory order here since it's possible that the
321
- // coroutine may have transferred execution to another thread and
322
- // completed on that other thread before the call to resume() returns.
323
- initialState = promise.m_state .load (std::memory_order_acquire);
324
- }
325
-
326
- m_initialState = initialState;
327
147
}
328
148
329
149
public:
330
150
331
- bool await_ready () const noexcept
332
- {
333
- return m_initialState == state::value_ready_producer_suspended;
334
- }
151
+ bool await_ready () const noexcept { return false ; }
335
152
336
- bool await_suspend (std::experimental::coroutine_handle<> consumerCoroutine) noexcept
153
+ std::experimental::coroutine_handle<>
154
+ await_suspend (std::experimental::coroutine_handle<> consumerCoroutine) noexcept
337
155
{
338
156
m_promise->m_consumerCoroutine = consumerCoroutine;
339
-
340
- auto currentState = m_initialState;
341
- if (currentState == state::value_ready_producer_active)
342
- {
343
- // A potential race between whether consumer or producer coroutine
344
- // suspends first. Resolve the race using a compare-exchange.
345
- if (m_promise->m_state .compare_exchange_strong (
346
- currentState,
347
- state::value_not_ready_consumer_suspended,
348
- std::memory_order_release,
349
- std::memory_order_acquire))
350
- {
351
- return true ;
352
- }
353
-
354
- assert (currentState == state::value_ready_producer_suspended);
355
-
356
- m_promise->m_state .store (state::value_not_ready_consumer_active, std::memory_order_relaxed);
357
-
358
- m_producerCoroutine.resume ();
359
-
360
- currentState = m_promise->m_state .load (std::memory_order_acquire);
361
- if (currentState == state::value_ready_producer_suspended)
362
- {
363
- // Producer coroutine produced a value synchronously.
364
- return false ;
365
- }
366
- }
367
-
368
- assert (currentState == state::value_not_ready_consumer_active);
369
-
370
- // Try to suspend consumer coroutine, transitioning to value_not_ready_consumer_suspended.
371
- // This could be racing with producer making the next value available and suspending
372
- // (transition to value_ready_producer_suspended) so we use compare_exchange to decide who
373
- // wins the race.
374
- // If compare_exchange succeeds then consumer suspended (and we return true).
375
- // If it fails then producer yielded next value and suspended and we can return
376
- // synchronously without suspended (ie. return false).
377
- return m_promise->m_state .compare_exchange_strong (
378
- currentState,
379
- state::value_not_ready_consumer_suspended,
380
- std::memory_order_release,
381
- std::memory_order_acquire);
157
+ return m_producerCoroutine;
382
158
}
383
159
384
160
protected:
385
161
386
162
async_generator_promise_base* m_promise;
387
163
std::experimental::coroutine_handle<> m_producerCoroutine;
388
164
389
- private:
390
-
391
- state m_initialState;
392
-
393
165
};
394
166
395
167
template <typename T>
@@ -594,10 +366,7 @@ namespace cppcoro
594
366
{
595
367
if (m_coroutine)
596
368
{
597
- if (m_coroutine.promise ().request_cancellation ())
598
- {
599
- m_coroutine.destroy ();
600
- }
369
+ m_coroutine.destroy ();
601
370
}
602
371
}
603
372
0 commit comments