From 12dc36cfeee3ac487aef1278c9b324cc082dcfeb Mon Sep 17 00:00:00 2001 From: Tanguy Date: Wed, 25 Oct 2023 15:16:10 +0200 Subject: [PATCH] Update README regarding cancellation (#450) * Update README regarding cancellation * Apply suggestions from code review Co-authored-by: Eugene Kabanov --------- Co-authored-by: Jacek Sieka Co-authored-by: Eugene Kabanov --- README.md | 81 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 0a23ea1..495f9f8 100644 --- a/README.md +++ b/README.md @@ -301,45 +301,64 @@ effects on forward declarations, callbacks and methods using ### 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. +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 -# Simple cancellation -let future = sleepAsync(10.minutes) -future.cancel() +import chronos/apps/http/httpclient -# Wait for cancellation -let future2 = sleepAsync(10.minutes) -await future2.cancelAndWait() +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. -# 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() + # 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) -let - futs = - @[ - retrievePage("https://duckduckgo.com/?q=chronos"), - retrievePage("https://www.google.fr/search?q=chronos") - ] + # 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 finishedFut = await one(futs) -for fut in futs: - if not fut.finished: - fut.cancel() -echo "Result: ", await finishedFut + 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()) ``` -When an `await` is cancelled, it will raise a `CancelledError`: +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"