diff --git a/docs/src/concepts.md b/docs/src/concepts.md index 0469b8be4..72a5db3ae 100644 --- a/docs/src/concepts.md +++ b/docs/src/concepts.md @@ -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 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. + ## 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 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 of downloading two web pages concurrently shows: @@ -127,8 +131,84 @@ of downloading two web pages concurrently shows: {{#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 -`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.