diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index bf754f81b..4caf53a04 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -1326,7 +1326,10 @@ func {camel}({go_params}) {go_results} {{ ( if abi::guest_export_needs_post_return(resolve, func) { - format!("{PINNER} := &runtime.Pinner{{}}") + format!( + "{PINNER} := &runtime.Pinner{{}} + defer {PINNER}.Unpin()" + ) } else { String::new() }, diff --git a/tests/runtime-async/async/return-string/runner.go b/tests/runtime-async/async/return-string/runner.go new file mode 100644 index 000000000..3038171f3 --- /dev/null +++ b/tests/runtime-async/async/return-string/runner.go @@ -0,0 +1,35 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' + +package export_wit_world + +import ( + "fmt" + "runtime" + + test "wit_component/my_test_i" +) + +/* + +This tests for pinner leaks in generated Go code for async exported +function that return heap-allocated types (strings, lists, etc.). Without +`pinner.Unpin()`, the `runtime.Pinner` object goes out of scope after +the function returns with pinned pointers still alive. When GC finalizes +the Pinner, it panics: + +``` +panic: runtime error: runtime.Pinner: found leaking pinned pointer; +forgot to call Unpin()? +``` +*/ + +func Run() { + // Perform a heap allocation + got := test.ReturnString() + if got != "hello" { + panic(fmt.Sprintf("expected \"hello\", got %q", got)) + } + + // Force GC to finalize any leaked Pinners + runtime.GC() +} diff --git a/tests/runtime-async/async/return-string/test.go b/tests/runtime-async/async/return-string/test.go new file mode 100644 index 000000000..2342e68d1 --- /dev/null +++ b/tests/runtime-async/async/return-string/test.go @@ -0,0 +1,5 @@ +package export_my_test_i + +func ReturnString() string { + return "hello" +} diff --git a/tests/runtime-async/async/return-string/test.wit b/tests/runtime-async/async/return-string/test.wit new file mode 100644 index 000000000..943d8cea7 --- /dev/null +++ b/tests/runtime-async/async/return-string/test.wit @@ -0,0 +1,14 @@ +package my:test; + +interface i { + return-string: async func() -> string; +} + +world test { + export i; +} + +world runner { + import i; + export run: async func(); +} \ No newline at end of file