Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PERF Only render extra destroyed information when debug mode is on #4027

Merged
merged 6 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/project/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ myst:

## Unreleased

- {{ Enhancement }} For performance reasons, don't render extra information in
PyProxy destroyed message by default. By using `pyodide.setDebug(true)`, you
can opt into worse performance and better error messages.
{pr}`4027`

- {{ Fix }} Fixed adding getters/setters to a `PyProxy` with
`Object.defineProperty` and improved compliance with JavaScript rules around
Proxy traps.
Expand Down
50 changes: 31 additions & 19 deletions src/core/pyproxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,35 @@ function pyproxy_decref_cache(cache: PyProxyCache) {
}
}

function generateDestroyedMessage(
proxy: PyProxy,
destroyed_msg: string,
): string {
destroyed_msg = destroyed_msg || "Object has already been destroyed";
if (API.debug_ffi) {
let proxy_type = proxy.type;
let proxy_repr;
try {
proxy_repr = proxy.toString();
} catch (e) {
if ((e as any).pyodide_fatal_error) {
throw e;
}
}
destroyed_msg += "\n" + `The object was of type "${proxy_type}" and `;
if (proxy_repr) {
destroyed_msg += `had repr "${proxy_repr}"`;
} else {
destroyed_msg += "an error was raised when trying to generate its repr";
}
} else {
destroyed_msg +=
"\n" +
"For more information about the cause of this error, use `pyodide.setDebug(true)`";
}
return destroyed_msg;
}

Module.pyproxy_destroy = function (
proxy: PyProxy,
destroyed_msg: string,
Expand All @@ -393,29 +422,12 @@ Module.pyproxy_destroy = function (
}
let ptrobj = _getPtr(proxy);
Module.finalizationRegistry.unregister(proxy.$$);
destroyed_msg = destroyed_msg || "Object has already been destroyed";
let proxy_type = proxy.type;
let proxy_repr;
try {
proxy_repr = proxy.toString();
} catch (e) {
if ((e as any).pyodide_fatal_error) {
throw e;
}
}
proxy.$$.destroyed_msg = generateDestroyedMessage(proxy, destroyed_msg);
pyproxy_decref_cache(proxy.$$.cache);
// Maybe the destructor will call JavaScript code that will somehow try
// to use this proxy. Mark it deleted before decrementing reference count
// just in case!
proxy.$$.ptr = 0;
Module.finalizationRegistry.unregister(proxy.$$);
destroyed_msg += "\n" + `The object was of type "${proxy_type}" and `;
if (proxy_repr) {
destroyed_msg += `had repr "${proxy_repr}"`;
} else {
destroyed_msg += "an error was raised when trying to generate its repr";
}
proxy.$$.destroyed_msg = destroyed_msg;
pyproxy_decref_cache(proxy.$$.cache);
try {
Py_ENTER();
Module._Py_DecRef(ptrobj);
Expand Down
12 changes: 12 additions & 0 deletions src/js/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,18 @@ export class PyodideAPI {
Object.defineProperty(this, "PythonError", { value: PythonError });
return PythonError;
}

/**
* Turn on or off debug mode. In debug mode, some error messages are improved
* at a performance cost.
* @param debug If true, turn debug mode on. If false, turn debug mode off.
* @returns The old value of the debug flag.
*/
static setDebug(debug: boolean): boolean {
const orig = !!API.debug_ffi;
API.debug_ffi = debug;
return orig;
}
}

/** @hidetype */
Expand Down
18 changes: 18 additions & 0 deletions src/tests/test_jsproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ def test_call_pyproxy_destroy_args(selenium):
selenium.run_js(
r"""
let y;
pyodide.setDebug(true);
self.f = function(x){ y = x; }
pyodide.runPython(`
from js import f
Expand All @@ -361,6 +362,23 @@ def test_call_pyproxy_destroy_args(selenium):
"""
)

selenium.run_js(
r"""
let y;
pyodide.setDebug(false);
self.f = function(x){ y = x; }
pyodide.runPython(`
from js import f
f({})
f([])
`);
assertThrows(() => y.length, "Error",
"This borrowed proxy was automatically destroyed at the end of a function call.*\n" +
'For more information about the cause of this error, use `pyodide.setDebug.true.`'
ryanking13 marked this conversation as resolved.
Show resolved Hide resolved
);
"""
)

selenium.run_js(
"""
let y;
Expand Down
2 changes: 1 addition & 1 deletion src/tests/test_pyodide.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ def test_return_destroyed_value(selenium):
f = run_js("(function(x){ return x; })")
p = create_proxy([])
p.destroy()
with pytest.raises(JsException, match='The object was of type "list" and had repr'):
with pytest.raises(JsException, match="Object has already been destroyed"):
f(p)


Expand Down
89 changes: 47 additions & 42 deletions src/tests/test_pyproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -856,50 +856,55 @@ def test_pyproxy_implicit_copy(selenium):
def test_errors(selenium):
selenium.run_js(
r"""
let t = pyodide.runPython(`
from pyodide.ffi import to_js
def te(self, *args, **kwargs):
raise Exception(repr(args))
class Temp:
__getattr__ = te
__setattr__ = te
__delattr__ = te
__dir__ = te
__call__ = te
__getitem__ = te
__setitem__ = te
__delitem__ = te
__iter__ = te
__len__ = te
__contains__ = te
__await__ = te
__repr__ = te
to_js(Temp())
Temp()
`);
assertThrows(() => t.x, "PythonError", "");
const origDebug = pyodide.setDebug(true);
try {
t.x;
} catch(e){
assert(() => e instanceof pyodide.ffi.PythonError);
const t = pyodide.runPython(`
from pyodide.ffi import to_js
def te(self, *args, **kwargs):
raise Exception(repr(args))
class Temp:
__getattr__ = te
__setattr__ = te
__delattr__ = te
__dir__ = te
__call__ = te
__getitem__ = te
__setitem__ = te
__delitem__ = te
__iter__ = te
__len__ = te
__contains__ = te
__await__ = te
__repr__ = te
to_js(Temp())
Temp()
`);
assertThrows(() => t.x, "PythonError", "");
try {
t.x;
} catch(e){
assert(() => e instanceof pyodide.ffi.PythonError);
}
assertThrows(() => t.x = 2, "PythonError", "");
assertThrows(() => delete t.x, "PythonError", "");
assertThrows(() => Object.getOwnPropertyNames(t), "PythonError", "");
assertThrows(() => t(), "PythonError", "");
assertThrows(() => t.get(1), "PythonError", "");
assertThrows(() => t.set(1, 2), "PythonError", "");
assertThrows(() => t.delete(1), "PythonError", "");
assertThrows(() => t.has(1), "PythonError", "");
assertThrows(() => t.length, "PythonError", "");
assertThrows(() => t.toString(), "PythonError", "");
assertThrows(() => Array.from(t), "PythonError", "");
await assertThrowsAsync(async () => await t, "PythonError", "");
t.destroy();
assertThrows(() => t.type, "Error",
"Object has already been destroyed\n" +
'The object was of type "Temp" and an error was raised when trying to generate its repr'
);
} finally {
pyodide.setDebug(origDebug);
}
assertThrows(() => t.x = 2, "PythonError", "");
assertThrows(() => delete t.x, "PythonError", "");
assertThrows(() => Object.getOwnPropertyNames(t), "PythonError", "");
assertThrows(() => t(), "PythonError", "");
assertThrows(() => t.get(1), "PythonError", "");
assertThrows(() => t.set(1, 2), "PythonError", "");
assertThrows(() => t.delete(1), "PythonError", "");
assertThrows(() => t.has(1), "PythonError", "");
assertThrows(() => t.length, "PythonError", "");
assertThrows(() => t.toString(), "PythonError", "");
assertThrows(() => Array.from(t), "PythonError", "");
await assertThrowsAsync(async () => await t, "PythonError", "");
t.destroy();
assertThrows(() => t.type, "Error",
"Object has already been destroyed\n" +
'The object was of type "Temp" and an error was raised when trying to generate its repr'
);
"""
)

Expand Down