diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 0000000..1668eb0 --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,62 @@ +name: Docgen +on: + push: + branches: + - master + - docs + workflow_dispatch: + +jobs: + build: + timeout-minutes: 20 + + name: 'Generate & upload documentation' + runs-on: 'ubuntu-latest' + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + + - uses: jiro4989/setup-nim-action@v1 + with: + nim-version: '1.6.6' + + - name: Generate doc + run: | + nim --version + nimble --version + nimble install -dy + # nim doc can "fail", but the doc is still generated + nim doc --git.url:https://github.com/status-im/nim-chronos --git.commit:master --outdir:docs --project chronos || true + + # check that the folder exists + ls docs + + - name: Clone the gh-pages branch + uses: actions/checkout@v2 + with: + repository: status-im/nim-chronos + ref: gh-pages + path: subdoc + submodules: true + fetch-depth: 0 + + - name: Commit & push + run: | + cd subdoc + + # Update / create this branch doc + rm -rf docs + mv ../docs . + + # Remove .idx files + # NOTE: git also uses idx files in his + # internal folder, hence the `*` instead of `.` + find * -name "*.idx" -delete + git add . + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + git config --global user.name = "${{ github.actor }}" + git commit -a -m "update docs" + git push origin gh-pages diff --git a/README.md b/README.md index cf7618f..c63d254 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Chronos - An efficient library for asynchronous programming [![Github action](https://github.com/status-im/nim-chronos/workflows/nim-chronos%20CI/badge.svg)](https://github.com/status-im/nim-chronos/actions/workflows/ci.yml) -[![Windows build status (AppVeyor)](https://img.shields.io/appveyor/ci/nimbus/nim-asyncdispatch2/master.svg?label=Windows "Windows build status (Appveyor)")](https://ci.appveyor.com/project/nimbus/nim-asyncdispatch2) [![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) ![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg) @@ -22,7 +21,7 @@ Chronos is an efficient [async/await](https://en.wikipedia.org/wiki/Async/await) You can use Nim's official package manager Nimble to install Chronos: ```text -nimble install https://github.com/status-im/nim-chronos.git +nimble install chronos ``` or add a dependency to your `.nimble` file: @@ -245,6 +244,68 @@ In the strict mode, `async` functions are checked such that they only raise effects on forward declarations, callbacks and methods using `{.raises: [CatchableError].}` (or more strict) annotations. +### Cancellation support + +Any running `Future` can be cancelled. This can be used to launch multiple +futures, and wait for one of them to finish, and cancel the rest of them, +to add timeout, or to let the user cancel a running task. + +```nim +# Simple cancellation +let future = sleepAsync(10.minutes) +future.cancel() + +# Wait for cancellation +let future2 = sleepAsync(10.minutes) +await future2.cancelAndWait() + +# Race between futures +proc retrievePage(uri: string): Future[string] {.async.} = + # requires to import uri, chronos/apps/http/httpclient, stew/byteutils + let httpSession = HttpSessionRef.new() + try: + resp = await httpSession.fetch(parseUri(uri)) + result = string.fromBytes(resp.data) + finally: + # be sure to always close the session + await 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.cancel() +echo "Result: ", await finishedFut +``` + +When an `await` is cancelled, it will raise a `CancelledError`: +```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 diff --git a/chronos/asyncloop.nim b/chronos/asyncloop.nim index 95227ed..13a71bf 100644 --- a/chronos/asyncloop.nim +++ b/chronos/asyncloop.nim @@ -22,7 +22,7 @@ export timer #{.injectStmt: newGcInvariant().} -## AsyncDispatch +## Chronos ## ************* ## ## This module implements asynchronous IO. This includes a dispatcher, @@ -112,57 +112,33 @@ export timer ## ``await socket.send("foobar")``. ## ## If an awaited future completes with an error, then ``await`` will re-raise -## this error. To avoid this, you can use the ``yield`` keyword instead of -## ``await``. The following section shows different ways that you can handle -## exceptions in async procs. +## this error. ## ## Handling Exceptions -## ~~~~~~~~~~~~~~~~~~~ +## ------------------- ## -## The most reliable way to handle exceptions is to use ``yield`` on a future -## then check the future's ``failed`` property. For example: -## -## .. code-block:: Nim -## var future = sock.recv(100) -## yield future -## if future.failed: -## # Handle exception -## -## The ``async`` procedures also offer limited support for the try statement. +## The ``async`` procedures also offer support for the try statement. ## ## .. code-block:: Nim ## try: ## let data = await sock.recv(100) ## echo("Received ", data) -## except: -## # Handle exception -## -## Unfortunately the semantics of the try statement may not always be correct, -## and occasionally the compilation may fail altogether. -## As such it is better to use the former style when possible. -## +## except CancelledError as exc: +## # Handle exc ## ## 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 -## use the ``asyncCheck`` procedure instead of the ``discard`` keyword. -## -## Examples -## -------- -## -## For examples take a look at the documentation for the modules implementing -## asynchronous IO. A good place to start is the -## `asyncnet module `_. +## use the ``asyncSpawn`` procedure instead of the ``discard`` keyword. +## ``asyncSpawn`` will transform any exception thrown by the called procedure +## to a Defect ## ## Limitations/Bugs ## ---------------- ## ## * The effect system (``raises: []``) does not work with async procedures. -## * Can't await in a ``except`` body -## * Forward declarations for async procs are broken, -## link includes workaround: https://github.com/nim-lang/Nim/issues/3182. # TODO: Check if yielded future is nil and throw a more meaningful exception