Add destroy method to remove references to Python objects

This commit is contained in:
Michael Droettboom 2018-06-24 12:29:46 -04:00
parent e2e8b29739
commit a4154d9669
3 changed files with 47 additions and 4 deletions

View File

@ -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

View File

@ -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);

View File

@ -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('<Foo')
def test_pyproxy_destroy(selenium):
selenium.run(
"class Foo:\n"
" bar = 42\n"
" def get_value(self, value):\n"
" return value * 64\n"
"f = Foo()\n"
)
try:
selenium.run_js(
"let f = pyodide.pyimport('f');\n"
"console.assert(f.get_value(1) === 64);\n"
"f.destroy();\n"
"f.get_value();\n")
except JavascriptException as e:
assert 'f.get_value is not a function' in str(e)
else:
assert False, 'Expected exception'
def test_jsproxy(selenium):
assert selenium.run(
"from js import document\n"