lean4-htt/tests/lean/run/async_sleep.lean
Henrik Böving e6a643770f
feat: implement basic async IO with timers (#6505)
This PR implements a basic async framework as well as asynchronously
running timers using libuv.

---------

Co-authored-by: Sofia Rodrigues <sofia@algebraic.dev>
Co-authored-by: Markus Himmel <markus@himmel-villmar.de>
Co-authored-by: Markus Himmel <markus@lean-fro.org>
2025-01-13 18:11:04 +00:00

219 lines
6.6 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Std.Internal.UV
open Std.Internal.UV
def assertElapsed (t1 t2 : Nat) (should : Nat) (eps : Nat) : IO Unit := do
let dur := t2 - t1
if (Int.ofNat dur - Int.ofNat should).natAbs > eps then
throw <| .userError s!"elapsed time was too different, measured {dur}, should: {should}, tolerance: {eps}"
def assertDuration (should : Nat) (eps : Nat) (x : IO α) : IO α := do
let t1 ← IO.monoMsNow
let res ← x
let t2 ← IO.monoMsNow
assertElapsed t1 t2 should eps
return res
def BASE_DURATION : Nat := 1000
-- generous tolerance for slow CI systems
def EPS : Nat := 150
def await (x : Task α) : IO α := pure x.get
namespace SleepTest
def oneShotSleep : IO Unit := do
assertDuration BASE_DURATION EPS do
let timer ← Timer.mk BASE_DURATION.toUInt64 false
let p ← timer.next
await p.result
def promiseBehavior1 : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 false
let p ← timer.next
let r := p.result
assert! (← IO.getTaskState r) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState r) == .finished
def promiseBehavior2 : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 false
let p1 ← timer.next
let p2 ← timer.next
assert! (← IO.getTaskState p1.result) != .finished
assert! (← IO.getTaskState p2.result) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState p1.result) == .finished
assert! (← IO.getTaskState p2.result) == .finished
def promiseBehavior3 : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 false
let p1 ← timer.next
assert! (← IO.getTaskState p1.result) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState p1.result) == .finished
let p3 ← timer.next
assert! (← IO.getTaskState p3.result) == .finished
def resetBehavior : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 false
let p ← timer.next
assert! (← IO.getTaskState p.result) != .finished
IO.sleep (BASE_DURATION / 2).toUInt32
assert! (← IO.getTaskState p.result) != .finished
timer.reset
IO.sleep (BASE_DURATION / 2).toUInt32
assert! (← IO.getTaskState p.result) != .finished
IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
assert! (← IO.getTaskState p.result) == .finished
#eval oneShotSleep
#eval promiseBehavior1
#eval promiseBehavior2
#eval promiseBehavior3
#eval resetBehavior
#eval oneShotSleep
end SleepTest
namespace IntervalTest
def sleepFirst : IO Unit := do
assertDuration 0 EPS go
where
go : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let prom ← timer.next
await prom.result
timer.stop
def sleepSecond : IO Unit := do
discard <| assertDuration BASE_DURATION EPS go
where
go : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let task ←
IO.bindTask (← timer.next).result fun _ => do
IO.bindTask (← timer.next).result fun _ => pure (Task.pure (.ok 2))
discard <| await task
timer.stop
def promiseBehavior1 : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let p1 ← timer.next
IO.sleep EPS.toUInt32
assert! (← IO.getTaskState p1.result) == .finished
let p2 ← timer.next
assert! (← IO.getTaskState p2.result) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState p2.result) == .finished
timer.stop
def promiseBehavior2 : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let p1 ← timer.next
IO.sleep EPS.toUInt32
assert! (← IO.getTaskState p1.result) == .finished
let prom1 ← timer.next
let prom2 ← timer.next
assert! (← IO.getTaskState prom1.result) != .finished
assert! (← IO.getTaskState prom2.result) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState prom1.result) == .finished
assert! (← IO.getTaskState prom2.result) == .finished
timer.stop
def promiseBehavior3 : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let p1 ← timer.next
IO.sleep EPS.toUInt32
assert! (← IO.getTaskState p1.result) == .finished
let prom1 ← timer.next
assert! (← IO.getTaskState prom1.result) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState prom1.result) == .finished
let prom2 ← timer.next
assert! (← IO.getTaskState prom2.result) != .finished
IO.sleep (BASE_DURATION + EPS).toUInt32
assert! (← IO.getTaskState prom2.result) == .finished
timer.stop
def delayedTickBehavior : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let p1 ← timer.next
IO.sleep EPS.toUInt32
assert! (← IO.getTaskState p1.result) == .finished
IO.sleep (BASE_DURATION / 2).toUInt32
let p2 ← timer.next
assert! (← IO.getTaskState p2.result) != .finished
IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
assert! (← IO.getTaskState p2.result) == .finished
timer.stop
def skippedTickBehavior : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let p1 ← timer.next
IO.sleep EPS.toUInt32
assert! (← IO.getTaskState p1.result) == .finished
IO.sleep (2 * BASE_DURATION + BASE_DURATION / 2).toUInt32
let p2 ← timer.next
assert! (← IO.getTaskState p2.result) != .finished
IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
assert! (← IO.getTaskState p2.result) == .finished
timer.stop
def resetBehavior : IO Unit := do
let timer ← Timer.mk BASE_DURATION.toUInt64 true
let p1 ← timer.next
IO.sleep EPS.toUInt32
assert! (← IO.getTaskState p1.result) == .finished
let prom ← timer.next
assert! (← IO.getTaskState prom.result) != .finished
IO.sleep (BASE_DURATION / 2).toUInt32
assert! (← IO.getTaskState prom.result) != .finished
timer.reset
IO.sleep (BASE_DURATION / 2).toUInt32
assert! (← IO.getTaskState prom.result) != .finished
IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
assert! (← IO.getTaskState prom.result) == .finished
timer.stop
def sequentialSleep : IO Unit := do
discard <| assertDuration BASE_DURATION EPS go
where
go : IO Unit := do
let timer ← Timer.mk (BASE_DURATION / 2).toUInt64 true
-- 0th interval ticks instantly
let task ←
IO.bindTask (← timer.next).result fun _ => do
IO.bindTask (← timer.next).result fun _ => do
IO.bindTask (← timer.next).result fun _ => pure (Task.pure (.ok 2))
discard <| await task
timer.stop
#eval sleepFirst
#eval sleepSecond
#eval promiseBehavior1
#eval promiseBehavior2
#eval promiseBehavior3
#eval delayedTickBehavior
#eval skippedTickBehavior
#eval resetBehavior
#eval sequentialSleep
end IntervalTest