fix: make timer_.on_expired survive (not crash) if the timer as been destroyed
This commit is contained in:
parent
bbcc800964
commit
367c497a38
1 changed files with 26 additions and 10 deletions
|
|
@ -761,24 +761,40 @@ struct timeout_impl {
|
||||||
owner_.env_.trace(array("timeout", "set", m, us.count()));
|
owner_.env_.trace(array("timeout", "set", m, us.count()));
|
||||||
|
|
||||||
timer_.expires_after(us);
|
timer_.expires_after(us);
|
||||||
timer_.on_expired([this, start, m(move(m)),
|
// Capture lifelock and dtor_cancelled instead of `this`.
|
||||||
lifelock{owner_.lifetime_}](const error_code &error) {
|
// timeout_impl is owned by a unique_ptr and its destructor calls cancel();
|
||||||
if (is_trace_enabled())
|
// the ASIO callback is queued *after* ~timeout_impl returns, so `this`
|
||||||
owner_.env_.trace(array("timeout", "expired", m,
|
// would be dangling. lifelock keeps the instance alive; dtor_cancelled
|
||||||
clk::now().time_since_epoch().count() - start,
|
// distinguishes destructor-triggered cancellation (where no notification
|
||||||
error.value(), error.message()));
|
// is desired) from an explicit user cancel() call.
|
||||||
|
timer_.on_expired([lifelock{owner_.lifetime_},
|
||||||
|
dtor_cancelled{dtor_cancelled_},
|
||||||
|
start, m(move(m))](const error_code &error) {
|
||||||
|
if (!lifelock) return;
|
||||||
|
if (lifelock->env_.enabled(channel::timer))
|
||||||
|
lifelock->env_.trace(array("timeout", "expired", m,
|
||||||
|
clk::now().time_since_epoch().count() - start,
|
||||||
|
error.value(), error.message()));
|
||||||
if (!error)
|
if (!error)
|
||||||
auto _ = owner_.send_raw(m);
|
auto _ = lifelock->send_raw(m);
|
||||||
else
|
else if (!*dtor_cancelled)
|
||||||
auto _ = owner_.send_raw(
|
// User called cancel() explicitly — preserve the existing timeout_error
|
||||||
|
// notification so callers can react (e.g. arm a different timer).
|
||||||
|
auto _ = lifelock->send_raw(
|
||||||
exit_message("timeout_error", error.value(), error.message()));
|
exit_message("timeout_error", error.value(), error.message()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
~timeout_impl() { cancel(); }
|
~timeout_impl() {
|
||||||
|
*dtor_cancelled_ = true;
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
void cancel() { timer_.cancel(); }
|
void cancel() { timer_.cancel(); }
|
||||||
|
|
||||||
instance &owner_;
|
instance &owner_;
|
||||||
executor::timer timer_;
|
executor::timer timer_;
|
||||||
|
// Shared with the timer callback so it can distinguish dtor cancellation
|
||||||
|
// from an explicit user cancel() without capturing `this`.
|
||||||
|
shared_ptr<bool> dtor_cancelled_{make_shared<bool>(false)};
|
||||||
};
|
};
|
||||||
void timeout::cancel() {
|
void timeout::cancel() {
|
||||||
if (ref)
|
if (ref)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue