The Cancel module describes the user's view of cancellation.
Internally, when the user calls a primitive operation that needs to block the fiber, the Suspend callback effect is performed. This suspends the fiber and calls callback from the scheduler's context, passing it the suspended fiber's context. If the operation can be cancelled, the callback should use set_cancel_fn to register a cancellation function.
There are two possible outcomes for the operation: it may complete normally, or it may be cancelled. If it is cancelled then the registered cancellation function is called. This function will always be called from the fiber's own domain, but care must be taken if the operation is being completed by another domain at the same time.
Consider the case of Stream.take, which can be fulfilled by a Stream.add from another domain. We want to ensure that either the item is removed from the stream and returned to the waiting fiber, or that the operation is cancelled and the item is not removed from the stream.
Therefore, cancelling and completing both attempt to clear the cancel function atomically, so that only one can succeed. The case where Stream.take succeeds before cancellation:
A fiber calls Suspend and is suspended. The callback sets a cancel function and registers a waiter on the stream.
When another domain has an item, it removes the cancel function (making the take uncancellable) and begins resuming the fiber with the new item.
If the taking fiber is cancelled after this, the cancellation will be ignored and the operation will complete successfully. Future operations will fail immediately, however.
The case of cancellation winning the race:
A fiber calls Suspend and is suspended. The callback sets a cancel function and registers a waiter on the stream.
The taking fiber is cancelled. Its cancellation function is called, which starts removing the waiter.
If another domain tries to provide an item to the waiter as this is happening, it will try to clear the cancel function and fail. The item will be given to the next waiter instead.
Note that there is a mutex around the list of waiters, so the taking domain can't finish removing the waiter and start another operation while the adding domain is trying to resume it. In future, we may want to make this lock-free by using a fresh atomic to hold the cancel function for each operation.
Note: A fiber will only have a cancel function set while it is suspended.
set_cancel_fn t fn sets fn as the fiber's cancel function.
If the cancellation context is cancelled, the function is removed and called. When the operation completes, you must call clear_cancel_fn to remove it.
clear_cancel_fn t removes the function previously set with set_cancel_fn, if any.
Returns true if this call removed the function, or false if there wasn't one. This operation is atomic and thread-safe. An operation that completes in another domain must use this to indicate that the operation is finished (can no longer be cancelled) before enqueuing the result. If it returns false, the operation was cancelled first and the canceller has called (or is calling) the function. If it returns true, the caller is responsible for any resources owned by the function, such as the continuation.