[breaking] implement new cancellation mechanism - #596
Open
Guest0x0 wants to merge 3 commits into
Open
Conversation
- `catch` can no longer capture cancellation - deprecate `@async.with_cancellation_handler` in favor of `@async.handle_cancellation` - introduce `nocancel` mark on type signature - group scoped defer block must be `nocancel` now
Guest0x0
force-pushed
the
new-cancellation-mechanism
branch
from
September 10, 2026 03:08
27dfe0c to
f475a42
Compare
Coverage Report for CI Build 1160Coverage decreased (-0.05%) to 77.687%Details
Uncovered Changes
Coverage Regressions12 previously-covered lines in 10 files lost coverage.
Coverage Stats
💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR utilizes a new compiler-builtin mechanism for implementing cancellation. In
moonbitlang/async, cancellation consist of two parts:asyncoperation will still get cancelled immediately), so that cancellation can never be accidentally swalloweddefer/errdeferblock etc. for cleanup. This signal is previously implemented as a privatesuberrorThe choice of representing
suberrorhas its merits: we getdefer/errdefersupport and propagation of cancellation signal for free. However, representing cancellation as a normalsuberroralso allows ordinarycatch, in particular catch-all handlers (i.e.catchwith a irrefutable pattern) to capture the cancellation signal, which can cause unwanted consequence. For example, if users want to wrap error raised by a function with additional context information with a catch-all handler, they may accidentally capture the cancellation signal and wrap it into something else. Wrapping cancellation into something else would turn a successful cancellation into fatal error, failing the program undesirably.To solve this problem, we have recently introduced some compiler-builtin mechanism for the cancellation signal. The compiler now provides a builtin cancellation pseudo-error and primitives for raising and capturing the pseudo-error. The special cancellation pseudo-error propagates like an error and can trigger
defer&errdeferblock. However,catch, includingcatch-allhandlers, can never capture the cancellation signal. This PR utilizes this new mechanism to implement cancellation inmoonbitlang/async, replacing the previoussuberrorapproach.As another consequence of compiler-builtin support for cancellation signal, we can now also track cancellation and error separately at type level. The latest version of MoonBit now provides a
nocanceleffect mark, representing "cannot raise cancellation signal".noraiseno longer covers cancellation signal. For convenience,asyncstill impliesraise+ cancellation by default. Explicitnoraise/nocancelannotation must be added for more precise type signature. Withnoraiseandnocancelseparated, many API that are cancellable but never raise an error themselves, such assleep, can now be assigned a more precisenoraisetype. In addition, we can also requirenocancelin critical cleanup code paths, such asdefer/errdefer/TaskGroup::add_defer.From the compiler's perspective,
nocancelmerely means "does not raise cancellation signal". But this is not equivalent to "not cancellable", because it is possible to write code that merely swallow the cancellation signal. Fortunately, cancellation related primitives are all hidden inmoonbitlang/async. And among the public API ofmoonbitlang/async, the only function that can erase cancellation from type signature is@async.protect_from_cancel, which also perform true cancellation protection. So for code using only public API ofmoonbitlang/async, we can guarantee that "nocancelis equivalent to not cancellable". This is also why@async.handle_cancellationis notnocancel: it merely capture the cancellation signal, but does not really protect the code from cancellation.Changes in this PR
catchcan no longer capture cancellationTaskGroup::add_defernow requires the defer block to benocancelresume_on_cancelparameter of@async.protect_from_cancelis now removed@async.is_cancellation_errornow always returnfalseand is deprecated@async.with_cancellation_handleris deprecated, in favor of a new API@async.handle_cancellationnocancel:@async.protect_from_cancel,@fs.remove,@fs.rmdirnoraise:@async.sleep,@async.CondVar::wait,@async.{Mutex,Semaphore}::acquire,@async.TaskGroup::return_immediately