diff --git a/src/python.ts b/src/python.ts index e7577ea..dce08bb 100644 --- a/src/python.ts +++ b/src/python.ts @@ -862,7 +862,10 @@ export class PyObject { maybeThrowError(); - return new PyObject(result); + const pyObject = new PyObject(result); + // PyObject_Call returns a new reference. Register it so GC releases it. + refregistry.register(pyObject, result); + return pyObject; } /** diff --git a/test/test_with_gc.ts b/test/test_with_gc.ts index 93a6f7b..f3e5e73 100644 --- a/test/test_with_gc.ts +++ b/test/test_with_gc.ts @@ -1,6 +1,34 @@ -import python, { Callback } from "../mod.ts"; +import python, { Callback, ProxiedPyObject } from "../mod.ts"; import { assertEquals } from "./asserts.ts"; +Deno.test("call results release their Python reference after gc", async () => { + const sys = python.import("sys"); + const pyModule = python.runModule( + ` +target = object() +def get_target(): + return target +`, + "call_result_gc", + ); + const target = pyModule.target; + const count = () => sys.getrefcount(target).valueOf() as number; + + const baseline = count(); + const getTarget = pyModule.get_target[ProxiedPyObject]; + const callMany = () => { + const results = []; + for (let i = 0; i < 100; i++) results.push(getTarget.call()); + }; + callMany(); + for (let i = 0; i < 10; i++) { + // @ts-ignore: requires --v8-flags=--expose-gc + gc(); + await new Promise((resolve) => setTimeout(resolve, 0)); + } + + assertEquals(count(), baseline); +}); Deno.test( "js fns are automaticlly converted to callbacks", // auto callbacks are just a convience api, but they leak their resources