From 0fe3a7701db952bd1ff679f05bf3b6e1d7547141 Mon Sep 17 00:00:00 2001 From: FrancoisLD Date: Thu, 29 Oct 2026 23:16:55 -0400 Subject: [PATCH 1/2] test async exports that borrow an imported resource Add an async world export that takes a `borrow` of an imported resource, and a host test that calls it. The Canonical ABI requires every borrow lent to a task to be released before `task.return`, so this currently traps with "borrow handles still remain at the end of the call". --- src/test/python_source/app.py | 3 +++ src/test/tests.rs | 25 +++++++++++++++++++++++++ src/test/wit/tests.wit | 3 +++ 3 files changed, 31 insertions(+) diff --git a/src/test/python_source/app.py b/src/test/python_source/app.py index 6d4a74f..7161873 100644 --- a/src/test/python_source/app.py +++ b/src/test/python_source/app.py @@ -253,6 +253,9 @@ class Tests(tests.WorldExports): def test_resource_borrow_import(self, v: int) -> int: return resource_borrow_import.foo(resource_borrow_import.Thing(v + 1)) + 4 + async def test_resource_borrow_import_async(self, t: resource_borrow_import.Thing) -> int: + return resource_borrow_import.foo(t) + 4 + def test_resource_alias(self, things: List[imports.resource_alias1.Thing]) -> List[imports.resource_alias1.Thing]: return things diff --git a/src/test/tests.rs b/src/test/tests.rs index 6929fa2..09aefd0 100644 --- a/src/test/tests.rs +++ b/src/test/tests.rs @@ -384,6 +384,31 @@ fn resource_borrow_import() -> Result<()> { }) } +#[test] +fn resource_borrow_import_async_export() -> Result<()> { + // An async export that is lent a borrow of an imported resource must release + // it before `task.return`, or the host traps with "borrow handles still + // remain at the end of the call". + TESTER.test(|world, store, runtime| { + runtime.block_on(async { + let thing = store.data_mut().ctx().table.push(ThingU32(42))?; + + assert_eq!( + 42 + 3 + 4, + store + .run_concurrent(async |store| { + world + .call_test_resource_borrow_import_async(store, thing) + .await + }) + .await?? + ); + + Ok(()) + }) + }) +} + #[test] fn resource_borrow_export() -> Result<()> { TESTER.test(|world, store, runtime| { diff --git a/src/test/wit/tests.wit b/src/test/wit/tests.wit index cfe233f..0ef3070 100644 --- a/src/test/wit/tests.wit +++ b/src/test/wit/tests.wit @@ -186,6 +186,7 @@ interface empty-resource { world tests { use resource-alias1.{thing}; use resource-floats.{float}; + use resource-borrow-import.{thing as imported-thing}; export simple-export; import simple-import-and-export; @@ -230,6 +231,8 @@ world tests { export test-resource-borrow-import: func(v: u32) -> u32; + export test-resource-borrow-import-async: async func(t: borrow) -> u32; + export test-resource-alias: func(things: list) -> list; export add: func(a: borrow, b: borrow) -> own; From 4bbdeb935930c5683fd2ba6666331f37ee04932c Mon Sep 17 00:00:00 2001 From: FrancoisLD Date: Thu, 29 Oct 2026 23:44:31 -0400 Subject: [PATCH 2/2] release borrows before `task.return` The Canonical ABI traps if a task returns while any borrow lent to it is still outstanding. `call_task_return` called `task.return` first and released the borrows afterwards, so every async-lifted export that took a `borrow` trapped with "borrow handles still remain at the end of the call" on Wasmtime 43 and later. Release the borrows first. Results can't hold borrows, so lowering them afterwards is unaffected. Fixes #255 --- runtime/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4fa62e5..94a2799 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -497,11 +497,14 @@ mod async_ { ReturnStyle::Result => vec![result], }; - let mut call = MyCall::new(results); - func.call_task_return(&mut call); + // `task.return` traps while any borrow lent to this task is still + // outstanding, so drop them first. if borrows != 0 { release_borrows(py, *unsafe { Box::from_raw(borrows as *mut Vec) }); } + + let mut call = MyCall::new(results); + func.call_task_return(&mut call); } #[pyo3::pyfunction]