Skip to content

Commit 110bcf8

Browse files
committed
feat: first throw at async sleep interface
1 parent 70f2a97 commit 110bcf8

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

src/Std/Internal.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Henrik Böving
55
-/
66
prelude
7+
-- TODO: after PR is merged
8+
--import Std.Internal.Async
79
import Std.Internal.Parsec
810
import Std.Internal.UV
911

src/Std/Internal/Async.lean

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/-
2+
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Henrik Böving
5+
-/
6+
prelude
7+
import Std.Time
8+
import Std.Internal.UV
9+
10+
11+
namespace Std
12+
namespace Internal
13+
namespace IO
14+
namespace Async
15+
16+
/--
17+
A `Task` that may resolve to a value or an `IO.Error`.
18+
-/
19+
def AsyncTask (α : Type u) : Type u := Task (Except IO.Error α)
20+
21+
namespace AsyncTask
22+
23+
@[inline]
24+
protected def pure (x : α) : AsyncTask α := Task.pure <| .ok x
25+
26+
instance : Pure AsyncTask where
27+
pure := AsyncTask.pure
28+
29+
@[inline]
30+
protected def bind (x : AsyncTask α) (f : α → AsyncTask β) : AsyncTask β :=
31+
Task.bind x fun r =>
32+
match r with
33+
| .ok a => f a
34+
| .error e => Task.pure <| .error e
35+
36+
-- TODO: variants with explicit error handling
37+
38+
@[inline]
39+
def bindIO (x : AsyncTask α) (f : α → IO (AsyncTask β)) : BaseIO (AsyncTask β) :=
40+
IO.bindTask x fun r =>
41+
match r with
42+
| .ok a => f a
43+
| .error e => .error e
44+
45+
@[inline]
46+
def mapIO (f : α → β) (x : AsyncTask α) : BaseIO (AsyncTask β) :=
47+
IO.mapTask (t := x) fun r =>
48+
match r with
49+
| .ok a => pure (f a)
50+
| .error e => .error e
51+
52+
/--
53+
Run the `AsyncTask` in `x` and block until it finishes.
54+
-/
55+
def spawnBlocking (x : IO (AsyncTask α)) : IO α := do
56+
let t ← x
57+
let res := t.get
58+
match res with
59+
| .ok a => return a
60+
| .error e => .error e
61+
62+
@[inline]
63+
def spawn (x : IO α) : BaseIO (AsyncTask α) := do
64+
IO.asTask x
65+
66+
@[inline]
67+
def ofPromise (x : IO.Promise α) : AsyncTask α :=
68+
x.result.map pure
69+
70+
@[inline]
71+
def getState (x : AsyncTask α) : BaseIO IO.TaskState :=
72+
IO.getTaskState x
73+
74+
end AsyncTask
75+
76+
/--
77+
`Sleep` can be used to sleep for some duration once.
78+
The underlying timer has millisecond resolution.
79+
-/
80+
structure Sleep where
81+
private ofNative ::
82+
native : Internal.UV.Timer
83+
84+
-- TODO: provable constraints on duration after changes in Std.Time
85+
86+
namespace Sleep
87+
88+
/--
89+
Set up a `Sleep` that waits for `duration` milliseconds.
90+
This function only initializes but does not yet start the underlying timer.
91+
-/
92+
def mk (duration : Std.Time.Millisecond.Offset) : IO Sleep := do
93+
let native ← Internal.UV.Timer.mk duration.toInt.toNat.toUInt64 false
94+
return ofNative native
95+
96+
/--
97+
Start the underlying timer of `s` and return an `AsyncTask` that will resolve once the previously
98+
configured duration has run out. Running this function twice returns the same `AsyncTask`.
99+
-/
100+
def wait (s : Sleep) : IO (AsyncTask Unit) := do
101+
let promise ← s.native.next
102+
return .ofPromise promise
103+
104+
/--
105+
If:
106+
- `Sleep.wait` was previously called on `s` this makes the timer wait for `duration` counting from
107+
the call of this function.
108+
- `Sleep.wait` was never called on `s` before this is a no-op.
109+
-/
110+
def reset (s : Sleep) : IO Unit :=
111+
s.native.reset
112+
113+
end Sleep
114+
115+
/--
116+
Return an `AsyncTask` that resolves after `duration`.
117+
-/
118+
def sleep (duration : Std.Time.Millisecond.Offset) : IO (AsyncTask Unit) := do
119+
let sleeper ← Sleep.mk duration
120+
sleeper.wait
121+
122+
/--
123+
`Interval` can be used to repeatedly wait for some duration like a clock.
124+
The underlying timer has millisecond resolution.
125+
-/
126+
structure Interval where
127+
private ofNative ::
128+
native : Internal.UV.Timer
129+
130+
131+
namespace Interval
132+
133+
/--
134+
Setup up an `Interval` that waits for `duration` milliseconds.
135+
This function only initializes but does not yet start the underlying timer.
136+
-/
137+
def mk (duration : Std.Time.Millisecond.Offset) : IO Interval := do
138+
let native ← Internal.UV.Timer.mk duration.toInt.toNat.toUInt64 true
139+
return ofNative native
140+
141+
/--
142+
Start the underlying timer of `s` and return an `AsyncTask` that will resolve upon the next tick
143+
of `i`. In particular:
144+
- calling this function for the first time starts the underlying timer of `i` and returns an
145+
`AsyncTask` that instantly resolves as the 0th multiple of `duration` has elapsed.
146+
- calling this function while the tick from the last call has not yet finished returns the same
147+
`AsyncTask` as the last call.
148+
- calling this function when the tick from the last call has finished returns a new `AsyncTask`
149+
that waits for the closest next tick from the time of calling this function.
150+
-/
151+
def tick (i : Interval) : IO (AsyncTask Unit) := do
152+
let promise ← i.native.next
153+
return .ofPromise promise
154+
155+
/--
156+
If:
157+
- `Interval.tick` was called on `i` before the next internal timer restarts counting from now and
158+
the next tick happens in `duration`.
159+
- `Interval.tick` was never called on `i` before this is a no-op.
160+
-/
161+
def reset (i : Interval) : IO Unit :=
162+
i.native.reset
163+
164+
end Interval
165+
166+
end Async
167+
end IO
168+
end Internal
169+
end Std

0 commit comments

Comments
 (0)