Skip to content

Commit 923bd42

Browse files
committed
Add some wast tests for async
1 parent 935c478 commit 923bd42

9 files changed

Lines changed: 1329 additions & 0 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
;; This test contains two components $C and $D that test cancelling reads
2+
;; and writes in the presence and absence of partial reads/writes.
3+
;;
4+
;; $C exports a function 'start-stream' that creates and holds onto a writable
5+
;; stream in the global $sw as well as various operations that operate on $sw.
6+
;; $D calls $C.start-stream to get the readable end and then drives the test.
7+
(component
8+
(component $C
9+
(core module $Memory (memory (export "mem") 1))
10+
(core instance $memory (instantiate $Memory))
11+
(core module $CM
12+
(import "" "mem" (memory 1))
13+
(import "" "task.return" (func $task.return (param i32)))
14+
(import "" "stream.new" (func $stream.new (result i64)))
15+
(import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))
16+
(import "" "stream.cancel-write" (func $stream.cancel-write (param i32) (result i32)))
17+
(import "" "stream.close-writable" (func $stream.close-writable (param i32)))
18+
19+
(global $sw (mut i32) (i32.const 0))
20+
21+
(func $start-stream (export "start-stream") (result i32)
22+
;; create a new stream, return the readable end to the caller
23+
(local $ret64 i64)
24+
(local.set $ret64 (call $stream.new))
25+
(global.set $sw (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32))))
26+
(i32.wrap_i64 (local.get $ret64))
27+
)
28+
29+
(func $write4 (export "write4")
30+
;; write 6 bytes into the stream, expecting to rendezvous with a stream.read
31+
(local $ret i32)
32+
(i32.store (i32.const 8) (i32.const 0xabcd))
33+
(local.set $ret (call $stream.write (global.get $sw) (i32.const 8) (i32.const 4)))
34+
(if (i32.ne (i32.const 0x40 (; COMPLETED=0 | (4<<4) ;)) (local.get $ret))
35+
(then unreachable))
36+
)
37+
38+
(func $write4-and-close (export "write4-and-close")
39+
(call $write4)
40+
(call $stream.close-writable (global.get $sw))
41+
)
42+
43+
(func $start-blocking-write (export "start-blocking-write")
44+
(local $ret i32)
45+
46+
;; prepare the write buffer
47+
(i64.store (i32.const 8) (i64.const 0x123456789abcdef))
48+
49+
;; start one blocking write and immediately cancel it
50+
(local.set $ret (call $stream.write (global.get $sw) (i32.const 8) (i32.const 8)))
51+
(if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret))
52+
(then unreachable))
53+
(local.set $ret (call $stream.cancel-write (global.get $sw)))
54+
(if (i32.ne (i32.const 0x2 (; CANCELLED ;)) (local.get $ret))
55+
(then unreachable))
56+
57+
;; start a second blockign write and leave it pending
58+
(local.set $ret (call $stream.write (global.get $sw) (i32.const 8) (i32.const 8)))
59+
(if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret))
60+
(then unreachable))
61+
)
62+
63+
(func $cancel-after-read4 (export "cancel-after-read4")
64+
(local $ret i32)
65+
(local.set $ret (call $stream.cancel-write (global.get $sw)))
66+
(; TODO: currently returns 0x40 ;)
67+
(if (i32.ne (i32.const 0x42 (; CANCELLED=2 | (4<<4) ;)) (local.get $ret))
68+
(then unreachable))
69+
)
70+
)
71+
(type $ST (stream u8))
72+
(canon task.return (result u32) (core func $task.return))
73+
(canon stream.new $ST (core func $stream.new))
74+
(canon stream.write $ST async (memory $memory "mem") (core func $stream.write))
75+
(canon stream.cancel-write $ST (core func $stream.cancel-write))
76+
(canon stream.close-writable $ST (core func $stream.close-writable))
77+
(core instance $cm (instantiate $CM (with "" (instance
78+
(export "mem" (memory $memory "mem"))
79+
(export "task.return" (func $task.return))
80+
(export "stream.new" (func $stream.new))
81+
(export "stream.write" (func $stream.write))
82+
(export "stream.cancel-write" (func $stream.cancel-write))
83+
(export "stream.close-writable" (func $stream.close-writable))
84+
))))
85+
(func (export "start-stream") (result (stream u8)) (canon lift (core func $cm "start-stream")))
86+
(func (export "write4") (canon lift (core func $cm "write4")))
87+
(func (export "write4-and-close") (canon lift (core func $cm "write4-and-close")))
88+
(func (export "start-blocking-write") (canon lift (core func $cm "start-blocking-write")))
89+
(func (export "cancel-after-read4") (canon lift (core func $cm "cancel-after-read4")))
90+
)
91+
92+
(component $D
93+
(import "c" (instance $c
94+
(export "start-stream" (func (result (stream u8))))
95+
(export "write4" (func))
96+
(export "write4-and-close" (func))
97+
(export "start-blocking-write" (func))
98+
(export "cancel-after-read4" (func))
99+
))
100+
101+
(core module $Memory (memory (export "mem") 1))
102+
(core instance $memory (instantiate $Memory))
103+
(core module $DM
104+
(import "" "mem" (memory 1))
105+
(import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
106+
(import "" "stream.cancel-read" (func $stream.cancel-read (param i32) (result i32)))
107+
(import "" "stream.close-readable" (func $stream.close-readable (param i32)))
108+
(import "" "start-stream" (func $start-stream (result i32)))
109+
(import "" "write4" (func $write4))
110+
(import "" "write4-and-close" (func $write4-and-close))
111+
(import "" "start-blocking-write" (func $start-blocking-write))
112+
(import "" "cancel-after-read4" (func $cancel-after-read4))
113+
114+
(func $run (export "run") (result i32)
115+
(local $ret i32)
116+
(local $sr i32)
117+
118+
;; call 'start-stream' to get the stream we'll be working with
119+
(local.set $sr (call $start-stream))
120+
(if (i32.ne (i32.const 1) (local.get $sr))
121+
(then unreachable))
122+
123+
;; start read that will block
124+
(local.set $ret (call $stream.read (local.get $sr) (i32.const 8) (i32.const 100)))
125+
(if (i32.ne (i32.const -1 (; BLOCKED;)) (local.get $ret))
126+
(then unreachable))
127+
128+
;; cancelling it will finish without anything having been written
129+
(local.set $ret (call $stream.cancel-read (local.get $sr)))
130+
(if (i32.ne (i32.const 0x2 (; CANCELLED ;)) (local.get $ret))
131+
(then unreachable))
132+
133+
;; read, block, call $C to write 4 bytes into the buffer,
134+
;; then cancel, which should show "4+cancelled"
135+
(local.set $ret (call $stream.read (local.get $sr) (i32.const 8) (i32.const 100)))
136+
(if (i32.ne (i32.const -1 (; BLOCKED;)) (local.get $ret))
137+
(then unreachable))
138+
(call $write4)
139+
(local.set $ret (call $stream.cancel-read (local.get $sr)))
140+
(; TODO: currently returns 0x40 ;)
141+
(if (i32.ne (i32.const 0x42 (; CANCELLED=2 | (4<<4) ;)) (local.get $ret))
142+
(then unreachable))
143+
(if (i32.ne (i32.const 0xabcd) (i32.load (i32.const 8)))
144+
(then unreachable))
145+
146+
;; read, block, call $C to write 4 bytes into the buffer and close,
147+
;; then cancel, which should show "4+closed"
148+
(local.set $ret (call $stream.read (local.get $sr) (i32.const 8) (i32.const 100)))
149+
(if (i32.ne (i32.const -1 (; BLOCKED;)) (local.get $ret))
150+
(then unreachable))
151+
(call $write4-and-close)
152+
(local.set $ret (call $stream.cancel-read (local.get $sr)))
153+
(; TODO: currently returns 0x40 ;)
154+
(if (i32.ne (i32.const 0x41 (; CLOSED=1 | (4<<4) ;)) (local.get $ret))
155+
(then unreachable))
156+
(if (i32.ne (i32.const 0xabcd) (i32.load (i32.const 8)))
157+
(then unreachable))
158+
(call $stream.close-readable (local.get $sr))
159+
160+
;; get a new $sr
161+
(local.set $sr (call $start-stream))
162+
(if (i32.ne (i32.const 1) (local.get $sr))
163+
(then unreachable))
164+
165+
;; start outstanding write in $C, read 4 of it, then call back into $C
166+
;; which will cancel and see 4 written.
167+
(call $start-blocking-write)
168+
(local.set $ret (call $stream.read (local.get $sr) (i32.const 8) (i32.const 4)))
169+
(if (i32.ne (i32.const 0x40 (; COMPLETED=0 | (4<<4) ;)) (local.get $ret))
170+
(then unreachable))
171+
(if (i32.ne (i32.const 0x89abcdef) (i32.load (i32.const 8)))
172+
(then unreachable))
173+
(call $cancel-after-read4)
174+
175+
;; return 42 to the top-level assert_return
176+
(i32.const 42)
177+
)
178+
)
179+
(type $ST (stream u8))
180+
(canon stream.read $ST async (memory $memory "mem") (core func $stream.read))
181+
(canon stream.cancel-read $ST (core func $stream.cancel-read))
182+
(canon stream.close-readable $ST (core func $stream.close-readable))
183+
(canon lower (func $c "start-stream") (core func $start-stream'))
184+
(canon lower (func $c "write4") (core func $write4'))
185+
(canon lower (func $c "write4-and-close") (core func $write4-and-close'))
186+
(canon lower (func $c "start-blocking-write") (core func $start-blocking-write'))
187+
(canon lower (func $c "cancel-after-read4") (core func $cancel-after-read4'))
188+
(core instance $dm (instantiate $DM (with "" (instance
189+
(export "mem" (memory $memory "mem"))
190+
(export "stream.read" (func $stream.read))
191+
(export "stream.cancel-read" (func $stream.cancel-read))
192+
(export "stream.close-readable" (func $stream.close-readable))
193+
(export "start-stream" (func $start-stream'))
194+
(export "write4" (func $write4'))
195+
(export "write4-and-close" (func $write4-and-close'))
196+
(export "start-blocking-write" (func $start-blocking-write'))
197+
(export "cancel-after-read4" (func $cancel-after-read4'))
198+
))))
199+
(func (export "run") (result u32) (canon lift (core func $dm "run")))
200+
)
201+
202+
(instance $c (instantiate $C))
203+
(instance $d (instantiate $D (with "c" (instance $c))))
204+
(func (export "run") (alias export $d "run"))
205+
)
206+
(assert_return (invoke "run") (u32.const 42))
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
;; This test contains two components $C and $D where $D imports and calls $C.
2+
;; $D.run calls $C.f, which blocks on an empty waitable set
3+
;; $D.run then subtask.cancels $C.f, which resumes $C.f which promptly resolves
4+
;; without returning a value.
5+
(component
6+
(component $C
7+
(core module $Memory (memory (export "mem") 1))
8+
(core instance $memory (instantiate $Memory))
9+
(core module $CM
10+
(import "" "mem" (memory 1))
11+
(import "" "task.cancel" (func $task.cancel))
12+
(import "" "waitable.join" (func $waitable.join (param i32 i32)))
13+
(import "" "waitable-set.new" (func $waitable-set.new (result i32)))
14+
(import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
15+
16+
;; $ws is waited on by 'f'
17+
(global $ws (mut i32) (i32.const 0))
18+
(func $start (global.set $ws (call $waitable-set.new)))
19+
(start $start)
20+
21+
(func $f (export "f") (result i32)
22+
;; wait on $ws which is currently empty, expected to get cancelled
23+
(i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4)))
24+
)
25+
(func $f_cb (export "f_cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32)
26+
;; confirm that we've received a cancellation request
27+
(if (i32.ne (local.get $event_code) (i32.const 6 (; TASK_CANCELLED ;)))
28+
(then unreachable))
29+
(if (i32.ne (local.get $index) (i32.const 0))
30+
(then unreachable))
31+
(if (i32.ne (local.get $payload) (i32.const 0))
32+
(then unreachable))
33+
34+
;; finish without returning a value
35+
(call $task.cancel)
36+
(i32.const 0 (; EXIT ;))
37+
)
38+
)
39+
(canon task.cancel (core func $task.cancel))
40+
(canon waitable.join (core func $waitable.join))
41+
(canon waitable-set.new (core func $waitable-set.new))
42+
(canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait))
43+
(core instance $cm (instantiate $CM (with "" (instance
44+
(export "mem" (memory $memory "mem"))
45+
(export "task.cancel" (func $task.cancel))
46+
(export "waitable.join" (func $waitable.join))
47+
(export "waitable-set.new" (func $waitable-set.new))
48+
(export "waitable-set.wait" (func $waitable-set.wait))
49+
))))
50+
(func (export "f") (result u32) (canon lift
51+
(core func $cm "f")
52+
async (callback (func $cm "f_cb"))
53+
))
54+
)
55+
56+
(component $D
57+
(import "f" (func $f (result u32)))
58+
59+
(core module $Memory (memory (export "mem") 1))
60+
(core instance $memory (instantiate $Memory))
61+
(core module $DM
62+
(import "" "mem" (memory 1))
63+
(import "" "subtask.cancel" (func $subtask.cancel (param i32) (result i32)))
64+
(import "" "subtask.drop" (func $subtask.drop (param i32)))
65+
(import "" "f" (func $f (param i32 i32) (result i32)))
66+
67+
(func $run (export "run") (result i32)
68+
(local $ret i32) (local $retp i32)
69+
(local $subtask i32)
70+
(local $event_code i32)
71+
72+
;; call 'f'; it should block
73+
(local.set $retp (i32.const 4))
74+
(i32.store (local.get $retp) (i32.const 0xbad0bad0))
75+
(local.set $ret (call $f (i32.const 0xdeadbeef) (local.get $retp)))
76+
(if (i32.ne (i32.const 1 (; STARTED ;)) (i32.and (local.get $ret) (i32.const 0xf)))
77+
(then unreachable))
78+
(local.set $subtask (i32.shr_u (local.get $ret) (i32.const 4)))
79+
80+
;; cancel 'f'; it should complete without blocking
81+
(local.set $ret (call $subtask.cancel (local.get $subtask)))
82+
(if (i32.ne (i32.const 4 (; CANCELLED_BEFORE_RETURNED ;)) (local.get $ret))
83+
(then unreachable))
84+
85+
;; The $retp memory shouldn't have changed
86+
(if (i32.ne (i32.load (local.get $retp)) (i32.const 0xbad0bad0))
87+
(then unreachable))
88+
89+
(call $subtask.drop (local.get $subtask))
90+
91+
;; return to the top-level assert_return
92+
(i32.const 42)
93+
)
94+
)
95+
(canon subtask.cancel (core func $subtask.cancel))
96+
(canon subtask.drop (core func $subtask.drop))
97+
(canon lower (func $f) async (memory $memory "mem") (core func $f'))
98+
(core instance $dm (instantiate $DM (with "" (instance
99+
(export "mem" (memory $memory "mem"))
100+
(export "subtask.cancel" (func $subtask.cancel))
101+
(export "subtask.drop" (func $subtask.drop))
102+
(export "f" (func $f'))
103+
))))
104+
(func (export "run") (result u32) (canon lift (core func $dm "run")))
105+
)
106+
107+
(instance $c (instantiate $C))
108+
(instance $d (instantiate $D (with "f" (func $c "f"))))
109+
(func (export "run") (alias export $d "run"))
110+
)
111+
(assert_return (invoke "run") (u32.const 42))

test/concurrency/deadlock.wast

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
;; This test defines components $C and $D where $D imports and calls $C
2+
;; $C.f waits on an empty waitable set
3+
;; $D.g calls $C.f and then waits for it to finish, which fails due to deadlock
4+
(component
5+
(component $C
6+
(core module $Memory (memory (export "mem") 1))
7+
(core instance $memory (instantiate $Memory))
8+
(core module $CM
9+
(import "" "mem" (memory 1))
10+
(import "" "waitable-set.new" (func $waitable-set.new (result i32)))
11+
12+
(func (export "f") (result i32)
13+
;; wait on a new empty waitable set
14+
(local $ws i32)
15+
(local.set $ws (call $waitable-set.new))
16+
(i32.or (i32.const 2 (; WAIT ;)) (i32.shl (local.get $ws) (i32.const 4)))
17+
)
18+
(func (export "cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32)
19+
unreachable
20+
)
21+
)
22+
(canon waitable-set.new (core func $waitable-set.new))
23+
(core instance $cm (instantiate $CM (with "" (instance
24+
(export "mem" (memory $memory "mem"))
25+
(export "waitable-set.new" (func $waitable-set.new))
26+
))))
27+
(func (export "f") (result u32) (canon lift
28+
(core func $cm "f")
29+
async (memory $memory "mem") (callback (func $cm "cb"))
30+
))
31+
)
32+
33+
(component $D
34+
(import "f" (func $f (result u32)))
35+
36+
(core module $Memory (memory (export "mem") 1))
37+
(core instance $memory (instantiate $Memory))
38+
(core module $DM
39+
(import "" "mem" (memory 1))
40+
(import "" "waitable.join" (func $waitable.join (param i32 i32)))
41+
(import "" "waitable-set.new" (func $waitable-set.new (result i32)))
42+
(import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
43+
(import "" "f" (func $f (param i32 i32) (result i32)))
44+
45+
(func (export "g") (result i32)
46+
(local $ws i32) (local $ret i32) (local $subtaski i32)
47+
(local.set $ret (call $f (i32.const 0) (i32.const 0)))
48+
(local.set $subtaski (i32.shr_u (local.get $ret) (i32.const 4)))
49+
(local.set $ws (call $waitable-set.new))
50+
(call $waitable.join (local.get $subtaski) (local.get $ws))
51+
(call $waitable-set.wait (local.get $ws) (i32.const 0))
52+
unreachable
53+
)
54+
)
55+
(canon waitable.join (core func $waitable.join))
56+
(canon waitable-set.new (core func $waitable-set.new))
57+
(canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait))
58+
(canon lower (func $f) async (memory $memory "mem") (core func $f'))
59+
(core instance $dm (instantiate $DM (with "" (instance
60+
(export "mem" (memory $memory "mem"))
61+
(export "waitable.join" (func $waitable.join))
62+
(export "waitable-set.new" (func $waitable-set.new))
63+
(export "waitable-set.wait" (func $waitable-set.wait))
64+
(export "f" (func $f'))
65+
))))
66+
(func (export "f") (result u32) (canon lift (core func $dm "g")))
67+
)
68+
69+
(instance $c (instantiate $C))
70+
(instance $d (instantiate $D (with "f" (func $c "f"))))
71+
(func (export "f") (alias export $d "f"))
72+
)
73+
(assert_trap (invoke "f") "wasm trap: deadlock detected: event loop cannot make further progress")

0 commit comments

Comments
 (0)