From a4154d9669ad039b89c5c418837f71db9d2dfdf9 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Sun, 24 Jun 2018 12:29:46 -0400 Subject: [PATCH] Add destroy method to remove references to Python objects --- docs/type_conversions.md | 13 ++++++++++--- src/pyproxy.c | 16 +++++++++++++++- test/test_python.py | 22 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/docs/type_conversions.md b/docs/type_conversions.md index c1247669f..b6f0cfc25 100644 --- a/docs/type_conversions.md +++ b/docs/type_conversions.md @@ -82,9 +82,16 @@ currently supported: An additional limitation is that when passing a Python object to Javascript, there is no way for Javascript to automatically garbage collect that object. -Therefore, Python objects must be manually free'd when passed to Javascript, or -they will leak. (TODO: There isn't currently a way to do this, but it will be -implemented soon). +Therefore, custom Python objects must be manually destroyed when passed to Javascript, or +they will leak. To do this, call `.destroy()` on the object, after which Javascript will no longer have access to the object. + +```javascript +var foo = pyodide.pyimport('foo'); +foo.call_method(); +foo.destroy(); +foo.call_method(); // This will raise an exception, since the object has been + // destroyed +``` ## Using Python objects from Javascript diff --git a/src/pyproxy.c b/src/pyproxy.c index 0904f43ec..1ad05583b 100644 --- a/src/pyproxy.c +++ b/src/pyproxy.c @@ -116,6 +116,13 @@ _pyproxy_apply(int ptrobj, int idargs) return idresult; } +void +_pyproxy_destroy(int ptrobj) +{ + PyObject* pyobj = (PyObject*)ptrobj; + Py_DECREF(ptrobj); +} + EM_JS(int, pyproxy_new, (int ptrobj), { var target = function(){}; target['$$'] = { ptr : ptrobj, type : 'PyProxy' }; @@ -126,7 +133,11 @@ EM_JS(int, pyproxy_init, (), { // clang-format off Module.PyProxy = { getPtr: function(jsobj) { - return jsobj['$$']['ptr']; + var ptr = jsobj['$$']['ptr']; + if (ptr === null) { + throw new Error("Object has already been destroyed"); + } + return ptr; }, isPyProxy: function(jsobj) { return jsobj['$$'] !== undefined && jsobj['$$']['type'] === 'PyProxy'; @@ -149,6 +160,9 @@ EM_JS(int, pyproxy_init, (), { } } else if (jskey === '$$') { return jsobj['$$']; + } else if (jskey === 'destroy') { + __pyproxy_destroy(this.getPtr(jsobj)); + jsobj['$$']['ptr'] = null; } ptrobj = this.getPtr(jsobj); var idkey = Module.hiwire_new_value(jskey); diff --git a/test/test_python.py b/test/test_python.py index 20e77e9f2..6e23027fa 100644 --- a/test/test_python.py +++ b/test/test_python.py @@ -52,6 +52,8 @@ def test_pythonexc2js(selenium): selenium.run_js('return pyodide.runPython("5 / 0")') except JavascriptException as e: assert('ZeroDivisionError' in str(e)) + else: + assert False, 'Expected exception' def test_js2python(selenium): @@ -131,6 +133,26 @@ def test_pyproxy(selenium): "return pyodide.pyimport('f').toString()").startswith('