2018-05-16 08:22:34 +00:00
|
|
|
#
|
2019-02-06 14:49:11 +00:00
|
|
|
# Chronos
|
2018-05-16 08:22:34 +00:00
|
|
|
#
|
2019-02-06 14:49:11 +00:00
|
|
|
# (c) Copyright 2015 Dominik Picheta
|
|
|
|
# (c) Copyright 2018-Present Status Research & Development GmbH
|
2018-05-16 08:22:34 +00:00
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
|
|
|
|
2023-06-05 20:21:50 +00:00
|
|
|
{.push raises: [].}
|
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
|
|
|
|
2023-02-16 20:27:31 +00:00
|
|
|
## Chronos
|
2018-05-16 08:22:34 +00:00
|
|
|
## *************
|
|
|
|
##
|
|
|
|
## This module implements asynchronous IO. This includes a dispatcher,
|
|
|
|
## a ``Future`` type implementation, and an ``async`` macro which allows
|
|
|
|
## asynchronous code to be written in a synchronous style with the ``await``
|
|
|
|
## keyword.
|
|
|
|
##
|
|
|
|
## The dispatcher acts as a kind of event loop. You must call ``poll`` on it
|
|
|
|
## (or a function which does so for you such as ``waitFor`` or ``runForever``)
|
|
|
|
## in order to poll for any outstanding events. The underlying implementation
|
|
|
|
## is based on epoll on Linux, IO Completion Ports on Windows and select on
|
|
|
|
## other operating systems.
|
|
|
|
##
|
|
|
|
## The ``poll`` function will not, on its own, return any events. Instead
|
|
|
|
## an appropriate ``Future`` object will be completed. A ``Future`` is a
|
|
|
|
## type which holds a value which is not yet available, but which *may* be
|
|
|
|
## available in the future. You can check whether a future is finished
|
|
|
|
## by using the ``finished`` function. When a future is finished it means that
|
|
|
|
## either the value that it holds is now available or it holds an error instead.
|
|
|
|
## The latter situation occurs when the operation to complete a future fails
|
|
|
|
## with an exception. You can distinguish between the two situations with the
|
|
|
|
## ``failed`` function.
|
|
|
|
##
|
|
|
|
## Future objects can also store a callback procedure which will be called
|
|
|
|
## automatically once the future completes.
|
|
|
|
##
|
|
|
|
## Futures therefore can be thought of as an implementation of the proactor
|
|
|
|
## pattern. In this
|
|
|
|
## pattern you make a request for an action, and once that action is fulfilled
|
|
|
|
## a future is completed with the result of that action. Requests can be
|
|
|
|
## made by calling the appropriate functions. For example: calling the ``recv``
|
|
|
|
## function will create a request for some data to be read from a socket. The
|
|
|
|
## future which the ``recv`` function returns will then complete once the
|
|
|
|
## requested amount of data is read **or** an exception occurs.
|
|
|
|
##
|
|
|
|
## Code to read some data from a socket may look something like this:
|
|
|
|
##
|
|
|
|
## .. code-block::nim
|
|
|
|
## var future = socket.recv(100)
|
|
|
|
## future.addCallback(
|
|
|
|
## proc () =
|
|
|
|
## echo(future.read)
|
|
|
|
## )
|
|
|
|
##
|
|
|
|
## All asynchronous functions returning a ``Future`` will not block. They
|
|
|
|
## will not however return immediately. An asynchronous function will have
|
|
|
|
## code which will be executed before an asynchronous request is made, in most
|
|
|
|
## cases this code sets up the request.
|
|
|
|
##
|
|
|
|
## In the above example, the ``recv`` function will return a brand new
|
|
|
|
## ``Future`` instance once the request for data to be read from the socket
|
|
|
|
## is made. This ``Future`` instance will complete once the requested amount
|
|
|
|
## of data is read, in this case it is 100 bytes. The second line sets a
|
|
|
|
## callback on this future which will be called once the future completes.
|
|
|
|
## All the callback does is write the data stored in the future to ``stdout``.
|
|
|
|
## The ``read`` function is used for this and it checks whether the future
|
|
|
|
## completes with an error for you (if it did it will simply raise the
|
|
|
|
## error), if there is no error however it returns the value of the future.
|
|
|
|
##
|
|
|
|
## Asynchronous procedures
|
|
|
|
## -----------------------
|
|
|
|
##
|
|
|
|
## Asynchronous procedures remove the pain of working with callbacks. They do
|
|
|
|
## this by allowing you to write asynchronous code the same way as you would
|
|
|
|
## write synchronous code.
|
|
|
|
##
|
|
|
|
## An asynchronous procedure is marked using the ``{.async.}`` pragma.
|
|
|
|
## When marking a procedure with the ``{.async.}`` pragma it must have a
|
|
|
|
## ``Future[T]`` return type or no return type at all. If you do not specify
|
|
|
|
## a return type then ``Future[void]`` is assumed.
|
|
|
|
##
|
|
|
|
## Inside asynchronous procedures ``await`` can be used to call any
|
|
|
|
## procedures which return a
|
|
|
|
## ``Future``; this includes asynchronous procedures. When a procedure is
|
|
|
|
## "awaited", the asynchronous procedure it is awaited in will
|
|
|
|
## suspend its execution
|
|
|
|
## until the awaited procedure's Future completes. At which point the
|
|
|
|
## asynchronous procedure will resume its execution. During the period
|
|
|
|
## when an asynchronous procedure is suspended other asynchronous procedures
|
|
|
|
## will be run by the dispatcher.
|
|
|
|
##
|
|
|
|
## The ``await`` call may be used in many contexts. It can be used on the right
|
|
|
|
## hand side of a variable declaration: ``var data = await socket.recv(100)``,
|
|
|
|
## in which case the variable will be set to the value of the future
|
|
|
|
## automatically. It can be used to await a ``Future`` object, and it can
|
|
|
|
## be used to await a procedure returning a ``Future[void]``:
|
|
|
|
## ``await socket.send("foobar")``.
|
|
|
|
##
|
|
|
|
## If an awaited future completes with an error, then ``await`` will re-raise
|
2023-02-16 20:27:31 +00:00
|
|
|
## this error.
|
2018-05-16 08:22:34 +00:00
|
|
|
##
|
|
|
|
## Handling Exceptions
|
2023-02-16 20:27:31 +00:00
|
|
|
## -------------------
|
2018-05-16 08:22:34 +00:00
|
|
|
##
|
2023-02-16 20:27:31 +00:00
|
|
|
## The ``async`` procedures also offer support for the try statement.
|
2018-05-16 08:22:34 +00:00
|
|
|
##
|
|
|
|
## .. code-block:: Nim
|
|
|
|
## try:
|
|
|
|
## let data = await sock.recv(100)
|
|
|
|
## echo("Received ", data)
|
2023-02-16 20:27:31 +00:00
|
|
|
## except CancelledError as exc:
|
|
|
|
## # Handle exc
|
2018-05-16 08:22:34 +00:00
|
|
|
##
|
|
|
|
## Discarding futures
|
|
|
|
## ------------------
|
|
|
|
##
|
|
|
|
## Futures should **never** be discarded. This is because they may contain
|
|
|
|
## errors. If you do not care for the result of a Future then you should
|
2023-02-16 20:27:31 +00:00
|
|
|
## use the ``asyncSpawn`` procedure instead of the ``discard`` keyword.
|
|
|
|
## ``asyncSpawn`` will transform any exception thrown by the called procedure
|
|
|
|
## to a Defect
|
2018-05-16 08:22:34 +00:00
|
|
|
##
|
|
|
|
## Limitations/Bugs
|
|
|
|
## ----------------
|
|
|
|
##
|
|
|
|
## * The effect system (``raises: []``) does not work with async procedures.
|
|
|
|
|
2023-10-17 18:25:25 +00:00
|
|
|
import ./internal/[asyncengine, asyncfutures, asyncmacro, errors]
|
2023-05-23 10:39:35 +00:00
|
|
|
|
2023-10-17 18:25:25 +00:00
|
|
|
export asyncfutures, asyncengine, errors
|
|
|
|
export asyncmacro.async, asyncmacro.await, asyncmacro.awaitne, asyncraises
|