97.70% Lines (85/87)
94.44% Functions (17/18)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | |||||
| 3 | // | 3 | // | |||||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 6 | // | 6 | // | |||||
| 7 | // Official repository: https://github.com/cppalliance/capy | 7 | // Official repository: https://github.com/cppalliance/capy | |||||
| 8 | // | 8 | // | |||||
| 9 | 9 | |||||||
| 10 | #include <boost/capy/test/run_blocking.hpp> | 10 | #include <boost/capy/test/run_blocking.hpp> | |||||
| 11 | 11 | |||||||
| 12 | #include <boost/capy/ex/abandon.hpp> | 12 | #include <boost/capy/ex/abandon.hpp> | |||||
| 13 | #include <boost/capy/ex/frame_allocator.hpp> | 13 | #include <boost/capy/ex/frame_allocator.hpp> | |||||
| 14 | #include <condition_variable> | 14 | #include <condition_variable> | |||||
| 15 | #include <mutex> | 15 | #include <mutex> | |||||
| 16 | #include <queue> | 16 | #include <queue> | |||||
| 17 | #include <system_error> | 17 | #include <system_error> | |||||
| 18 | #include <utility> | 18 | #include <utility> | |||||
| 19 | 19 | |||||||
| 20 | namespace boost { | 20 | namespace boost { | |||||
| 21 | namespace capy { | 21 | namespace capy { | |||||
| 22 | namespace test { | 22 | namespace test { | |||||
| 23 | 23 | |||||||
| 24 | namespace { | 24 | namespace { | |||||
| 25 | 25 | |||||||
| 26 | // The context whose run() is on this thread's stack. Only there is it | 26 | // The context whose run() is on this thread's stack. Only there is it | |||||
| 27 | // safe to resume dispatched work inline. | 27 | // safe to resume dispatched work inline. | |||||
| 28 | thread_local blocking_context const* running_ = nullptr; | 28 | thread_local blocking_context const* running_ = nullptr; | |||||
| 29 | 29 | |||||||
| 30 | } // namespace | 30 | } // namespace | |||||
| 31 | 31 | |||||||
| 32 | struct blocking_context::impl | 32 | struct blocking_context::impl | |||||
| 33 | { | 33 | { | |||||
| 34 | std::mutex mtx; | 34 | std::mutex mtx; | |||||
| 35 | std::condition_variable cv; | 35 | std::condition_variable cv; | |||||
| 36 | std::queue<continuation> queue; | 36 | std::queue<continuation> queue; | |||||
| 37 | std::exception_ptr ep; | 37 | std::exception_ptr ep; | |||||
| 38 | bool done = false; | 38 | bool done = false; | |||||
| 39 | bool abandoned = false; | 39 | bool abandoned = false; | |||||
| 40 | }; | 40 | }; | |||||
| 41 | 41 | |||||||
| HITCBC | 42 | 1125 | blocking_context::blocking_context() | 42 | 1125 | blocking_context::blocking_context() | ||
| HITCBC | 43 | 1125 | : impl_(new impl) | 43 | 1125 | : impl_(new impl) | ||
| 44 | { | 44 | { | |||||
| HITCBC | 45 | 1125 | } | 45 | 1125 | } | ||
| 46 | 46 | |||||||
| HITCBC | 47 | 1125 | blocking_context::~blocking_context() | 47 | 1125 | blocking_context::~blocking_context() | ||
| 48 | { | 48 | { | |||||
| 49 | // Nothing will run these now; abandon them as their holder. The | 49 | // Nothing will run these now; abandon them as their holder. The | |||||
| 50 | // services may post while they shut down or are destroyed, so the | 50 | // services may post while they shut down or are destroyed, so the | |||||
| 51 | // queue outlives both. Each is taken under the lock, as other | 51 | // queue outlives both. Each is taken under the lock, as other | |||||
| 52 | // threads may still post, and abandoned outside it. | 52 | // threads may still post, and abandoned outside it. | |||||
| HITCBC | 53 | 2253 | auto drain = [this] | 53 | 2254 | auto drain = [this] | ||
| 54 | { | 54 | { | |||||
| HITCBC | 55 | 2253 | bool any = false; | 55 | 2254 | bool any = false; | ||
| 56 | for(;;) | 56 | for(;;) | |||||
| 57 | { | 57 | { | |||||
| HITCBC | 58 | 4260 | continuation c; | 58 | 4261 | continuation c; | ||
| 59 | { | 59 | { | |||||
| HITCBC | 60 | 4260 | std::lock_guard<std::mutex> lock(impl_->mtx); | 60 | 4261 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 61 | 4260 | if(impl_->queue.empty()) | 61 | 4261 | if(impl_->queue.empty()) | ||
| HITCBC | 62 | 4506 | return any; | 62 | 4508 | return any; | ||
| HITCBC | 63 | 2007 | c = impl_->queue.front(); | 63 | 2007 | c = impl_->queue.front(); | ||
| HITCBC | 64 | 2007 | impl_->queue.pop(); | 64 | 2007 | impl_->queue.pop(); | ||
| HITCBC | 65 | 4260 | } | 65 | 4261 | } | ||
| HITCBC | 66 | 2007 | any = true; | 66 | 2007 | any = true; | ||
| HITCBC | 67 | 2007 | capy::abandon(c); | 67 | 2007 | capy::abandon(c); | ||
| HITCBC | 68 | 2007 | } | 68 | 2007 | } | ||
| HITCBC | 69 | 1125 | }; | 69 | 1125 | }; | ||
| HITCBC | 70 | 1125 | shutdown(); | 70 | 1125 | shutdown(); | ||
| HITCBC | 71 | 1125 | drain(); | 71 | 1125 | drain(); | ||
| 72 | // A frame the drain unwinds can create a service, whose destructor | 72 | // A frame the drain unwinds can create a service, whose destructor | |||||
| 73 | // can post again: repeat until nothing is left of either. | 73 | // can post again: repeat until nothing is left of either. | |||||
| 74 | do | 74 | do | |||||
| 75 | { | 75 | { | |||||
| HITCBC | 76 | 1128 | destroy(); | 76 | 1129 | destroy(); | ||
| 77 | } | 77 | } | |||||
| HITCBC | 78 | 1128 | while(drain()); | 78 | 1129 | while(drain()); | ||
| HITCBC | 79 | 1125 | delete impl_; | 79 | 1125 | delete impl_; | ||
| HITCBC | 80 | 1125 | } | 80 | 1125 | } | ||
| 81 | 81 | |||||||
| 82 | blocking_executor | 82 | blocking_executor | |||||
| HITCBC | 83 | 1126 | blocking_context::get_executor() noexcept | 83 | 1126 | blocking_context::get_executor() noexcept | ||
| 84 | { | 84 | { | |||||
| HITCBC | 85 | 1126 | return blocking_executor{this}; | 85 | 1126 | return blocking_executor{this}; | ||
| 86 | } | 86 | } | |||||
| 87 | 87 | |||||||
| 88 | void | 88 | void | |||||
| HITCBC | 89 | 778 | blocking_context::signal_done() noexcept | 89 | 778 | blocking_context::signal_done() noexcept | ||
| 90 | { | 90 | { | |||||
| HITCBC | 91 | 778 | std::lock_guard<std::mutex> lock(impl_->mtx); | 91 | 778 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 92 | 778 | impl_->done = true; | 92 | 778 | impl_->done = true; | ||
| HITCBC | 93 | 778 | impl_->cv.notify_one(); | 93 | 778 | impl_->cv.notify_one(); | ||
| HITCBC | 94 | 778 | } | 94 | 778 | } | ||
| 95 | 95 | |||||||
| 96 | void | 96 | void | |||||
| HITCBC | 97 | 336 | blocking_context::signal_done( | 97 | 336 | blocking_context::signal_done( | ||
| 98 | std::exception_ptr ep) noexcept | 98 | std::exception_ptr ep) noexcept | |||||
| 99 | { | 99 | { | |||||
| HITCBC | 100 | 336 | std::lock_guard<std::mutex> lock(impl_->mtx); | 100 | 336 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 101 | 336 | impl_->ep = ep; | 101 | 336 | impl_->ep = ep; | ||
| HITCBC | 102 | 336 | impl_->done = true; | 102 | 336 | impl_->done = true; | ||
| HITCBC | 103 | 336 | impl_->cv.notify_one(); | 103 | 336 | impl_->cv.notify_one(); | ||
| HITCBC | 104 | 336 | } | 104 | 336 | } | ||
| 105 | 105 | |||||||
| 106 | void | 106 | void | |||||
| HITCBC | 107 | 1 | blocking_context::signal_abandoned() noexcept | 107 | 1 | blocking_context::signal_abandoned() noexcept | ||
| 108 | { | 108 | { | |||||
| HITCBC | 109 | 1 | std::lock_guard<std::mutex> lock(impl_->mtx); | 109 | 1 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 110 | 1 | impl_->abandoned = true; | 110 | 1 | impl_->abandoned = true; | ||
| HITCBC | 111 | 1 | impl_->done = true; | 111 | 1 | impl_->done = true; | ||
| HITCBC | 112 | 1 | impl_->cv.notify_one(); | 112 | 1 | impl_->cv.notify_one(); | ||
| HITCBC | 113 | 1 | } | 113 | 1 | } | ||
| 114 | 114 | |||||||
| 115 | void | 115 | void | |||||
| HITCBC | 116 | 1115 | blocking_context::run() | 116 | 1115 | blocking_context::run() | ||
| 117 | { | 117 | { | |||||
| 118 | struct scoped_running | 118 | struct scoped_running | |||||
| 119 | { | 119 | { | |||||
| 120 | blocking_context const* prev_; | 120 | blocking_context const* prev_; | |||||
| 121 | 121 | |||||||
| HITCBC | 122 | 1115 | explicit scoped_running(blocking_context const* ctx) noexcept | 122 | 1115 | explicit scoped_running(blocking_context const* ctx) noexcept | ||
| HITCBC | 123 | 1115 | : prev_(std::exchange(running_, ctx)) | 123 | 1115 | : prev_(std::exchange(running_, ctx)) | ||
| 124 | { | 124 | { | |||||
| HITCBC | 125 | 1115 | } | 125 | 1115 | } | ||
| 126 | 126 | |||||||
| HITCBC | 127 | 1115 | ~scoped_running() | 127 | 1115 | ~scoped_running() | ||
| 128 | { | 128 | { | |||||
| HITCBC | 129 | 1115 | running_ = prev_; | 129 | 1115 | running_ = prev_; | ||
| HITCBC | 130 | 1115 | } | 130 | 1115 | } | ||
| HITCBC | 131 | 1115 | } guard(this); | 131 | 1115 | } guard(this); | ||
| 132 | 132 | |||||||
| 133 | for(;;) | 133 | for(;;) | |||||
| 134 | { | 134 | { | |||||
| HITCBC | 135 | 2352 | continuation c; | 135 | 2352 | continuation c; | ||
| 136 | { | 136 | { | |||||
| HITCBC | 137 | 2352 | std::unique_lock<std::mutex> lock(impl_->mtx); | 137 | 2352 | std::unique_lock<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 138 | 2352 | impl_->cv.wait(lock, [&] { | 138 | 2352 | impl_->cv.wait(lock, [&] { | ||
| HITCBC | 139 | 2353 | return impl_->done || !impl_->queue.empty(); | 139 | 2353 | return impl_->done || !impl_->queue.empty(); | ||
| 140 | }); | 140 | }); | |||||
| HITCBC | 141 | 2352 | if(impl_->done && impl_->queue.empty()) | 141 | 2352 | if(impl_->done && impl_->queue.empty()) | ||
| HITCBC | 142 | 1115 | break; | 142 | 1115 | break; | ||
| HITCBC | 143 | 1237 | c = impl_->queue.front(); | 143 | 1237 | c = impl_->queue.front(); | ||
| HITCBC | 144 | 1237 | impl_->queue.pop(); | 144 | 1237 | impl_->queue.pop(); | ||
| HITCBC | 145 | 2352 | } | 145 | 2352 | } | ||
| HITCBC | 146 | 1237 | safe_resume(c.h); | 146 | 1237 | safe_resume(c.h); | ||
| HITCBC | 147 | 1237 | } | 147 | 1237 | } | ||
| HITCBC | 148 | 1115 | if(impl_->ep) | 148 | 1115 | if(impl_->ep) | ||
| HITCBC | 149 | 672 | std::rethrow_exception(impl_->ep); | 149 | 672 | std::rethrow_exception(impl_->ep); | ||
| HITCBC | 150 | 779 | if(impl_->abandoned) | 150 | 779 | if(impl_->abandoned) | ||
| HITCBC | 151 | 1 | throw std::system_error(error::canceled, "task abandoned"); | 151 | 1 | throw std::system_error(error::canceled, "task abandoned"); | ||
| HITCBC | 152 | 1115 | } | 152 | 1115 | } | ||
| 153 | 153 | |||||||
| 154 | void | 154 | void | |||||
| HITCBC | 155 | 3244 | blocking_context::enqueue( | 155 | 3244 | blocking_context::enqueue( | ||
| 156 | continuation const& c) | 156 | continuation const& c) | |||||
| 157 | { | 157 | { | |||||
| HITCBC | 158 | 3244 | std::lock_guard<std::mutex> lock(impl_->mtx); | 158 | 3244 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 159 | 3244 | impl_->queue.push(c); | 159 | 3244 | impl_->queue.push(c); | ||
| HITCBC | 160 | 3244 | impl_->cv.notify_one(); | 160 | 3244 | impl_->cv.notify_one(); | ||
| HITCBC | 161 | 3244 | } | 161 | 3244 | } | ||
| 162 | 162 | |||||||
| 163 | //---------------------------------------------------------- | 163 | //---------------------------------------------------------- | |||||
| 164 | 164 | |||||||
| 165 | bool | 165 | bool | |||||
| MISUBC | 166 | ✗ | blocking_executor::operator==( | 166 | ✗ | blocking_executor::operator==( | ||
| 167 | blocking_executor const& other) const noexcept | 167 | blocking_executor const& other) const noexcept | |||||
| 168 | { | 168 | { | |||||
| MISUBC | 169 | ✗ | return ctx_ == other.ctx_; | 169 | ✗ | return ctx_ == other.ctx_; | ||
| 170 | } | 170 | } | |||||
| 171 | 171 | |||||||
| 172 | blocking_context& | 172 | blocking_context& | |||||
| HITCBC | 173 | 1117 | blocking_executor::context() const noexcept | 173 | 1117 | blocking_executor::context() const noexcept | ||
| 174 | { | 174 | { | |||||
| HITCBC | 175 | 1117 | return *ctx_; | 175 | 1117 | return *ctx_; | ||
| 176 | } | 176 | } | |||||
| 177 | 177 | |||||||
| 178 | void | 178 | void | |||||
| HITCBC | 179 | 1120 | blocking_executor::on_work_started() const noexcept | 179 | 1120 | blocking_executor::on_work_started() const noexcept | ||
| 180 | { | 180 | { | |||||
| HITCBC | 181 | 1120 | } | 181 | 1120 | } | ||
| 182 | 182 | |||||||
| 183 | void | 183 | void | |||||
| HITCBC | 184 | 1120 | blocking_executor::on_work_finished() const noexcept | 184 | 1120 | blocking_executor::on_work_finished() const noexcept | ||
| 185 | { | 185 | { | |||||
| HITCBC | 186 | 1120 | } | 186 | 1120 | } | ||
| 187 | 187 | |||||||
| 188 | std::coroutine_handle<> | 188 | std::coroutine_handle<> | |||||
| HITCBC | 189 | 1163 | blocking_executor::dispatch( | 189 | 1163 | blocking_executor::dispatch( | ||
| 190 | continuation& c) const | 190 | continuation& c) const | |||||
| 191 | { | 191 | { | |||||
| HITCBC | 192 | 1163 | if(running_ == ctx_) | 192 | 1163 | if(running_ == ctx_) | ||
| HITCBC | 193 | 47 | return c.h; | 193 | 47 | return c.h; | ||
| HITCBC | 194 | 1116 | ctx_->enqueue(c); | 194 | 1116 | ctx_->enqueue(c); | ||
| HITCBC | 195 | 1116 | return std::noop_coroutine(); | 195 | 1116 | return std::noop_coroutine(); | ||
| 196 | } | 196 | } | |||||
| 197 | 197 | |||||||
| 198 | void | 198 | void | |||||
| HITCBC | 199 | 2128 | blocking_executor::post( | 199 | 2128 | blocking_executor::post( | ||
| 200 | continuation& c) const | 200 | continuation& c) const | |||||
| 201 | { | 201 | { | |||||
| HITCBC | 202 | 2128 | ctx_->enqueue(c); | 202 | 2128 | ctx_->enqueue(c); | ||
| HITCBC | 203 | 2128 | } | 203 | 2128 | } | ||
| 204 | 204 | |||||||
| 205 | } // namespace test | 205 | } // namespace test | |||||
| 206 | } // namespace capy | 206 | } // namespace capy | |||||
| 207 | } // namespace boost | 207 | } // namespace boost | |||||