1
- #ifndef __NBL_I_ASYNC_QUEUE_DISPATCHER_H_INCLUDED__
2
- #define __NBL_I_ASYNC_QUEUE_DISPATCHER_H_INCLUDED__
1
+ #ifndef _NBL_I_ASYNC_QUEUE_DISPATCHER_H_INCLUDED_
2
+ #define _NBL_I_ASYNC_QUEUE_DISPATCHER_H_INCLUDED_
3
3
4
- #include < atomic>
5
4
#include " nbl/core/declarations.h"
5
+
6
6
#include " nbl/system/IThreadHandler.h"
7
+ #include " nbl/system/atomic_state.h"
7
8
8
9
namespace nbl ::system
9
10
{
@@ -12,74 +13,7 @@ namespace impl
12
13
{
13
14
class IAsyncQueueDispatcherBase
14
15
{
15
- protected:
16
- IAsyncQueueDispatcherBase () = default ;
17
- ~IAsyncQueueDispatcherBase () = default ;
18
-
19
- template <class STATE , STATE kInitial =static_cast <STATE>(0u )>
20
- class atomic_state_t
21
- {
22
- static_assert (std::is_enum_v<STATE>);
23
-
24
- public:
25
- ~atomic_state_t ()
26
- {
27
- static_assert (std::atomic_uint32_t ::is_always_lock_free);
28
- // must have been consumed before exit !
29
- const auto atExit = state.load ();
30
- assert (static_cast <STATE>(atExit)==kInitial );
31
- }
32
-
33
- inline STATE query () const {return static_cast <STATE>(state.load ());}
34
- // TODO: improve
35
- inline void wait (const STATE targetState) const
36
- {
37
- uint32_t current;
38
- while ((current=state.load ()) != targetState)
39
- state.wait (current);
40
- }
41
-
42
- [[nodiscard]] inline bool tryTransition (STATE& expected, const STATE to)
43
- {
44
- return state.compare_exchange_strong (reinterpret_cast <uint32_t &>(from),static_cast <uint32_t >(to));
45
- }
46
-
47
- inline void waitTransition (const STATE from, const STATE to)
48
- {
49
- STATE expected = from;
50
- while (!tryTransition (expected,to))
51
- {
52
- state.wait (static_cast <uint32_t >(expected));
53
- expected = from;
54
- }
55
- assert (expected==from);
56
- }
57
-
58
- [[nodiscard]] inline bool waitAbortableTransition (const STATE from, const STATE to, const STATE abortState)
59
- {
60
- uint32_t expected = static_cast <uint32_t >(from);
61
- while (!state.compare_exchange_strong (expected,static_cast <uint32_t >(to)))
62
- {
63
- state.wait (expected);
64
- if (expected==static_cast <uint32_t >(abortState))
65
- return false ;
66
- expected = from;
67
- }
68
- assert (expected==from);
69
- return true ;
70
- }
71
- // TODO: improve (assert and notify one vs all)
72
- inline void exchangeNotify (const STATE expected, const STATE to)
73
- {
74
- const auto prev = state.exchange (static_cast <uint32_t >(to));
75
- assert (static_cast <STATE>(prev)==expected);
76
- state.notify_one ();
77
- }
78
-
79
- private:
80
- std::atomic_uint32_t state = static_cast <uint32_t >(kInitial );
81
- };
82
-
16
+ public:
83
17
struct future_base_t ;
84
18
// dont want to play around with relaxed memory ordering yet
85
19
struct request_base_t
@@ -100,7 +34,7 @@ class IAsyncQueueDispatcherBase
100
34
// ! REQUESTING THREAD: lock when overwriting the request's data
101
35
inline void start ()
102
36
{
103
- state.waitTransition (STATE::INITIAL ,STATE::RECORDING );
37
+ state.waitTransition (STATE::RECORDING ,STATE::INITIAL );
104
38
// previous thing cleaned up after itself
105
39
assert (!future);
106
40
}
@@ -115,8 +49,8 @@ class IAsyncQueueDispatcherBase
115
49
// ! ANY THREAD [except worker]: via cancellable_future_t::cancel
116
50
inline void cancel ()
117
51
{
118
- const auto prev = state.exchangeNotify (STATE::CANCELLED);
119
- // If we were in EXECUTING then worker thread is definitely stuck `base_t::disassociate_request` spinlock
52
+ const auto prev = state.exchangeNotify < false > (STATE::CANCELLED);
53
+ // If we were in EXECUTING then worker thread is definitely stuck in `base_t::disassociate_request` spinlock
120
54
assert (prev==STATE::PENDING || prev==STATE::EXECUTING);
121
55
// sanity check, but its not our job to set it to nullptr
122
56
assert (future);
@@ -164,25 +98,25 @@ class IAsyncQueueDispatcherBase
164
98
// sanity check
165
99
assert (req->getState ().query ()==request_base_t ::STATE::RECORDING);
166
100
// if not initial state then wait until it gets moved, etc.
167
- state.waitTransition (STATE::INITIAL ,STATE::ASSOCIATED );
101
+ state.waitTransition (STATE::ASSOCIATED ,STATE::INITIAL );
168
102
}
169
103
// ! WORKER THREAD: done as part of execution at the very start, after we want to begin work
170
104
[[nodiscard]] virtual inline bool disassociate_request ()
171
105
{
172
- return state.waitAbortableTransition (STATE::ASSOCIATED ,STATE::EXECUTING ,STATE::INITIAL);
106
+ return state.waitAbortableTransition (STATE::EXECUTING ,STATE::ASSOCIATED ,STATE::INITIAL);
173
107
}
174
108
// ! WORKER THREAD: done as part of execution at the very end, after object is constructed
175
109
inline void notify ()
176
110
{
177
- state.exchangeNotify (STATE::EXECUTING ,STATE::READY );
111
+ state.exchangeNotify < true > (STATE::READY ,STATE::EXECUTING );
178
112
}
179
113
180
114
protected:
181
115
// the base class is not directly usable
182
116
virtual inline ~future_base_t ()
183
117
{
184
118
// non-cancellable future just need to get to this state, and cancellable will move here
185
- state.wait (STATE::INITIAL);
119
+ state.wait ([]( const STATE _query)-> bool { return _query!=STATE ::INITIAL;} );
186
120
}
187
121
// future_t is non-copyable and non-movable because request needs a pointer to it
188
122
future_base_t (const future_base_t &) = delete ;
@@ -195,10 +129,21 @@ class IAsyncQueueDispatcherBase
195
129
atomic_state_t <STATE,STATE::INITIAL> state= {};
196
130
};
197
131
132
+ protected:
133
+ IAsyncQueueDispatcherBase () = default ;
134
+ ~IAsyncQueueDispatcherBase () = default ;
135
+
198
136
public:
199
137
template <typename T>
200
- struct future_t : private core ::StorageTrivializer<T>, protected future_base_t
138
+ class future_t : private core ::StorageTrivializer<T>, protected future_base_t
201
139
{
140
+ using storage_t = core::StorageTrivializer<T>;
141
+ inline void discard_common ()
142
+ {
143
+ storage_t::destruct ();
144
+ state.exchangeNotify <true >(STATE::INITIAL,STATE::LOCKED);
145
+ }
146
+
202
147
public:
203
148
inline future_t () = default;
204
149
inline ~future_t ()
@@ -222,25 +167,27 @@ class IAsyncQueueDispatcherBase
222
167
}
223
168
224
169
// ! Returns after waiting till `ready()` would be true or after
225
- inline bool wait ()
170
+ inline bool wait () const
226
171
{
227
- while ( true )
228
- {
229
- switch (state. query () )
172
+ bool retval = false ;
173
+ state. wait ([&retval]( const STATE _query)-> bool {
174
+ switch (_query )
230
175
{
231
- case STATE::INITIAL:
232
- return false ;
233
- break ;
234
- case STATE::READY:
235
- [[fallthrough]];
236
- case STATE::LOCKED:
237
- return true ;
238
- break ;
239
- default :
240
- break ;
176
+ case STATE::INITIAL:
177
+ return false ;
178
+ break ;
179
+ case STATE::READY:
180
+ [[fallthrough]];
181
+ case STATE::LOCKED:
182
+ retval = true ;
183
+ return false ;
184
+ break ;
185
+ default :
186
+ break ;
241
187
}
242
- }
243
- assert (false );
188
+ return true ;
189
+ });
190
+ return retval;
244
191
}
245
192
246
193
// ! NOTE: Deliberately named `...acquire` instead of `...lock` to make them incompatible with `unique_lock`
@@ -250,36 +197,36 @@ class IAsyncQueueDispatcherBase
250
197
[[nodiscard]] inline T* try_acquire ()
251
198
{
252
199
auto expected = STATE::READY;
253
- if (state.tryTransition (expected, STATE::LOCKED))
254
- return getStorage ();
200
+ if (state.tryTransition (STATE::LOCKED,expected ))
201
+ return storage_t:: getStorage ();
255
202
return nullptr ;
256
203
}
257
204
// ! ANY THREAD [except WORKER]: Wait till we're either in READY and move us to LOCKED or bail on INITIAL
258
205
// this accounts for being cancelled or consumed while waiting
259
206
[[nodiscard]] inline T* acquire ()
260
207
{
261
- if (state.waitAbortableTransition (STATE::READY ,STATE::LOCKED ,STATE::INITIAL))
262
- return getStorage ();
208
+ if (state.waitAbortableTransition (STATE::LOCKED ,STATE::READY ,STATE::INITIAL))
209
+ return storage_t:: getStorage ();
263
210
return nullptr ;
264
211
}
265
212
// ! ANY THREAD [except WORKER]: Release an acquired lock
266
213
inline void release ()
267
214
{
268
- state.exchangeNotify (STATE::LOCKED ,STATE::READY );
215
+ state.exchangeNotify < true > (STATE::READY ,STATE::LOCKED );
269
216
}
270
217
271
218
// ! NOTE: You're in charge of ensuring future doesn't transition back to INITIAL (e.g. lock or use sanely!)
272
219
inline const T* get () const
273
220
{
274
221
if (ready ())
275
- return getStorage ();
222
+ return storage_t:: getStorage ();
276
223
return nullptr ;
277
224
}
278
225
inline T* get ()
279
226
{
280
227
if (future_base_t ::state.query () != future_base_t ::STATE::LOCKED)
281
228
return nullptr ;
282
- return getStorage ();
229
+ return storage_t:: getStorage ();
283
230
}
284
231
285
232
// ! Can only be called once! If returns false means has been cancelled and nothing happened
@@ -288,7 +235,7 @@ class IAsyncQueueDispatcherBase
288
235
T* pSrc = acquire ();
289
236
if (!pSrc)
290
237
return false ;
291
- dst = std::move (*T );
238
+ dst = std::move (*pSrc );
292
239
discard_common ();
293
240
return true ;
294
241
}
@@ -305,16 +252,9 @@ class IAsyncQueueDispatcherBase
305
252
template <typename ... Args>
306
253
inline void notify (Args&&... args)
307
254
{
308
- new ( getStorage ()) T (std::forward<Args>(args)...);
255
+ storage_t::construct (std::forward<Args>(args)...);
309
256
future_base_t::notify ();
310
257
}
311
-
312
- private:
313
- inline discard_common ()
314
- {
315
- destruct ();
316
- state.exchangeNotify (STATE::LOCKED, STATE::INITIAL);
317
- }
318
258
};
319
259
template <typename T>
320
260
struct cancellable_future_t final : public future_t <T>
@@ -326,8 +266,8 @@ class IAsyncQueueDispatcherBase
326
266
// ! ANY THREAD [except WORKER]: Cancel pending request if we can, returns whether we actually managed to cancel
327
267
inline bool cancel ()
328
268
{
329
- STATE expected = STATE::ASSOCIATED;
330
- if (state.tryTransition (expected, STATE::EXECUTING))
269
+ auto expected = base_t :: STATE::ASSOCIATED;
270
+ if (state.tryTransition (STATE::EXECUTING,expected ))
331
271
{
332
272
// Since we're here we've managed to move from ASSOCIATED to fake "EXECUTING" this means that the Request is either:
333
273
// 1. RECORDING but after returning from `base_t::associate_request`
@@ -338,7 +278,7 @@ class IAsyncQueueDispatcherBase
338
278
request.exchange (nullptr )->cancel ();
339
279
340
280
// after doing everything, we can mark ourselves as cleaned up
341
- state.exchangeNotify (STATE::EXECUTING ,STATE::INITIAL );
281
+ state.exchangeNotify < false > (STATE::INITIAL ,STATE::EXECUTING );
342
282
return true ;
343
283
}
344
284
// we're here because either:
@@ -372,12 +312,11 @@ class IAsyncQueueDispatcherBase
372
312
}
373
313
inline bool disassociate_request () override
374
314
{
375
- assert (request.load ()->getState ().query ()==request_base_t ::STATE::EXECUTING);
376
315
if (base_t::disassociate_request ())
377
316
{
378
317
// only assign if we didn't get cancelled mid-way, otherwise will mess up `associate_request` sanity checks
379
318
request_base_t * prev = request.exchange (nullptr );
380
- assert (prev);
319
+ assert (prev && prev-> getState (). query ()== request_base_t ::STATE::EXECUTING );
381
320
return true ;
382
321
}
383
322
return false ;
@@ -389,16 +328,16 @@ inline void IAsyncQueueDispatcherBase::request_base_t::finalize(future_base_t* f
389
328
{
390
329
future = fut;
391
330
future->associate_request (this );
392
- state.exchangeNotify (STATE::RECORDING ,STATE::PENDING );
331
+ state.exchangeNotify < false > (STATE::PENDING ,STATE::RECORDING );
393
332
}
394
333
395
334
inline bool IAsyncQueueDispatcherBase::request_base_t::wait ()
396
335
{
397
- if (state.waitAbortableTransition (STATE::PENDING ,STATE::EXECUTING ,STATE::CANCELLED) && future->disassociate_request ())
336
+ if (state.waitAbortableTransition (STATE::EXECUTING ,STATE::PENDING ,STATE::CANCELLED) && future->disassociate_request ())
398
337
return true ;
399
338
// assert(future->cancellable);
400
339
future = nullptr ;
401
- state.exchangeNotify (STATE::CANCELLED ,STATE::INITIAL );
340
+ state.exchangeNotify < false > (STATE::INITIAL ,STATE::CANCELLED );
402
341
return false ;
403
342
}
404
343
inline void IAsyncQueueDispatcherBase::request_base_t::notify ()
@@ -407,7 +346,7 @@ inline void IAsyncQueueDispatcherBase::request_base_t::notify()
407
346
// cleanup
408
347
future = nullptr ;
409
348
// allow to be recycled
410
- state.exchangeNotify (STATE::EXECUTING ,STATE::INITIAL );
349
+ state.exchangeNotify < false > (STATE::INITIAL ,STATE::EXECUTING );
411
350
}
412
351
413
352
}
@@ -438,7 +377,7 @@ inline void IAsyncQueueDispatcherBase::request_base_t::notify()
438
377
* notify_all_ready() takes an r-value reference to an already locked mutex and notifies any waiters then releases the lock
439
378
*/
440
379
template <typename CRTP, typename RequestType, uint32_t BufferSize = 256u , typename InternalStateType = void >
441
- class IAsyncQueueDispatcher : public IThreadHandler <CRTP, InternalStateType>, protected impl::IAsyncQueueDispatcherBase
380
+ class IAsyncQueueDispatcher : public IThreadHandler <CRTP,InternalStateType>, protected impl::IAsyncQueueDispatcherBase
442
381
{
443
382
static_assert (std::is_base_of_v<impl::IAsyncQueueDispatcherBase::request_base_t ,RequestType>, " Request type must derive from request_base_t!" );
444
383
static_assert (BufferSize>0u , " BufferSize must not be 0!" );
@@ -467,7 +406,7 @@ class IAsyncQueueDispatcher : public IThreadHandler<CRTP, InternalStateType>, pr
467
406
468
407
public:
469
408
inline IAsyncQueueDispatcher () {}
470
- inline ~ IAsyncQueueDispatcher () {}
409
+ inline IAsyncQueueDispatcher (base_t :: start_on_construction_t ) : base_t( base_t :: start_on_construction_t ) {}
471
410
472
411
using mutex_t = typename base_t ::mutex_t ;
473
412
using lock_t = typename base_t ::lock_t ;
@@ -505,6 +444,7 @@ class IAsyncQueueDispatcher : public IThreadHandler<CRTP, InternalStateType>, pr
505
444
}
506
445
507
446
protected:
447
+ inline ~IAsyncQueueDispatcher () {}
508
448
void background_work () {}
509
449
510
450
private:
@@ -538,9 +478,8 @@ class IAsyncQueueDispatcher : public IThreadHandler<CRTP, InternalStateType>, pr
538
478
lock.lock ();
539
479
}
540
480
541
-
542
- bool wakeupPredicate () const { return (cb_begin != cb_end); }
543
- bool continuePredicate () const { return (cb_begin != cb_end); }
481
+ inline bool wakeupPredicate () const { return (cb_begin != cb_end); }
482
+ inline bool continuePredicate () const { return (cb_begin != cb_end); }
544
483
};
545
484
546
485
}
0 commit comments