docs for `join` and `noCancel`

This commit is contained in:
Jacek Sieka 2024-03-26 20:08:21 +01:00
parent aab6e8085e
commit 8166d7d0f1
No known key found for this signature in database
GPG Key ID: A1B09461ABB656B8
1 changed files with 83 additions and 3 deletions

View File

@ -4,6 +4,9 @@ Async/await is a programming model that relies on cooperative multitasking to
coordinate the concurrent execution of procedures, using event notifications coordinate the concurrent execution of procedures, using event notifications
from the operating system or other treads to resume execution. from the operating system or other treads to resume execution.
Code execution happens in a loop that alternates between making progress on
tasks and handling events.
<!-- toc --> <!-- toc -->
## The dispatcher ## The dispatcher
@ -118,7 +121,8 @@ The `CancelledError` will now travel up the stack like any other exception.
It can be caught for instance to free some resources and is then typically It can be caught for instance to free some resources and is then typically
re-raised for the whole chain operations to get cancelled. re-raised for the whole chain operations to get cancelled.
Alternatively, the cancellation request can be translated to a regular outcome of the operation - for example, a `read` operation might return an empty result. Alternatively, the cancellation request can be translated to a regular outcome
of the operation - for example, a `read` operation might return an empty result.
Cancelling an already-finished `Future` has no effect, as the following example Cancelling an already-finished `Future` has no effect, as the following example
of downloading two web pages concurrently shows: of downloading two web pages concurrently shows:
@ -127,8 +131,84 @@ of downloading two web pages concurrently shows:
{{#include ../examples/twogets.nim}} {{#include ../examples/twogets.nim}}
``` ```
### Ownership
When calling a procedure that returns a `Future`, ownership of that `Future` is
shared between the callee that created it and the caller that waits for it to be
finished.
The `Future` can be thought of as a single-item channel between a producer and a
consumer. The producer creates the `Future` and is responsible for completing or
failing it while the caller waits for completion and may `cancel` it.
Although it is technically possible, callers must not `complete` or `fail`
futures and callees or other intermediate observers must not `cancel` them as
this may lead to panics and shutdown (ie if the future is completed twice or a
cancalletion is not handled by the original caller).
### `noCancel`
Certain operations must not be cancelled for semantic reasons. Common scenarios
include `closeWait` that releases a resources irrevocably and composed
operations whose individual steps should be performed together or not at all.
In such cases, the `noCancel` modifier to `await` can be used to temporarily
disable cancellation propagation, allowing the operation to complete even if
the caller initiates a cancellation request:
```nim
proc deepSleep(dur: Duration) {.async.} =
# `noCancel` prevents any cancellation request by the caller of `deepSleep`
# from reaching `sleepAsync` - even if `deepSleep` is cancelled, its future
# will not complete until the sleep finishes.
await noCancel sleepAsync(dur)
let future = deepSleep(10.minutes)
# This will take ~10 minutes even if we try to cancel the call to `deepSleep`!
await cancelAndWait(future)
```
### `join`
The `join` modifier to `await` allows cancelling an `async` procedure without
propagating the cancellation to the awaited operation. This is useful when
`await`:ing a `Future` for monitoring purposes, ie when a procedure is not the
owner of the future that's being `await`:ed.
One situation where this happens is when implementing the "observer" pattern,
where a helper monitors an operation it did not initiate:
```nim
var tick: Future[void]
proc ticker() {.async.} =
while true:
tick = sleepAsync(1.second)
await tick
echo "tick!"
proc tocker() {.async.} =
# This operation does not own or implement the operation behind `tick`,
# so it should not cancel it when `tocker` is cancelled
await join tick
echo "tock!"
let
fut = ticker() # `ticker` is now looping and most likely waiting for `tick`
fut2 = tocker() # both `ticker` and `tocker` are waiting for `tick`
# We don't want `tocker` to cancel a future that was created in `ticker`
waitFor fut2.cancelAndWait()
waitFor fut # keeps printing `tick!` every second.
```
## Compile-time configuration ## Compile-time configuration
`chronos` contains several compile-time [configuration options](./chronos/config.nim) enabling stricter compile-time checks and debugging helpers whose runtime cost may be significant. `chronos` contains several compile-time
[configuration options](./chronos/config.nim) enabling stricter compile-time
checks and debugging helpers whose runtime cost may be significant.
Strictness options generally will become default in future chronos releases and allow adapting existing code without changing the new version - see the [`config.nim`](./chronos/config.nim) module for more information. Strictness options generally will become default in future chronos releases and
allow adapting existing code without changing the new version - see the
[`config.nim`](./chronos/config.nim) module for more information.