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