37#include <asio/any_io_executor.hpp>
38#include <asio/cancellation_signal.hpp>
39#include <asio/post.hpp>
40#include <asio/strand.hpp>
43#include <condition_variable>
53namespace neograph::graph {
68 : std::runtime_error(
"neograph: run cancelled") {}
70 : std::runtime_error(
"neograph: run cancelled — " + detail) {}
105 if (stop_source_.stop_requested())
return;
108 auto keep_alive = self_keep_alive_for_post();
109 if (!stop_source_.request_stop())
return;
114 std::lock_guard<std::mutex> lk(mu_);
117 [
this, binding = binding_generation_,
118 keep_alive = std::move(keep_alive)]() {
119 emit_if_bound(binding);
134 std::vector<std::shared_ptr<CancelToken>> live_children;
136 std::lock_guard<std::mutex> lk(children_mu_);
137 live_children.reserve(children_.size());
138 for (
auto& w : children_) {
139 if (
auto sp = w.lock(); sp && sp.get() !=
this) {
140 live_children.push_back(std::move(sp));
147 for (
auto& child : live_children) {
154 return stop_source_.stop_requested();
159 return stop_source_.get_token();
181 [[nodiscard]] asio::any_io_executor
bind_executor(asio::any_io_executor ex) {
182 asio::any_io_executor bound;
183 bool fire_immediately =
false;
184 std::uint64_t binding = 0;
186 std::lock_guard<std::mutex> lk(mu_);
187 ex_ = ex ? asio::any_io_executor(asio::make_strand(std::move(ex)))
188 : asio::any_io_executor{};
189 binding = ++binding_generation_;
191 fire_immediately = stop_source_.stop_requested();
193 emit_finished_.notify_all();
194 if (fire_immediately && bound) {
195 auto keep_alive = self_keep_alive_for_post();
197 [
this, binding, keep_alive = std::move(keep_alive)]() {
198 emit_if_bound(binding);
228 asio::any_io_executor released;
230 std::unique_lock<std::mutex> lk(mu_);
231 released = std::move(ex_);
232 ++binding_generation_;
233 emit_finished_.wait(lk, [
this] {
return !emitting_; });
240 static_cast<void>(released);
252 asio::cancellation_slot
slot() noexcept {
261 if (is_cancelled()) {
298 [[nodiscard]] std::shared_ptr<CancelToken>
fork() {
302 auto child = std::shared_ptr<CancelToken>(
new CancelToken());
307 std::lock_guard<std::mutex> lk(child->children_mu_);
308 child->children_.push_back(child);
312 std::lock_guard<std::mutex> lk(children_mu_);
321 children_.begin(), children_.end(),
322 [](
const std::weak_ptr<CancelToken>& w) {
326 children_.push_back(child);
332 if (stop_source_.stop_requested()) {
339 void emit_if_bound(std::uint64_t binding) {
341 std::unique_lock<std::mutex> lk(mu_);
342 emit_finished_.wait(lk, [
this, binding] {
343 return !ex_ || binding != binding_generation_ || !emitting_;
345 if (!ex_ || binding != binding_generation_)
return;
348 struct EmitCompletion {
352 std::lock_guard<std::mutex> lk(token.mu_);
353 token.emitting_ =
false;
355 token.emit_finished_.notify_all();
360 sig_.emit(asio::cancellation_type::all);
363 std::shared_ptr<CancelToken> self_keep_alive_for_post() {
364 std::lock_guard<std::mutex> lk(children_mu_);
365 for (
auto& candidate : children_) {
366 if (
auto self = candidate.lock(); self.get() ==
this) {
373 std::stop_source stop_source_;
374 mutable std::mutex mu_;
375 std::condition_variable emit_finished_;
376 std::uint64_t binding_generation_ = 0;
377 bool emitting_ =
false;
378 asio::any_io_executor ex_;
379 asio::cancellation_signal sig_;
383 mutable std::mutex children_mu_;
384 std::vector<std::weak_ptr<CancelToken>> children_;
391class CancelExecutorLease final {
393 explicit CancelExecutorLease(std::shared_ptr<CancelToken> token) noexcept
394 : token_(std::move(token)) {}
396 CancelExecutorLease(
const CancelExecutorLease&) =
delete;
397 CancelExecutorLease& operator=(
const CancelExecutorLease&) =
delete;
399 ~CancelExecutorLease() noexcept {
400 if (token_) token_->unbind_executor();
404 std::shared_ptr<CancelToken> token_;
NEOGRAPH_API export/import macro for shared-library builds.
Cooperative cancel primitive shared between caller and engine.
void unbind_executor() noexcept
Detach the serial executor after every bound operation drained.
std::stop_token stop_token() const noexcept
Native cancellation subscriptions independent of Asio slot ownership.
void throw_if_cancelled(const std::string &detail={}) const
Throws CancelledException if cancelled.
asio::any_io_executor bind_executor(asio::any_io_executor ex)
Bind a serial executor that owns this token's cancellation_signal.
asio::cancellation_slot slot() noexcept
Cancellation slot for binding to a coroutine via asio::bind_cancellation_slot at co_spawn.
void cancel() noexcept
Request cancellation.
bool is_cancelled() const noexcept
Thread-safe observation of the same state used by subscriptions.
std::shared_ptr< CancelToken > fork()
Create a child token that cascades from this one.
Thrown by the engine when a run is cancelled mid-flight via CancelToken::cancel().