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