| 15 | /// This is a simple class making a lockfree::spsc_queue awaitable. It's not movable, since the spsc_queue isn't. |
| 16 | template<typename T, typename ... Options> |
| 17 | struct awaitable_spsc_queue |
| 18 | { |
| 19 | boost::lockfree::spsc_queue<T, Options...> queue; |
| 20 | |
| 21 | template<typename ... Args> |
| 22 | awaitable_spsc_queue(Args && ... args) : queue(std::forward<Args>(args)...) {} |
| 23 | |
| 24 | // if the queue gets destroyed, destroying the awaiters is all we can do. |
| 25 | ~awaitable_spsc_queue() |
| 26 | { |
| 27 | if (auto r = reader.load(); r != nullptr) |
| 28 | std::coroutine_handle<void>::from_address(r).destroy(); |
| 29 | |
| 30 | if (auto w = writer.load(); w != nullptr) |
| 31 | std::coroutine_handle<void>::from_address(w).destroy(); |
| 32 | } |
| 33 | // to avoid locks, put the coroutine handles into atomics. |
| 34 | std::atomic<void*> reader{nullptr}, writer{nullptr}; |
| 35 | |
| 36 | // capture the read & write executor |
| 37 | cobalt::executor read_executor, write_executor; |
| 38 | |
| 39 | // the awaitable to read a value from the queue |
| 40 | struct read_op |
| 41 | { |
| 42 | awaitable_spsc_queue * this_; |
| 43 | |
| 44 | // if reads are available we don't need to suspend |
| 45 | bool await_ready() {return this_->queue.read_available();} |
| 46 | |
| 47 | // The suspend implementation. We expect the coroutine promise to have an associated executor. |
| 48 | // If it doesn't resumption will end up on the system_executor |
| 49 | template<typename Promise> |
| 50 | void await_suspend(std::coroutine_handle<Promise> h) |
| 51 | { |
| 52 | // Capture the read_executor. |
| 53 | if constexpr (requires {h.promise().get_executor();}) |
| 54 | this_->read_executor = h.promise().get_executor(); |
| 55 | else |
| 56 | this_->read_executor = cobalt::this_thread::get_executor(); |
| 57 | |
| 58 | // Make sure there's only one coroutine awaiting the read |
| 59 | assert(this_->reader == nullptr); |
| 60 | // Store the handle of the awaiter. |
| 61 | this_->reader.store(h.address()); |
| 62 | } |
| 63 | T await_resume() |
| 64 | { |
| 65 | T res; |
| 66 | // Grab the value from the queue |
| 67 | this_->queue.pop(res); |
| 68 | // if a writer is waiting post it to complete on it's thread |
| 69 | auto w = cobalt::unique_handle<void>::from_address(this_->writer.exchange(nullptr)); |
| 70 | if (w) |
| 71 | boost::asio::post(this_->write_executor, std::move(w)); |
| 72 | |
| 73 | return res; |
| 74 | } |
nothing calls this directly
no outgoing calls
no test coverage detected