nim-chronos/README.md

488 lines
16 KiB
Markdown
Raw Normal View History

# Chronos - An efficient library for asynchronous programming
2019-02-06 15:43:27 +00:00
2023-09-05 10:48:09 +00:00
[![Github action](https://github.com/status-im/nim-chronos/workflows/CI/badge.svg)](https://github.com/status-im/nim-chronos/actions/workflows/ci.yml)
2018-09-05 03:36:14 +00:00
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
2018-09-05 03:27:01 +00:00
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
2018-05-21 22:15:34 +00:00
## Introduction
2021-03-20 07:12:35 +00:00
Chronos is an efficient [async/await](https://en.wikipedia.org/wiki/Async/await) framework for Nim. Features include:
* Asynchronous socket and process I/O
2021-03-20 07:12:35 +00:00
* HTTP server with SSL/TLS support out of the box (no OpenSSL needed)
* Synchronization primitivies like queues, events and locks
* Cancellation
* Efficient dispatch pipeline with excellent multi-platform support
* Exception effect support (see [exception effects](#exception-effects))
2018-05-30 03:34:58 +00:00
2018-05-30 03:32:47 +00:00
## Installation
2021-03-20 07:12:35 +00:00
You can use Nim's official package manager Nimble to install Chronos:
2018-05-28 23:35:15 +00:00
2021-03-20 07:12:35 +00:00
```text
nimble install chronos
2018-05-30 03:32:47 +00:00
```
2021-03-20 07:12:35 +00:00
or add a dependency to your `.nimble` file:
```text
requires "chronos"
2018-05-30 03:32:47 +00:00
```
2018-05-28 23:35:15 +00:00
2021-03-20 07:12:35 +00:00
## Projects using `chronos`
* [libp2p](https://github.com/status-im/nim-libp2p) - Peer-to-Peer networking stack implemented in many languages
2022-09-06 16:50:12 +00:00
* [presto](https://github.com/status-im/nim-presto) - REST API framework
* [Scorper](https://github.com/bung87/scorper) - Web framework
2021-03-20 07:12:35 +00:00
* [2DeFi](https://github.com/gogolxdong/2DeFi) - Decentralised file system
2022-09-06 16:50:12 +00:00
* [websock](https://github.com/status-im/nim-websock/) - WebSocket library with lots of features
2021-03-20 07:12:35 +00:00
`chronos` is available in the [Nim Playground](https://play.nim-lang.org/#ix=2TpS)
Submit a PR to add yours!
## Documentation
### Concepts
Chronos implements the async/await paradigm in a self-contained library using
the macro and closure iterator transformation features provided by Nim.
The event loop is called a "dispatcher" and a single instance per thread is
created, as soon as one is needed.
To trigger a dispatcher's processing step, we need to call `poll()` - either
directly or through a wrapper like `runForever()` or `waitFor()`. Each step
handles any file descriptors, timers and callbacks that are ready to be
processed.
`Future` objects encapsulate the result of an `async` procedure upon successful
completion, and a list of callbacks to be scheduled after any type of
completion - be that success, failure or cancellation.
(These explicit callbacks are rarely used outside Chronos, being replaced by
implicit ones generated by async procedure execution and `await` chaining.)
Async procedures (those using the `{.async.}` pragma) return `Future` objects.
Inside an async procedure, you can `await` the future returned by another async
procedure. At this point, control will be handled to the event loop until that
future is completed.
Future completion is tested with `Future.finished()` and is defined as success,
failure or cancellation. This means that a future is either pending or completed.
To differentiate between completion states, we have `Future.failed()` and
`Future.cancelled()`.
### Dispatcher
You can run the "dispatcher" event loop forever, with `runForever()` which is defined as:
```nim
proc runForever*() =
while true:
poll()
```
You can also run it until a certain future is completed, with `waitFor()` which
will also call `Future.read()` on it:
```nim
proc p(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
return 1
echo waitFor p() # prints "1"
```
`waitFor()` is defined like this:
```nim
proc waitFor*[T](fut: Future[T]): T =
while not(fut.finished()):
poll()
return fut.read()
```
### Async procedures and methods
The `{.async.}` pragma will transform a procedure (or a method) returning a
specialised `Future` type into a closure iterator. If there is no return type
specified, a `Future[void]` is returned.
```nim
proc p() {.async.} =
await sleepAsync(100.milliseconds)
echo p().type # prints "Future[system.void]"
```
Whenever `await` is encountered inside an async procedure, control is passed
back to the dispatcher for as many steps as it's necessary for the awaited
future to complete successfully, fail or be cancelled. `await` calls the
equivalent of `Future.read()` on the completed future and returns the
encapsulated value.
```nim
proc p1() {.async.} =
await sleepAsync(1.seconds)
proc p2() {.async.} =
await sleepAsync(1.seconds)
proc p3() {.async.} =
let
fut1 = p1()
fut2 = p2()
# Just by executing the async procs, both resulting futures entered the
# dispatcher's queue and their "clocks" started ticking.
await fut1
await fut2
# Only one second passed while awaiting them both, not two.
waitFor p3()
```
Don't let `await`'s behaviour of giving back control to the dispatcher surprise
you. If an async procedure modifies global state, and you can't predict when it
will start executing, the only way to avoid that state changing underneath your
feet, in a certain section, is to not use `await` in it.
### Error handling
Exceptions inheriting from [`CatchableError`](https://nim-lang.org/docs/system.html#CatchableError)
interrupt execution of the `async` procedure. The exception is placed in the
`Future.error` field while changing the status of the `Future` to `Failed`
and callbacks are scheduled.
When a future is awaited, the exception is re-raised, traversing the `async`
execution chain until handled.
```nim
proc p1() {.async.} =
await sleepAsync(1.seconds)
raise newException(ValueError, "ValueError inherits from CatchableError")
proc p2() {.async.} =
await sleepAsync(1.seconds)
proc p3() {.async.} =
let
fut1 = p1()
fut2 = p2()
await fut1
echo "unreachable code here"
await fut2
# `waitFor()` would call `Future.read()` unconditionally, which would raise the
# exception in `Future.error`.
let fut3 = p3()
while not(fut3.finished()):
poll()
echo "fut3.state = ", fut3.state # "Failed"
if fut3.failed():
echo "p3() failed: ", fut3.error.name, ": ", fut3.error.msg
# prints "p3() failed: ValueError: ValueError inherits from CatchableError"
```
You can put the `await` in a `try` block, to deal with that exception sooner:
```nim
proc p3() {.async.} =
let
fut1 = p1()
fut2 = p2()
try:
await fut1
exception tracking (#166) * exception tracking This PR adds minimal exception tracking to chronos, moving the goalpost one step further. In particular, it becomes invalid to raise exceptions from `callSoon` callbacks: this is critical for writing correct error handling because there's no reasonable way that a user of chronos can possibly _reason_ about exceptions coming out of there: the event loop will be in an indeterminite state when the loop is executing an _random_ callback. As expected, there are several issues in the error handling of chronos: in particular, it will end up in an inconsistent internal state whenever the selector loop operations fail, because the internal state update functions are not written in an exception-safe way. This PR turns this into a Defect, which probably is not the optimal way of handling things - expect more work to be done here. Some API have no way of reporting back errors to callers - for example, when something fails in the accept loop, there's not much it can do, and no way to report it back to the user of the API - this has been fixed with the new accept flow - the old one should be deprecated. Finally, there is information loss in the API: in composite operations like `poll` and `waitFor` there's no way to differentiate internal errors from user-level errors originating from callbacks. * store `CatchableError` in future * annotate proc's with correct raises information * `selectors2` to avoid non-CatchableError IOSelectorsException * `$` should never raise * remove unnecessary gcsafe annotations * fix exceptions leaking out of timer waits * fix some imports * functions must signal raising the union of all exceptions across all platforms to enable cross-platform code * switch to unittest2 * add `selectors2` which supercedes the std library version and fixes several exception handling issues in there * fixes * docs, platform-independent eh specifiers for some functions * add feature flag for strict exception mode also bump version to 3.0.0 - _most_ existing code should be compatible with this version of exception handling but some things might need fixing - callbacks, existing raises specifications etc. * fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
except CachableError:
echo "p1() failed: ", fut1.error.name, ": ", fut1.error.msg
echo "reachable code here"
await fut2
```
Because `chronos` ensures that all exceptions are re-routed to the `Future`,
`poll` will not itself raise exceptions.
`poll` may still panic / raise `Defect` if such are raised in user code due to
undefined behavior.
exception tracking (#166) * exception tracking This PR adds minimal exception tracking to chronos, moving the goalpost one step further. In particular, it becomes invalid to raise exceptions from `callSoon` callbacks: this is critical for writing correct error handling because there's no reasonable way that a user of chronos can possibly _reason_ about exceptions coming out of there: the event loop will be in an indeterminite state when the loop is executing an _random_ callback. As expected, there are several issues in the error handling of chronos: in particular, it will end up in an inconsistent internal state whenever the selector loop operations fail, because the internal state update functions are not written in an exception-safe way. This PR turns this into a Defect, which probably is not the optimal way of handling things - expect more work to be done here. Some API have no way of reporting back errors to callers - for example, when something fails in the accept loop, there's not much it can do, and no way to report it back to the user of the API - this has been fixed with the new accept flow - the old one should be deprecated. Finally, there is information loss in the API: in composite operations like `poll` and `waitFor` there's no way to differentiate internal errors from user-level errors originating from callbacks. * store `CatchableError` in future * annotate proc's with correct raises information * `selectors2` to avoid non-CatchableError IOSelectorsException * `$` should never raise * remove unnecessary gcsafe annotations * fix exceptions leaking out of timer waits * fix some imports * functions must signal raising the union of all exceptions across all platforms to enable cross-platform code * switch to unittest2 * add `selectors2` which supercedes the std library version and fixes several exception handling issues in there * fixes * docs, platform-independent eh specifiers for some functions * add feature flag for strict exception mode also bump version to 3.0.0 - _most_ existing code should be compatible with this version of exception handling but some things might need fixing - callbacks, existing raises specifications etc. * fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
#### Checked exceptions
By specifying a `raises` list to an async procedure, you can check which
exceptions can be raised by it:
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
```nim
proc p1(): Future[void] {.async: (raises: [IOError]).} =
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
assert not (compiles do: raise newException(ValueError, "uh-uh"))
raise newException(IOError, "works") # Or any child of IOError
proc p2(): Future[void] {.async, (raises: [IOError]).} =
await p1() # Works, because await knows that p1
# can only raise IOError
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
```
Under the hood, the return type of `p1` will be rewritten to an internal type
2023-10-24 14:21:07 +00:00
which will convey raises informations to `await`.
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
#### The `Exception` type
Exceptions deriving from `Exception` are not caught by default as these may
include `Defect` and other forms undefined or uncatchable behavior.
Because exception effect tracking is turned on for `async` functions, this may
sometimes lead to compile errors around forward declarations, methods and
closures as Nim conservatively asssumes that any `Exception` might be raised
from those.
Make sure to excplicitly annotate these with `{.raises.}`:
```nim
# Forward declarations need to explicitly include a raises list:
proc myfunction() {.raises: [ValueError].}
# ... as do `proc` types
type MyClosure = proc() {.raises: [ValueError].}
proc myfunction() =
raise (ref ValueError)(msg: "Implementation here")
let closure: MyClosure = myfunction
```
For compatibility, `async` functions can be instructed to handle `Exception` as
well, specifying `handleException: true`. `Exception` that is not a `Defect` and
not a `CatchableError` will then be caught and remapped to
`AsyncExceptionError`:
```nim
proc raiseException() {.async: (handleException: true, raises: [AsyncExceptionError]).} =
raise (ref Exception)(msg: "Raising Exception is UB")
proc callRaiseException() {.async: (raises: []).} =
try:
raiseException()
except AsyncExceptionError as exc:
# The original Exception is available from the `parent` field
echo exc.parent.msg
```
This mode can be enabled globally with `-d:chronosHandleException` as a help
when porting code to `chronos` but should generally be avoided as global
configuration settings may interfere with libraries that use `chronos` leading
to unexpected behavior.
### Raw functions
Raw functions are those that interact with `chronos` via the `Future` type but
whose body does not go through the async transformation.
Such functions are created by adding `raw: true` to the `async` parameters:
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
```nim
proc rawAsync(): Future[void] {.async: (raw: true).} =
let future = newFuture[void]("rawAsync")
future.complete()
return future
```
Raw functions must not raise exceptions directly - they are implicitly declared
as `raises: []` - instead they should store exceptions in the returned `Future`:
```nim
proc rawFailure(): Future[void] {.async: (raw: true).} =
let future = newFuture[void]("rawAsync")
future.fail((ref ValueError)(msg: "Oh no!"))
return future
```
Raw functions can also use checked exceptions:
```nim
proc rawAsyncRaises(): Future[void] {.async: (raw: true, raises: [IOError]).} =
let fut = newFuture[void]()
assert not (compiles do: fut.fail((ref ValueError)(msg: "uh-uh")))
fut.fail((ref IOError)(msg: "IO"))
return fut
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
```
### Callbacks and closures
Callback/closure types are declared using the `async` annotation as usual:
2023-10-24 14:21:07 +00:00
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
```nim
type MyCallback = proc(): Future[void] {.async.}
proc runCallback(cb: MyCallback) {.async: (raises: []).} =
try:
await cb()
except CatchableError:
discard # handle errors as usual
Raise tracking (#251) * Exception tracking v2 * some fixes * Nim 1.2 compat * simpler things * Fixes for libp2p * Fixes for strictException * better await exception check * Fix for template async proc * make async work with procTy * FuturEx is now a ref object type * add tests * update test * update readme * Switch to asyncraises pragma * Address tests review comments * Rename FuturEx to RaiseTrackingFuture * Fix typo * Split asyncraises into async, asyncraises * Add -d:chronosWarnMissingRaises * Add comment to RaiseTrackingFuture * Allow standalone asyncraises * CheckedFuture.fail type checking * First cleanup * Remove useless line * Review comments * nimble: Remove #head from unittest2 * Remove implict raises: CancelledError * Move checkFutureExceptions to asyncfutures2 * Small refacto * small cleanup * Complete in closure finally * cleanup tests, add comment * bump * chronos is not compatible with nim 1.2 anymore * re-add readme modifications * fix special exception handlers * also propagate excetion type in `read` * `RaiseTrackingFuture` -> `InternalRaisesFuture` Use internal naming scheme for RTF (this type should only be accessed via asyncraises) * use `internalError` for error reading * oops * 2.0 workarounds * again * remove try/finally for non-raising functions * Revert "remove try/finally for non-raising functions" This reverts commit 86bfeb5c972ef379a3bd34e4a16cd158a7455721. `finally` is needed if code returns early :/ * fixes * avoid exposing `newInternalRaisesFuture` in manual macro code * avoid unnecessary codegen for `Future[void]` * avoid reduntant block around async proc body * simplify body generation for forward declarations with comment but no body * avoid duplicate `gcsafe` annotiations * line info for return at end of async proc * expand tests * fix comments, add defer test --------- Co-authored-by: Jacek Sieka <jacek@status.im>
2023-10-17 12:18:14 +00:00
```
When calling a callback, it is important to remember that the given function
may raise and exceptions need to be handled.
2023-10-24 14:21:07 +00:00
Checked exceptions can be used to limit the exceptions that a callback can
raise:
2023-10-24 14:21:07 +00:00
```nim
type MyEasyCallback = proc: Future[void] {.async: (raises: []).}
2023-10-24 14:21:07 +00:00
proc runCallback(cb: MyEasyCallback) {.async: (raises: [])} =
await cb()
2023-10-24 14:21:07 +00:00
```
exception tracking (#166) * exception tracking This PR adds minimal exception tracking to chronos, moving the goalpost one step further. In particular, it becomes invalid to raise exceptions from `callSoon` callbacks: this is critical for writing correct error handling because there's no reasonable way that a user of chronos can possibly _reason_ about exceptions coming out of there: the event loop will be in an indeterminite state when the loop is executing an _random_ callback. As expected, there are several issues in the error handling of chronos: in particular, it will end up in an inconsistent internal state whenever the selector loop operations fail, because the internal state update functions are not written in an exception-safe way. This PR turns this into a Defect, which probably is not the optimal way of handling things - expect more work to be done here. Some API have no way of reporting back errors to callers - for example, when something fails in the accept loop, there's not much it can do, and no way to report it back to the user of the API - this has been fixed with the new accept flow - the old one should be deprecated. Finally, there is information loss in the API: in composite operations like `poll` and `waitFor` there's no way to differentiate internal errors from user-level errors originating from callbacks. * store `CatchableError` in future * annotate proc's with correct raises information * `selectors2` to avoid non-CatchableError IOSelectorsException * `$` should never raise * remove unnecessary gcsafe annotations * fix exceptions leaking out of timer waits * fix some imports * functions must signal raising the union of all exceptions across all platforms to enable cross-platform code * switch to unittest2 * add `selectors2` which supercedes the std library version and fixes several exception handling issues in there * fixes * docs, platform-independent eh specifiers for some functions * add feature flag for strict exception mode also bump version to 3.0.0 - _most_ existing code should be compatible with this version of exception handling but some things might need fixing - callbacks, existing raises specifications etc. * fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
### Platform independence
Several functions in `chronos` are backed by the operating system, such as
waiting for network events, creating files and sockets etc. The specific
exceptions that are raised by the OS is platform-dependent, thus such functions
are declared as raising `CatchableError` but will in general raise something
more specific. In particular, it's possible that some functions that are
annotated as raising `CatchableError` only raise on _some_ platforms - in order
to work on all platforms, calling code must assume that they will raise even
when they don't seem to do so on one platform.
### Cancellation support
Any running `Future` can be cancelled. This can be used for timeouts,
to let a user cancel a running task, to start multiple futures in parallel
and cancel them as soon as one finishes, etc.
```nim
import chronos/apps/http/httpclient
proc cancellationExample() {.async.} =
# Simple cancellation
let future = sleepAsync(10.minutes)
future.cancelSoon()
# `cancelSoon` will not wait for the cancellation
# to be finished, so the Future could still be
# pending at this point.
# Wait for cancellation
let future2 = sleepAsync(10.minutes)
await future2.cancelAndWait()
# Using `cancelAndWait`, we know that future2 isn't
# pending anymore. However, it could have completed
# before cancellation happened (in which case, it
# will hold a value)
# Race between futures
proc retrievePage(uri: string): Future[string] {.async.} =
let httpSession = HttpSessionRef.new()
try:
let resp = await httpSession.fetch(parseUri(uri))
return bytesToString(resp.data)
finally:
# be sure to always close the session
# `finally` will run also during cancellation -
# `noCancel` ensures that `closeWait` doesn't get cancelled
await noCancel(httpSession.closeWait())
let
futs =
@[
retrievePage("https://duckduckgo.com/?q=chronos"),
retrievePage("https://www.google.fr/search?q=chronos")
]
let finishedFut = await one(futs)
for fut in futs:
if not fut.finished:
fut.cancelSoon()
echo "Result: ", await finishedFut
waitFor(cancellationExample())
```
Even if cancellation is initiated, it is not guaranteed that
the operation gets cancelled - the future might still be completed
or fail depending on the ordering of events and the specifics of
the operation.
If the future indeed gets cancelled, `await` will raise a
`CancelledError` as is likely to happen in the following example:
```nim
proc c1 {.async.} =
echo "Before sleep"
try:
await sleepAsync(10.minutes)
echo "After sleep" # not reach due to cancellation
except CancelledError as exc:
echo "We got cancelled!"
raise exc
proc c2 {.async.} =
await c1()
echo "Never reached, since the CancelledError got re-raised"
let work = c2()
waitFor(work.cancelAndWait())
```
The `CancelledError` will now travel up the stack like any other exception.
It can be caught and handled (for instance, freeing some resources)
### Multiple async backend support
Thanks to its powerful macro support, Nim allows `async`/`await` to be
implemented in libraries with only minimal support from the language - as such,
multiple `async` libraries exist, including `chronos` and `asyncdispatch`, and
more may come to be developed in the futures.
Libraries built on top of `async`/`await` may wish to support multiple async
backends - the best way to do so is to create separate modules for each backend
that may be imported side-by-side - see [nim-metrics](https://github.com/status-im/nim-metrics/blob/master/metrics/)
for an example.
An alternative way is to select backend using a global compile flag - this
method makes it diffucult to compose applications that use both backends as may
happen with transitive dependencies, but may be appropriate in some cases -
libraries choosing this path should call the flag `asyncBackend`, allowing
applications to choose the backend with `-d:asyncBackend=<backend_name>`.
Known `async` backends include:
* `chronos` - this library (`-d:asyncBackend=chronos`)
* `asyncdispatch` the standard library `asyncdispatch` [module](https://nim-lang.org/docs/asyncdispatch.html) (`-d:asyncBackend=asyncdispatch`)
* `none` - ``-d:asyncBackend=none`` - disable ``async`` support completely
``none`` can be used when a library supports both a synchronous and
asynchronous API, to disable the latter.
### 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.
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.
2018-05-30 03:32:47 +00:00
## TODO
* Pipe/Subprocess Transports.
* Multithreading Stream/Datagram servers
## Contributing
When submitting pull requests, please add test cases for any new features or fixes and make sure `nimble test` is still able to execute the entire test suite successfully.
2018-05-28 23:35:15 +00:00
2021-03-20 07:12:35 +00:00
`chronos` follows the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
## Other resources
* [Historical differences with asyncdispatch](https://github.com/status-im/nim-chronos/wiki/AsyncDispatch-comparison)
2018-09-05 03:36:14 +00:00
## License
2018-09-05 18:45:22 +00:00
Licensed and distributed under either of
* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
2019-01-02 13:53:00 +00:00
or
* Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0)
2018-09-05 03:36:14 +00:00
at your option. These files may not be copied, modified, or distributed except according to those terms.