Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: create raceAndCancelPending proc #1175

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pinned
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bearssl;https://github.com/status-im/nim-bearssl@#667b40440a53a58e9f922e29e20818720c62d9ac
chronicles;https://github.com/status-im/nim-chronicles@#32ac8679680ea699f7dbc046e8e0131cac97d41a
chronos;https://github.com/status-im/nim-chronos@#dc3847e4d6733dfc3811454c2a9c384b87343e26
chronos;https://github.com/status-im/nim-chronos@#ef93a15bf9eb22b3fcc15aab3edaf3f8ae9618a7
dnsclient;https://github.com/ba0f3/dnsclient.nim@#23214235d4784d24aceed99bbfe153379ea557c8
faststreams;https://github.com/status-im/nim-faststreams@#720fc5e5c8e428d9d0af618e1e27c44b42350309
httputils;https://github.com/status-im/nim-http-utils@#3b491a40c60aad9e8d3407443f46f62511e63b18
Expand Down
17 changes: 17 additions & 0 deletions libp2p/utils/future.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

{.push raises: [].}

import sequtils
import chronos

type AllFuturesFailedError* = object of CatchableError
Expand All @@ -31,3 +32,19 @@ proc anyCompleted*[T](futs: seq[Future[T]]): Future[Future[T]] {.async.} =

let index = requests.find(raceFut)
requests.del(index)

proc raceAndCancelPending*(
futs: seq[SomeFuture]
): Future[void] {.async: (raises: [ValueError, CancelledError]).} =
## Executes a race between the provided sequence of futures.
## Cancels any remaining futures that have not yet completed.
##
## - `futs`: A sequence of futures to race.
##
## Raises:
## - `ValueError` if the sequence of futures is empty.
## - `CancelledError` if the operation is canceled.
try:
discard await race(futs)
finally:
await noCancel allFutures(futs.filterIt(not it.finished).mapIt(it.cancelAndWait))
57 changes: 57 additions & 0 deletions tests/utils/testfuture.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{.used.}

# Nim-Libp2p
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

import ../helpers
import ../../libp2p/utils/future

suite "Utils Future":
asyncTest "All Pending Tasks are canceled when returned future is canceled":
proc longRunningTaskA() {.async.} =
await sleepAsync(10.seconds)

proc longRunningTaskB() {.async.} =
await sleepAsync(10.seconds)

let
futureA = longRunningTaskA()
futureB = longRunningTaskB()

# Both futures should be canceled when raceCancel is called
await raceAndCancelPending(@[futureA, futureB]).cancelAndWait()
check futureA.cancelled
check futureB.cancelled

# Test where one task finishes immediately, leading to the cancellation of the pending task
asyncTest "Cancel Pending Tasks When One Completes":
proc quickTask() {.async.} =
return

proc slowTask() {.async.} =
await sleepAsync(10.seconds)

let
futureQuick = quickTask()
futureSlow = slowTask()

# The quick task finishes, so the slow task should be canceled
await raceAndCancelPending(@[futureQuick, futureSlow])
check futureQuick.finished
check futureSlow.cancelled

asyncTest "raceAndCancelPending with AsyncEvent":
let asyncEvent = newAsyncEvent()
let fut1 = asyncEvent.wait()
let fut2 = newAsyncEvent().wait()
asyncEvent.fire()
await raceAndCancelPending(@[fut1, fut2])

check fut1.finished
check fut2.cancelled
Loading