Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 28f15ac

Browse files
dicejlukewagner
andcommitted
trap on subtask.drop of unresolved subtask
Fixes #165 Co-authored-by: Luke Wagner <mail@lukewagner.name> Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 8f7decf commit 28f15ac

2 files changed

Lines changed: 164 additions & 2 deletions

File tree

crates/wasmtime/src/runtime/component/concurrent.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2986,21 +2986,35 @@ impl ComponentInstance {
29862986
self.waitable_join(caller_instance, task_id, 0)?;
29872987

29882988
let (rep, state) = self.waitable_tables()[caller_instance].remove_by_index(task_id)?;
2989+
29892990
let (waitable, expected_caller_instance) = match state {
29902991
WaitableState::HostTask => {
29912992
let id = TableId::<HostTask>::new(rep);
2992-
(Waitable::Host(id), self.get(id)?.caller_instance)
2993+
let task = self.get(id)?;
2994+
if task.abort_handle.is_some() {
2995+
bail!("cannot drop a subtask which has not yet resolved");
2996+
}
2997+
(Waitable::Host(id), task.caller_instance)
29932998
}
29942999
WaitableState::GuestTask => {
29953000
let id = TableId::<GuestTask>::new(rep);
2996-
if let Caller::Guest { instance, .. } = &self.get(id)?.caller {
3001+
let task = self.get(id)?;
3002+
if task.lift_result.is_some() {
3003+
bail!("cannot drop a subtask which has not yet resolved");
3004+
}
3005+
if let Caller::Guest { instance, .. } = &task.caller {
29973006
(Waitable::Guest(id), *instance)
29983007
} else {
29993008
unreachable!()
30003009
}
30013010
}
30023011
_ => bail!("invalid task handle: {task_id}"),
30033012
};
3013+
3014+
if waitable.take_event(self)?.is_some() {
3015+
bail!("cannot drop a subtask with an undelivered event");
3016+
}
3017+
30043018
// Since waitables can neither be passed between instances nor forged,
30053019
// this should never fail unless there's a bug in Wasmtime, but we check
30063020
// here to be sure:
@@ -3050,6 +3064,9 @@ impl ComponentInstance {
30503064
let guest_task = TableId::<GuestTask>::new(rep);
30513065
let task = self.get_mut(guest_task)?;
30523066
if task.lower_params.is_some() {
3067+
task.lower_params = None;
3068+
task.lift_result = None;
3069+
30533070
// Not yet started; cancel and remove from pending
30543071
let callee_instance = task.instance;
30553072

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
;;! component_model_async = true
2+
3+
;; This test contains two components: $Looper and $Caller.
4+
;; $Caller starts an async subtask for $Looper.loop and then drops these
5+
;; subtasks in both allowed and disallowed cases, testing for success and
6+
;; traps.
7+
;;
8+
;; (Copied from
9+
;; https://github.com/WebAssembly/component-model/blob/add-tests/test/concurrency/drop-subtask.wast)
10+
(component
11+
(component $Looper
12+
(core module $Memory (memory (export "mem") 1))
13+
(core instance $memory (instantiate $Memory))
14+
(core module $CoreLooper
15+
(import "" "mem" (memory 1))
16+
(import "" "task.return" (func $task.return))
17+
18+
(global $done (mut i32) (i32.const 0))
19+
20+
(func $loop (export "loop") (result i32)
21+
(i32.const 1 (; YIELD ;))
22+
)
23+
(func $loop_cb (export "loop_cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32)
24+
;; confirm that we've received a cancellation request
25+
(if (i32.ne (local.get $event_code) (i32.const 0 (; NONE ;)))
26+
(then unreachable))
27+
(if (i32.ne (local.get $index) (i32.const 0))
28+
(then unreachable))
29+
(if (i32.ne (local.get $payload) (i32.const 0))
30+
(then unreachable))
31+
32+
(if (i32.eqz (global.get $done))
33+
(then (return (i32.const 1 (; YIELD ;)))))
34+
(call $task.return)
35+
(i32.const 0 (; EXIT ;))
36+
)
37+
38+
(func $return (export "return")
39+
(global.set $done (i32.const 1))
40+
)
41+
)
42+
(canon task.return (core func $task.return))
43+
(core instance $core_looper (instantiate $CoreLooper (with "" (instance
44+
(export "mem" (memory $memory "mem"))
45+
(export "task.return" (func $task.return))
46+
))))
47+
(func (export "loop") (canon lift
48+
(core func $core_looper "loop")
49+
async (callback (func $core_looper "loop_cb"))
50+
))
51+
(func (export "return") (canon lift
52+
(core func $core_looper "return")
53+
))
54+
)
55+
56+
(component $Caller
57+
(import "looper" (instance $looper
58+
(export "loop" (func))
59+
(export "return" (func))
60+
))
61+
62+
(core module $Memory (memory (export "mem") 1))
63+
(core instance $memory (instantiate $Memory))
64+
(core module $CoreCaller
65+
(import "" "mem" (memory 1))
66+
(import "" "subtask.drop" (func $subtask.drop (param i32)))
67+
(import "" "waitable.join" (func $waitable.join (param i32 i32)))
68+
(import "" "waitable-set.new" (func $waitable-set.new (result i32)))
69+
(import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
70+
(import "" "loop" (func $loop (param i32 i32) (result i32)))
71+
(import "" "return" (func $return))
72+
73+
(func $drop-after-return (export "drop-after-return") (result i32)
74+
(local $ret i32) (local $ws i32) (local $subtask i32)
75+
76+
;; start 'loop'
77+
(local.set $ret (call $loop (i32.const 0xdead) (i32.const 0xbeef)))
78+
(if (i32.ne (i32.const 1 (; STARTED ;)) (i32.and (local.get $ret) (i32.const 0xf)))
79+
(then unreachable))
80+
(local.set $subtask (i32.shr_u (local.get $ret) (i32.const 4)))
81+
82+
;; tell 'loop' to stop
83+
(call $return)
84+
85+
;; wait for 'loop' to run and return
86+
(local.set $ws (call $waitable-set.new))
87+
(call $waitable.join (local.get $subtask) (local.get $ws))
88+
(local.set $ret (call $waitable-set.wait (local.get $ws) (i32.const 0)))
89+
(if (i32.ne (i32.const 1 (; SUBTASK ;)) (local.get $ret))
90+
(then unreachable))
91+
(if (i32.ne (local.get $subtask) (i32.load (i32.const 0)))
92+
(then unreachable))
93+
(if (i32.ne (i32.const 2 (; RETURNED ;)) (i32.load (i32.const 4)))
94+
(then unreachable))
95+
96+
;; ok to drop
97+
(call $subtask.drop (local.get $subtask))
98+
(i32.const 42)
99+
)
100+
101+
(func $drop-before-return (export "drop-before-return") (result i32)
102+
(local $ret i32) (local $subtask i32)
103+
104+
;; start 'loop'
105+
(local.set $ret (call $loop (i32.const 0xdead) (i32.const 0xbeef)))
106+
(if (i32.ne (i32.const 1 (; STARTED ;)) (i32.and (local.get $ret) (i32.const 0xf)))
107+
(then unreachable))
108+
(local.set $subtask (i32.shr_u (local.get $ret) (i32.const 4)))
109+
110+
;; this should trap
111+
(call $subtask.drop (local.get $subtask))
112+
unreachable
113+
)
114+
)
115+
(canon subtask.drop (core func $subtask.drop))
116+
(canon waitable.join (core func $waitable.join))
117+
(canon waitable-set.new (core func $waitable-set.new))
118+
(canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait))
119+
(canon lower (func $looper "loop") async (memory $memory "mem") (core func $loop'))
120+
(canon lower (func $looper "return") (memory $memory "mem") (core func $return'))
121+
(core instance $core_caller (instantiate $CoreCaller (with "" (instance
122+
(export "mem" (memory $memory "mem"))
123+
(export "subtask.drop" (func $subtask.drop))
124+
(export "waitable.join" (func $waitable.join))
125+
(export "waitable-set.new" (func $waitable-set.new))
126+
(export "waitable-set.wait" (func $waitable-set.wait))
127+
(export "loop" (func $loop'))
128+
(export "return" (func $return'))
129+
))))
130+
(func (export "drop-after-return") (result u32) (canon lift
131+
(core func $core_caller "drop-after-return")
132+
))
133+
(func (export "drop-before-return") (result u32) (canon lift
134+
(core func $core_caller "drop-before-return")
135+
))
136+
)
137+
138+
(instance $looper (instantiate $Looper))
139+
(instance $caller1 (instantiate $Caller (with "looper" (instance $looper))))
140+
(instance $caller2 (instantiate $Caller (with "looper" (instance $looper))))
141+
(func (export "drop-after-return") (alias export $caller1 "drop-after-return"))
142+
(func (export "drop-before-return") (alias export $caller2 "drop-before-return"))
143+
)
144+
(assert_return (invoke "drop-after-return") (u32.const 42))
145+
(assert_trap (invoke "drop-before-return") "cannot drop a subtask which has not yet resolved")

0 commit comments

Comments
 (0)