1;;! component_model_async = true
2
3;; This test starts a host subtask that never returns which takes a borrow.
4;;
5;; When cancelling that subtask it should correctly yield the borrow back to the
6;; guest and allow the guest to destroy the resource.
7(component
8  (import "host" (instance $host
9    (export "resource1" (type $r (sub resource)))
10    (export "[constructor]resource1" (func (param "r" u32) (result (own $r))))
11    (export "[method]resource1.never-return" (func async (param "self" (borrow $r))))
12  ))
13
14  (core module $m
15    (import "" "f" (func $f (param i32) (result i32)))
16    (import "" "new" (func $new (param i32) (result i32)))
17    (import "" "cancel" (func $cancel (param i32) (result i32)))
18    (import "" "drop-subtask" (func $drop-subtask (param i32)))
19    (import "" "drop-resource" (func $drop-resource (param i32)))
20
21    (func (export "run")
22      (local $handle i32)
23      (local $subtask i32)
24
25      ;; Create an owned resource
26      (call $new (i32.const 100))
27      local.set $handle
28
29      ;; Call async function with a borrow of the resource.
30      ;; This returns STARTED (1) | (subtask_id << 4).
31      (call $f (local.get $handle))
32      local.tee $subtask
33
34      ;; Check status is STARTED (lower 4 bits = 1)
35      i32.const 0xf
36      i32.and
37      i32.const 1 ;; STARTED
38      i32.ne
39      if unreachable end
40
41      ;; Extract subtask id
42      local.get $subtask
43      i32.const 4
44      i32.shr_u
45      local.set $subtask
46
47      ;; Cancel the subtask — should release the borrow
48      (call $cancel (local.get $subtask))
49      i32.const 4 ;; RETURN_CANCELLED
50      i32.ne
51      if unreachable end
52
53      ;; Drop the subtask
54      (call $drop-subtask (local.get $subtask))
55
56      ;; Drop the owned resource
57      (call $drop-resource (local.get $handle))
58    )
59  )
60  (alias export $host "resource1" (type $r))
61  (core func $f (canon lower (func $host "[method]resource1.never-return") async))
62  (core func $new (canon lower (func $host "[constructor]resource1")))
63  (core func $cancel (canon subtask.cancel))
64  (core func $drop-subtask (canon subtask.drop))
65  (core func $drop-resource (canon resource.drop $r))
66  (core instance $i (instantiate $m
67      (with "" (instance
68          (export "f" (func $f))
69          (export "new" (func $new))
70          (export "cancel" (func $cancel))
71          (export "drop-subtask" (func $drop-subtask))
72          (export "drop-resource" (func $drop-resource))
73      ))
74  ))
75
76  (func (export "f") async
77      (canon lift (core func $i "run")))
78)
79
80(assert_return (invoke "f"))
81
82;; This test starts two subtasks and waits for one to complete. Cancelling the
83;; second one should then work correctly. Historically this triggered a panic
84;; in Wasmtime.
85(component
86  (import "host" (instance $host
87    (export "return-two-slowly" (func async (result s32)))
88  ))
89
90  (core module $Mem (memory (export "mem") 1))
91  (core instance $mem (instantiate $Mem))
92
93  (core module $m
94    (import "" "slow" (func $slow (param i32) (result i32)))
95    (import "" "subtask.cancel" (func $subtask.cancel (param i32) (result i32)))
96    (import "" "subtask.drop" (func $subtask.drop (param i32)))
97    (import "" "waitable.join" (func $waitable.join (param i32 i32)))
98    (import "" "waitable-set.new" (func $waitable-set.new (result i32)))
99    (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
100    (import "" "waitable-set.drop" (func $waitable-set.drop (param i32)))
101    (func (export "run")
102      (local $s1 i32) (local $s2 i32) (local $ws i32) (local $tmp i32)
103
104      ;; start `slow` twice
105      (local.set $s1 (call $start-slow))
106      (local.set $s2 (call $start-slow))
107
108      ;; Wait for slow to complete via waitable-set
109      (local.set $ws (call $waitable-set.new))
110      (call $waitable.join (local.get $s1) (local.get $ws))
111      (drop (call $waitable-set.wait (local.get $ws) (i32.const 104)))
112
113      ;; first task returned, and if the second task is cancelled then nothing
114      ;; bad should happen...
115      ;;
116      ;; Note that this cancellation may indicate that the host task returned,
117      ;; or it may return it was cancelled, that's up to the host.
118      (call $subtask.cancel (local.get $s2))
119      drop
120
121      (call $subtask.drop (local.get $s2))
122      (call $subtask.drop (local.get $s1))
123
124      ;; Clean up the waitable-set.
125      (call $waitable-set.drop (local.get $ws))
126    )
127
128    (func $start-slow (result i32)
129      (local $tmp i32)
130
131      ;; Start slow, expect STARTED
132      (call $slow (i32.const 100))
133      local.tee $tmp
134      i32.const 0xf
135      i32.and
136      i32.const 1 ;; STARTED
137      i32.ne
138      if unreachable end
139      local.get $tmp
140      i32.const 4
141      i32.shr_u
142    )
143  )
144  (core func $slow (canon lower (func $host "return-two-slowly") async (memory $mem "mem")))
145  (core func $subtask.cancel (canon subtask.cancel))
146  (core func $subtask.drop (canon subtask.drop))
147  (core func $waitable-set.new (canon waitable-set.new))
148  (core func $waitable.join (canon waitable.join))
149  (core func $waitable-set.wait (canon waitable-set.wait (memory $mem "mem")))
150  (core func $waitable-set.drop (canon waitable-set.drop))
151  (core instance $i (instantiate $m
152    (with "" (instance
153      (export "slow" (func $slow))
154      (export "subtask.cancel" (func $subtask.cancel))
155      (export "subtask.drop" (func $subtask.drop))
156      (export "waitable.join" (func $waitable.join))
157      (export "waitable-set.new" (func $waitable-set.new))
158      (export "waitable-set.wait" (func $waitable-set.wait))
159      (export "waitable-set.drop" (func $waitable-set.drop))
160    ))
161  ))
162
163  (func (export "run") async
164    (canon lift (core func $i "run")))
165)
166
167(assert_return (invoke "run"))
168
169
170;; Similar to the above test, but asserts that `subtask.cancel` can't be called
171;; twice on the same host task.
172(component
173  (import "host" (instance $host
174    (export "return-two-slowly" (func async (result s32)))
175  ))
176
177  (core module $Mem (memory (export "mem") 1))
178  (core instance $mem (instantiate $Mem))
179
180  (core module $m
181    (import "" "slow" (func $slow (param i32) (result i32)))
182    (import "" "subtask.cancel" (func $subtask.cancel (param i32) (result i32)))
183    (import "" "subtask.drop" (func $subtask.drop (param i32)))
184    (import "" "waitable.join" (func $waitable.join (param i32 i32)))
185    (import "" "waitable-set.new" (func $waitable-set.new (result i32)))
186    (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
187    (import "" "thread.yield" (func $thread.yield (result i32)))
188    (func (export "run")
189      (local $s1 i32) (local $s2 i32) (local $ws i32) (local $tmp i32)
190
191      ;; start `slow` twice
192      (local.set $s1 (call $start-slow))
193      (local.set $s2 (call $start-slow))
194
195      ;; Wait for slow to complete via waitable-set
196      (local.set $ws (call $waitable-set.new))
197      (call $waitable.join (local.get $s1) (local.get $ws))
198      (drop (call $waitable-set.wait (local.get $ws) (i32.const 104)))
199
200      ;; first task returned, and if the second task is cancelled then nothing
201      ;; bad should happen...
202      ;;
203      ;; Note that this cancellation may indicate that the host task returned,
204      ;; or it may return it was cancelled, that's up to the host.
205      (call $subtask.cancel (local.get $s2))
206      drop
207
208      ;; let the host do something else for a moment
209      (drop (call $thread.yield))
210
211      ;; calling cancel again on this task should trap since we already received
212      ;; a terminal status code from above.
213      (call $subtask.cancel (local.get $s2))
214      unreachable
215    )
216
217    (func $start-slow (result i32)
218      (local $tmp i32)
219
220      ;; Start slow, expect STARTED
221      (call $slow (i32.const 100))
222      local.tee $tmp
223      i32.const 0xf
224      i32.and
225      i32.const 1 ;; STARTED
226      i32.ne
227      if unreachable end
228      local.get $tmp
229      i32.const 4
230      i32.shr_u
231    )
232  )
233  (core func $slow (canon lower (func $host "return-two-slowly") async (memory $mem "mem")))
234  (core func $subtask.cancel (canon subtask.cancel))
235  (core func $subtask.drop (canon subtask.drop))
236  (core func $waitable-set.new (canon waitable-set.new))
237  (core func $waitable.join (canon waitable.join))
238  (core func $waitable-set.wait (canon waitable-set.wait (memory $mem "mem")))
239  (core func $thread.yield (canon thread.yield))
240  (core instance $i (instantiate $m
241    (with "" (instance
242      (export "slow" (func $slow))
243      (export "subtask.cancel" (func $subtask.cancel))
244      (export "subtask.drop" (func $subtask.drop))
245      (export "waitable.join" (func $waitable.join))
246      (export "waitable-set.new" (func $waitable-set.new))
247      (export "waitable-set.wait" (func $waitable-set.wait))
248      (export "thread.yield" (func $thread.yield))
249    ))
250  ))
251
252  (func (export "run") async
253    (canon lift (core func $i "run")))
254)
255
256(assert_trap (invoke "run") "`subtask.cancel` called after terminal status delivered")
257
258;; This test covers a historical bug in Wasmtime where cancelled host tasks
259;; could keep running in a sort of zombie state which would clobber other tasks.
260;;
261;; Here two tasks are started, the first completes, the second is cancelled,
262;; another is started/waited on. It's then asserted that the cancelled
263;; task's side effects are not visible.
264(component
265  (import "host" (instance $host
266    (export "echo-slowly" (func async (param "val" u32) (result u32)))
267  ))
268
269  (core module $Mem (memory (export "mem") 1))
270  (core instance $mem (instantiate $Mem))
271
272  (core module $m
273    (import "" "mem" (memory 1))
274    ;; echo: (val, retptr) → status|handle
275    (import "" "echo" (func $echo (param i32 i32) (result i32)))
276    (import "" "subtask.cancel" (func $subtask.cancel (param i32) (result i32)))
277    (import "" "subtask.drop" (func $subtask.drop (param i32)))
278    (import "" "waitable.join" (func $waitable.join (param i32 i32)))
279    (import "" "waitable-set.new" (func $waitable-set.new (result i32)))
280    (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
281    (import "" "waitable-set.drop" (func $waitable-set.drop (param i32)))
282
283    (func (export "run")
284      (local $e0 i32) (local $e111 i32) (local $e222 i32)
285      (local $e111-returned i32)
286
287      ;; Start echo(0,retptr=0) first
288      (local.set $e0 (call $start-echo (i32.const 0) (i32.const 0)))
289
290      ;; Start echo(111,retptr=100)
291      (local.set $e111 (call $start-echo (i32.const 111) (i32.const 100)))
292
293      ;; wait for $e0 to complete
294      (call $wait-for (local.get $e0))
295
296      ;; Cancel/drop echo(111)
297      (local.set $e111-returned
298        (i32.ne
299          (call $subtask.cancel (local.get $e111))
300          (i32.const 4) ;; RETURN_CANCELLED=4
301        ))
302      (call $subtask.drop (local.get $e111))
303
304      ;; Start echo(222,retptr=200)
305      (local.set $e222 (call $start-echo (i32.const 222) (i32.const 200)))
306
307      ;; Wait for echo(222).
308      (call $wait-for (local.get $e222))
309
310      ;; retptr=100: should be 0 or 111 depending on if it returned
311      local.get $e111-returned
312      if
313        (call $assert-eq (i32.load (i32.const 100)) (i32.const 111))
314      else
315        (call $assert-eq (i32.load (i32.const 100)) (i32.const 0))
316      end
317      ;; retptr=200: should be 222.
318      (call $assert-eq (i32.load (i32.const 200)) (i32.const 222))
319
320      ;; Cleanup.
321      (call $subtask.drop (local.get $e222))
322      (call $subtask.drop (local.get $e0))
323    )
324
325    (func $assert-eq (param i32 i32)
326      (local.get 0)
327      (local.get 1)
328      i32.ne
329      if unreachable end
330    )
331
332    ;; start a call to `echo(local.get 0, local.get 1)`
333    (func $start-echo (param i32 i32) (result i32)
334      (local $tmp i32)
335      (call $echo (local.get 0) (local.get 1))
336      local.set $tmp
337      (call $assert-eq
338        (i32.and (local.get $tmp) (i32.const 0xf))
339        (i32.const 0x1))
340      (i32.shr_u (local.get $tmp) (i32.const 4))
341    )
342
343    ;; wait for the waitable identified by local 0
344    (func $wait-for (param i32)
345      (local $ws i32)
346
347      (local.set $ws (call $waitable-set.new))
348      (call $waitable.join (local.get 0) (local.get $ws))
349      (call $assert-eq
350        (call $waitable-set.wait (local.get $ws) (i32.const 500))
351        (i32.const 1) ;; EVENT_SUBTASK
352      )
353      (call $waitable.join (local.get 0) (i32.const 0))
354
355      (call $assert-eq
356        (i32.load (i32.const 500))
357        (local.get 0))
358
359      (call $waitable-set.drop (local.get $ws))
360    )
361  )
362  (core func $echo (canon lower (func $host "echo-slowly") async (memory $mem "mem")))
363  (core func $subtask.cancel (canon subtask.cancel))
364  (core func $subtask.drop (canon subtask.drop))
365  (core func $waitable-set.new (canon waitable-set.new))
366  (core func $waitable.join (canon waitable.join))
367  (core func $waitable-set.wait (canon waitable-set.wait (memory $mem "mem")))
368  (core func $waitable-set.drop (canon waitable-set.drop))
369  (core instance $i (instantiate $m
370    (with "" (instance
371      (export "mem" (memory $mem "mem"))
372      (export "echo" (func $echo))
373      (export "subtask.cancel" (func $subtask.cancel))
374      (export "subtask.drop" (func $subtask.drop))
375      (export "waitable.join" (func $waitable.join))
376      (export "waitable-set.new" (func $waitable-set.new))
377      (export "waitable-set.wait" (func $waitable-set.wait))
378      (export "waitable-set.drop" (func $waitable-set.drop))
379    ))
380  ))
381
382  (func (export "run") async (canon lift (core func $i "run")))
383)
384
385(assert_return (invoke "run"))
386
387;; If a host task completes, but the guest doesn't actually receive the
388;; notification that it's done, it should be possible to cancel it.
389(component
390  (import "host" (instance $host
391    (export "return-two-slowly" (func async (result s32)))
392  ))
393
394  (core module $Mem (memory (export "mem") 1))
395  (core instance $mem (instantiate $Mem))
396
397  (core module $m
398    (import "" "slow-sync" (func $slow-sync (result i32)))
399    (import "" "slow-async" (func $slow-async (param i32) (result i32)))
400    (import "" "subtask.cancel" (func $subtask.cancel (param i32) (result i32)))
401    (import "" "subtask.drop" (func $subtask.drop (param i32)))
402    (func (export "run")
403      (local $subtask i32)
404
405      ;; start a subtask for `return-two-slowly`
406      (local.set $subtask (call $start-slow))
407
408      ;; Call `return-two-slowly` synchronously a few times. This gives a
409      ;; chance for the host to finish the previous task, which shouldn't
410      ;; interfere with the below...
411      (call $assert-eq (call $slow-sync) (i32.const 2))
412      (call $assert-eq (call $slow-sync) (i32.const 2))
413      (call $assert-eq (call $slow-sync) (i32.const 2))
414
415      ;; We've never seen the result of `$subtask`, so it should be safe to
416      ;; cancel. This should either yield that the subtask returned or that
417      ;; it's return was cancelled, it's up to hosts.
418      (call $assert-either (call $subtask.cancel (local.get $subtask))
419                           (i32.const 4)  ;; RETURN_CANCELLED
420                           (i32.const 2)) ;; RETURNED
421      (call $subtask.drop (local.get $subtask))
422    )
423
424    ;; asserts param 0 == param 1
425    (func $assert-eq (param i32 i32)
426      local.get 0
427      local.get 1
428      i32.ne
429      if unreachable end)
430
431    ;; Asserts that param 0 is either equal to param 1 or param 2
432    (func $assert-either (param i32 i32 i32)
433      local.get 0
434      local.get 1
435      i32.eq
436      if return end
437      local.get 0
438      local.get 2
439      i32.eq
440      if return end
441      unreachable)
442
443    (func $start-slow (result i32)
444      (local $tmp i32)
445
446      ;; Start slow, expect STARTED
447      (call $slow-async (i32.const 100))
448      local.tee $tmp
449      i32.const 0xf
450      i32.and
451      i32.const 1 ;; STARTED
452      i32.ne
453      if unreachable end
454      local.get $tmp
455      i32.const 4
456      i32.shr_u
457    )
458  )
459  (core func $slow-sync (canon lower (func $host "return-two-slowly") (memory $mem "mem")))
460  (core func $slow-async (canon lower (func $host "return-two-slowly") async (memory $mem "mem")))
461  (core func $subtask.cancel (canon subtask.cancel))
462  (core func $subtask.drop (canon subtask.drop))
463  (core instance $i (instantiate $m
464    (with "" (instance
465      (export "slow-sync" (func $slow-sync))
466      (export "slow-async" (func $slow-async))
467      (export "subtask.cancel" (func $subtask.cancel))
468      (export "subtask.drop" (func $subtask.drop))
469    ))
470  ))
471
472  (func (export "run") async
473    (canon lift (core func $i "run")))
474)
475
476(assert_return (invoke "run"))
477