feat: add tunable polling interval with conservative defaults to async predicate checks

This commit is contained in:
gmega 2025-03-21 17:02:16 -03:00
parent 5154c0d79d
commit fb6110ec05
No known key found for this signature in database
GPG Key ID: E6365B693CB1DF35
3 changed files with 24 additions and 8 deletions

View File

@ -1,14 +1,14 @@
import std/asyncdispatch import std/asyncdispatch
import std/times import std/times
template eventually*(expression: untyped, timeout=5000): bool = template eventually*(expression: untyped, timeout=5000, pollInterval=1000): bool =
proc eventually: Future[bool] {.async.} = proc eventually: Future[bool] {.async.} =
let endTime = getTime() + initDuration(milliseconds=timeout) let endTime = getTime() + initDuration(milliseconds=timeout)
while not expression: while not expression:
if endTime < getTime(): if endTime < getTime():
return false return false
await sleepAsync(10) await sleepAsync(pollInterval)
return true return true
await eventually() await eventually()

View File

@ -10,7 +10,7 @@ template eventuallyProcSignature(body: untyped): untyped =
proc eventually: Future[bool] {.async.} = proc eventually: Future[bool] {.async.} =
body body
template eventually*(expression: untyped, timeout=5000): bool = template eventually*(expression: untyped, timeout=5000, pollInterval=1000): bool =
bind Moment, now, milliseconds bind Moment, now, milliseconds
eventuallyProcSignature: eventuallyProcSignature:
@ -18,7 +18,7 @@ template eventually*(expression: untyped, timeout=5000): bool =
while not expression: while not expression:
if endTime < Moment.now(): if endTime < Moment.now():
return false return false
await sleepAsync(10.milliseconds) await sleepAsync(pollInterval.milliseconds)
return true return true
await eventually() await eventually()

View File

@ -42,13 +42,13 @@ suite "eventually":
inc tries inc tries
tries == 3 tries == 3
check eventually becomesTrue() check eventually(becomesTrue(), pollInterval=10)
test "becomes false after timeout": test "becomes false after timeout":
proc remainsFalse: bool = false proc remainsFalse: bool = false
check not eventually(remainsFalse(), timeout=100) check not eventually(remainsFalse(), timeout=100, pollInterval=10)
test "becomes true during timeout": test "becomes true during timeout":
@ -56,7 +56,7 @@ suite "eventually":
sleep(100) sleep(100)
true true
check eventually(slowTrue(), timeout=50) check eventually(slowTrue(), timeout=50, pollInterval=10)
test "works with async procedures": test "works with async procedures":
@ -70,5 +70,21 @@ suite "eventually":
x = 42 x = 42
let future = slowProcedure() let future = slowProcedure()
check eventually x == 42 check eventually(x == 42, pollInterval=10)
await future await future
test "respects poll interval":
var evaluations: int = 0
# If we try to access this from the closure, it will crash later with
# a segfault, so pass as var.
proc expensivePredicate(counter: var int): bool =
inc counter
return false
check not eventually(evaluations.expensivePredicate(), pollInterval=100, timeout=500)
check 1 <= evaluations and evaluations <= 6
evaluations = 0
check not eventually(evaluations.expensivePredicate(), pollInterval=10, timeout=500)
check 20 <= evaluations and evaluations <= 51