97.65% Lines (83/85)
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 | 1130 | blocking_context::blocking_context() | 42 | 1125 | blocking_context::blocking_context() | ||
| HITCBC | 43 | 1130 | : impl_(new impl) | 43 | 1125 | : impl_(new impl) | ||
| 44 | { | 44 | { | |||||
| HITCBC | 45 | 1130 | } | 45 | 1125 | } | ||
| 46 | 46 | |||||||
| HITCBC | 47 | 1130 | blocking_context::~blocking_context() | 47 | 1125 | blocking_context::~blocking_context() | ||
| 48 | { | 48 | { | |||||
| 49 | // Nothing will run these now; abandon them as their holder. Each is | 49 | // Nothing will run these now; abandon them as their holder. Each is | |||||
| 50 | // taken under the lock and abandoned outside it, since abandoning | 50 | // taken under the lock and abandoned outside it, since abandoning | |||||
| 51 | // one may post another. | 51 | // one may post another. | |||||
| HITCBC | 52 | 2260 | auto drain = [this] | 52 | 2250 | auto drain = [this] | ||
| 53 | { | 53 | { | |||||
| 54 | for(;;) | 54 | for(;;) | |||||
| 55 | { | 55 | { | |||||
| HITCBC | 56 | 2283 | continuation c; | 56 | 2273 | continuation c; | ||
| 57 | { | 57 | { | |||||
| HITCBC | 58 | 2283 | std::lock_guard<std::mutex> lock(impl_->mtx); | 58 | 2273 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 59 | 2283 | if(impl_->queue.empty()) | 59 | 2273 | if(impl_->queue.empty()) | ||
| HITCBC | 60 | 4520 | return; | 60 | 4500 | return; | ||
| HITCBC | 61 | 23 | c = impl_->queue.front(); | 61 | 23 | c = impl_->queue.front(); | ||
| HITCBC | 62 | 23 | impl_->queue.pop(); | 62 | 23 | impl_->queue.pop(); | ||
| HITCBC | 63 | 2283 | } | 63 | 2273 | } | ||
| HITCBC | 64 | 23 | capy::abandon(c); | 64 | 23 | capy::abandon(c); | ||
| HITCBC | 65 | 23 | } | 65 | 23 | } | ||
| HITCBC | 66 | 1130 | }; | 66 | 1125 | }; | ||
| HITCBC | 67 | 1130 | shutdown(); | 67 | 1125 | shutdown(); | ||
| 68 | // Takes what the unwinds post too, and the work of any service they | 68 | // Takes what the unwinds post too, and the work of any service they | |||||
| 69 | // create, which is shut down as it is created. | 69 | // create, which is shut down as it is created. | |||||
| HITCBC | 70 | 1130 | drain(); | 70 | 1125 | drain(); | ||
| HITCBC | 71 | 1130 | destroy(); | 71 | 1125 | destroy(); | ||
| 72 | // Services give up their work in shutdown(), but anything a | 72 | // Services give up their work in shutdown(), but anything a | |||||
| 73 | // destructor posts anyway is still destroyed. | 73 | // destructor posts anyway is still destroyed. | |||||
| HITCBC | 74 | 1130 | drain(); | 74 | 1125 | drain(); | ||
| HITCBC | 75 | 1130 | delete impl_; | 75 | 1125 | delete impl_; | ||
| HITCBC | 76 | 1130 | } | 76 | 1125 | } | ||
| 77 | 77 | |||||||
| 78 | blocking_executor | 78 | blocking_executor | |||||
| HITCBC | 79 | 1131 | blocking_context::get_executor() noexcept | 79 | 1126 | blocking_context::get_executor() noexcept | ||
| 80 | { | 80 | { | |||||
| HITCBC | 81 | 1131 | return blocking_executor{this}; | 81 | 1126 | return blocking_executor{this}; | ||
| 82 | } | 82 | } | |||||
| 83 | 83 | |||||||
| 84 | void | 84 | void | |||||
| HITCBC | 85 | 783 | blocking_context::signal_done() noexcept | 85 | 778 | blocking_context::signal_done() noexcept | ||
| 86 | { | 86 | { | |||||
| HITCBC | 87 | 783 | std::lock_guard<std::mutex> lock(impl_->mtx); | 87 | 778 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 88 | 783 | impl_->done = true; | 88 | 778 | impl_->done = true; | ||
| HITCBC | 89 | 783 | impl_->cv.notify_one(); | 89 | 778 | impl_->cv.notify_one(); | ||
| HITCBC | 90 | 783 | } | 90 | 778 | } | ||
| 91 | 91 | |||||||
| 92 | void | 92 | void | |||||
| HITCBC | 93 | 336 | blocking_context::signal_done( | 93 | 336 | blocking_context::signal_done( | ||
| 94 | std::exception_ptr ep) noexcept | 94 | std::exception_ptr ep) noexcept | |||||
| 95 | { | 95 | { | |||||
| HITCBC | 96 | 336 | std::lock_guard<std::mutex> lock(impl_->mtx); | 96 | 336 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 97 | 336 | impl_->ep = ep; | 97 | 336 | impl_->ep = ep; | ||
| HITCBC | 98 | 336 | impl_->done = true; | 98 | 336 | impl_->done = true; | ||
| HITCBC | 99 | 336 | impl_->cv.notify_one(); | 99 | 336 | impl_->cv.notify_one(); | ||
| HITCBC | 100 | 336 | } | 100 | 336 | } | ||
| 101 | 101 | |||||||
| 102 | void | 102 | void | |||||
| HITCBC | 103 | 1 | blocking_context::signal_abandoned() noexcept | 103 | 1 | blocking_context::signal_abandoned() noexcept | ||
| 104 | { | 104 | { | |||||
| HITCBC | 105 | 1 | std::lock_guard<std::mutex> lock(impl_->mtx); | 105 | 1 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 106 | 1 | impl_->abandoned = true; | 106 | 1 | impl_->abandoned = true; | ||
| HITCBC | 107 | 1 | impl_->done = true; | 107 | 1 | impl_->done = true; | ||
| HITCBC | 108 | 1 | impl_->cv.notify_one(); | 108 | 1 | impl_->cv.notify_one(); | ||
| HITCBC | 109 | 1 | } | 109 | 1 | } | ||
| 110 | 110 | |||||||
| 111 | void | 111 | void | |||||
| HITCBC | 112 | 1120 | blocking_context::run() | 112 | 1115 | blocking_context::run() | ||
| 113 | { | 113 | { | |||||
| 114 | struct scoped_running | 114 | struct scoped_running | |||||
| 115 | { | 115 | { | |||||
| 116 | blocking_context const* prev_; | 116 | blocking_context const* prev_; | |||||
| 117 | 117 | |||||||
| HITCBC | 118 | 1120 | explicit scoped_running(blocking_context const* ctx) noexcept | 118 | 1115 | explicit scoped_running(blocking_context const* ctx) noexcept | ||
| HITCBC | 119 | 1120 | : prev_(std::exchange(running_, ctx)) | 119 | 1115 | : prev_(std::exchange(running_, ctx)) | ||
| 120 | { | 120 | { | |||||
| HITCBC | 121 | 1120 | } | 121 | 1115 | } | ||
| 122 | 122 | |||||||
| HITCBC | 123 | 1120 | ~scoped_running() | 123 | 1115 | ~scoped_running() | ||
| 124 | { | 124 | { | |||||
| HITCBC | 125 | 1120 | running_ = prev_; | 125 | 1115 | running_ = prev_; | ||
| HITCBC | 126 | 1120 | } | 126 | 1115 | } | ||
| HITCBC | 127 | 1120 | } guard(this); | 127 | 1115 | } guard(this); | ||
| 128 | 128 | |||||||
| 129 | for(;;) | 129 | for(;;) | |||||
| 130 | { | 130 | { | |||||
| HITCBC | 131 | 2362 | continuation c; | 131 | 2352 | continuation c; | ||
| 132 | { | 132 | { | |||||
| HITCBC | 133 | 2362 | std::unique_lock<std::mutex> lock(impl_->mtx); | 133 | 2352 | std::unique_lock<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 134 | 2362 | impl_->cv.wait(lock, [&] { | 134 | 2352 | impl_->cv.wait(lock, [&] { | ||
| HITCBC | 135 | 2363 | return impl_->done || !impl_->queue.empty(); | 135 | 2353 | return impl_->done || !impl_->queue.empty(); | ||
| 136 | }); | 136 | }); | |||||
| HITCBC | 137 | 2362 | if(impl_->done && impl_->queue.empty()) | 137 | 2352 | if(impl_->done && impl_->queue.empty()) | ||
| HITCBC | 138 | 1120 | break; | 138 | 1115 | break; | ||
| HITCBC | 139 | 1242 | c = impl_->queue.front(); | 139 | 1237 | c = impl_->queue.front(); | ||
| HITCBC | 140 | 1242 | impl_->queue.pop(); | 140 | 1237 | impl_->queue.pop(); | ||
| HITCBC | 141 | 2362 | } | 141 | 2352 | } | ||
| HITCBC | 142 | 1242 | safe_resume(c.h); | 142 | 1237 | safe_resume(c.h); | ||
| HITCBC | 143 | 1242 | } | 143 | 1237 | } | ||
| HITCBC | 144 | 1120 | if(impl_->ep) | 144 | 1115 | if(impl_->ep) | ||
| HITCBC | 145 | 672 | std::rethrow_exception(impl_->ep); | 145 | 672 | std::rethrow_exception(impl_->ep); | ||
| HITCBC | 146 | 784 | if(impl_->abandoned) | 146 | 779 | if(impl_->abandoned) | ||
| HITCBC | 147 | 1 | throw std::system_error(error::canceled, "task abandoned"); | 147 | 1 | throw std::system_error(error::canceled, "task abandoned"); | ||
| HITCBC | 148 | 1120 | } | 148 | 1115 | } | ||
| 149 | 149 | |||||||
| 150 | void | 150 | void | |||||
| HITCBC | 151 | 1265 | blocking_context::enqueue( | 151 | 1260 | blocking_context::enqueue( | ||
| 152 | continuation const& c) | 152 | continuation const& c) | |||||
| 153 | { | 153 | { | |||||
| HITCBC | 154 | 1265 | std::lock_guard<std::mutex> lock(impl_->mtx); | 154 | 1260 | std::lock_guard<std::mutex> lock(impl_->mtx); | ||
| HITCBC | 155 | 1265 | impl_->queue.push(c); | 155 | 1260 | impl_->queue.push(c); | ||
| HITCBC | 156 | 1265 | impl_->cv.notify_one(); | 156 | 1260 | impl_->cv.notify_one(); | ||
| HITCBC | 157 | 1265 | } | 157 | 1260 | } | ||
| 158 | 158 | |||||||
| 159 | //---------------------------------------------------------- | 159 | //---------------------------------------------------------- | |||||
| 160 | 160 | |||||||
| 161 | bool | 161 | bool | |||||
| MISUBC | 162 | ✗ | blocking_executor::operator==( | 162 | ✗ | blocking_executor::operator==( | ||
| 163 | blocking_executor const& other) const noexcept | 163 | blocking_executor const& other) const noexcept | |||||
| 164 | { | 164 | { | |||||
| MISUBC | 165 | ✗ | return ctx_ == other.ctx_; | 165 | ✗ | return ctx_ == other.ctx_; | ||
| 166 | } | 166 | } | |||||
| 167 | 167 | |||||||
| 168 | blocking_context& | 168 | blocking_context& | |||||
| HITCBC | 169 | 1122 | blocking_executor::context() const noexcept | 169 | 1117 | blocking_executor::context() const noexcept | ||
| 170 | { | 170 | { | |||||
| HITCBC | 171 | 1122 | return *ctx_; | 171 | 1117 | return *ctx_; | ||
| 172 | } | 172 | } | |||||
| 173 | 173 | |||||||
| 174 | void | 174 | void | |||||
| HITCBC | 175 | 1125 | blocking_executor::on_work_started() const noexcept | 175 | 1120 | blocking_executor::on_work_started() const noexcept | ||
| 176 | { | 176 | { | |||||
| HITCBC | 177 | 1125 | } | 177 | 1120 | } | ||
| 178 | 178 | |||||||
| 179 | void | 179 | void | |||||
| HITCBC | 180 | 1125 | blocking_executor::on_work_finished() const noexcept | 180 | 1120 | blocking_executor::on_work_finished() const noexcept | ||
| 181 | { | 181 | { | |||||
| HITCBC | 182 | 1125 | } | 182 | 1120 | } | ||
| 183 | 183 | |||||||
| 184 | std::coroutine_handle<> | 184 | std::coroutine_handle<> | |||||
| HITCBC | 185 | 1168 | blocking_executor::dispatch( | 185 | 1163 | blocking_executor::dispatch( | ||
| 186 | continuation& c) const | 186 | continuation& c) const | |||||
| 187 | { | 187 | { | |||||
| HITCBC | 188 | 1168 | if(running_ == ctx_) | 188 | 1163 | if(running_ == ctx_) | ||
| HITCBC | 189 | 47 | return c.h; | 189 | 47 | return c.h; | ||
| HITCBC | 190 | 1121 | ctx_->enqueue(c); | 190 | 1116 | ctx_->enqueue(c); | ||
| HITCBC | 191 | 1121 | return std::noop_coroutine(); | 191 | 1116 | return std::noop_coroutine(); | ||
| 192 | } | 192 | } | |||||
| 193 | 193 | |||||||
| 194 | void | 194 | void | |||||
| HITCBC | 195 | 144 | blocking_executor::post( | 195 | 144 | blocking_executor::post( | ||
| 196 | continuation& c) const | 196 | continuation& c) const | |||||
| 197 | { | 197 | { | |||||
| HITCBC | 198 | 144 | ctx_->enqueue(c); | 198 | 144 | ctx_->enqueue(c); | ||
| HITCBC | 199 | 144 | } | 199 | 144 | } | ||
| 200 | 200 | |||||||
| 201 | } // namespace test | 201 | } // namespace test | |||||
| 202 | } // namespace capy | 202 | } // namespace capy | |||||
| 203 | } // namespace boost | 203 | } // namespace boost | |||||