-
Notifications
You must be signed in to change notification settings - Fork 67
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
How do you catch runtime errors in the realm? #212
Comments
Are you wanting something like class Environment {
_realm = Realm.makeRootRealm();
_onUncaughtError;
constructor(onUncaughtError) {
this._onUncaughtError = onUncaughtError;
}
runScript(sourceText) {
try {
return this._realm.eval(sourceText);
} catch (err) {
if (this._realm.global.onerror) {
this._realm.global.onerror(err);
} else {
this._onUncaughtError(err);
}
}
}
}
const e = new Environment(uncaughtError => console.error(uncaughtError));
e.runScript(`
globalThis.onerror = (err) => {
globalThis.caughtAnError = true;
}
`);
e.runScript(`
throw new Error("Ooops!");
`);
e.global.caughtAnError === true; |
@Jamesernator This does not catch asynchronous execution errors. e.runScript(`
setTimeout(() => {
throw new Error("Ooops!");
})
`); |
Note that class Environment {
_realm = Realm.makeRootRealm();
_onUncaughtError;
constructor(onUncaughtError) {
this._onUncaughtError = onUncaughtError;
// Define our own setTimeout that creates a setTimeout within the Realm
this._realm.evaluate(`
globalThis.setTimeout = function setTimeout(func, ...otherArgs) {
realSetTimeout(function(...args) {
try {
func(...args);
} catch (err) {
handleUncaughtError(err);
}
}, ...otherArgs);
}
`, {
// inject real functions from our own host into this evaluation
// this does not make them available to successive untrusted scripts
realSetTimeout: setTimeout,
handleUncaughtError: (err) => this._handleUncaughtError(err),
});
}
_handleUncaughtError(err) {
if (this._realm.onerror) {
try {
this._realm.onerror(err);
} catch (err) {
this._onUncaughtError(err);
}
} else {
this._onUncaughtError(err);
}
}
runScript(sourceText) {
try {
return this._realm.eval(sourceText);
} catch (err) {
this._handleUncaughtError(err);
}
}
}
const e = new Environment(() => console.error("An error was never caught"));
e.runScript(`
globalThis.onerror = () => {
// will be called at some point
}
setTimeout(() => {
throw new Error("Oops!");
});
`); Now this is especially confusing because the † In browsers |
Resolution: The error control is done by the agent, not by the realm the realm doesn't know about next-turn. This level of control could be implemented in the future if we ever attempt to introduce the concept of the agent. It seems that there is nothing to be done here. |
I agree having error control happening on the agent is a good idea, but this quote:
worries me... What will be the recommended way to do error handling until cc/ @erights |
We should work towards an Do you have a particular error example that is too urgent to wait for |
synchronous: let r = new Realm();
let f = r.evaluate(`
throw new Error('error in realm!');
`); and async: let r = new Realm();
let f = r.evaluate(`
let p = new Promise((resolve, reject) => {
reject('rejection in relam');
});
`); I'm specifically concerned with the observability and debuggability around uncaught exceptions and unhandled rejections. Once Realms land, it is likely that frameworks start incorporating it to achieve isolation and I would like to be sure we have proper error handling... |
@ghermeto and I talked about this and we decided to start an |
@ghermeto this is a great question, for "synchronous" error, it seems to be fine, because the "initiator" is always the outer realm, which can try/catch it and react to it. But for "async" error, this is a little bit more complicated. IIRC, in browsers today, the identity of the Promise intrinsic object matters, e.g.: iframe.contentWindow.eval(`
new Promise((resolve, reject) => {
reject('rejection of Promise Intrinsic Object from iframe');
});
`);
iframe.contentWindow.eval(`
// top.* here means outer realm
new top.Promise((resolve, reject) => {
reject('rejection of Promise Intrinsic Object from outer realm');
});
`); I believe that only the second rejection will be observed from outer realm. We should discuss this in more details asap. I also think that this touches on another set of open issues, what are the capabilities a newly created realm, maybe the host can provide extra sugar for folks trying to observe uncaught exceptions. I will reopen this issue. /cc @littledan |
To clarify, I am talking about Update: another important point to notice here is that in nodejs, things are quite different, the unhandled rejection can be capture at the process level via |
@caridy I think if it gets to the I think having an |
Ok, after spending some time discussing this with few folks, here is my proposal: Proposal
Once we have the new proposal for the Agent, we can provide a lot more control. Honestly, I haven't seen folks complaining about this in node when using VM, it might be sufficient to capture them at the process level without ways to identify the realm. |
This is already a normative requirement in the current spec. |
@erights you should call it |
I'm having trouble following this thread. I imagined the Realm proposal was all part of the same Agent, and therefore, there's no support for attribution of errors to a particular Realm. (I'm not even sure what it would mean to attribute errors to a Realm, since callstacks may be mixed, unlike between Agents.) Is there anywhere I can read more about what people are thinking about for Agents? |
For Stage 3, I think we should draw a conclusion on this issue. Here's a conclusion that I'd propose:
Does this match the champion group's thoughts? |
💯 |
As part of closing this issue, should the resolution be documented anywhere (e.g., the explainer)? I'm planning on integrating it into the HTML PR, but we should probably have some record of what the intention is cross-environment. |
The text was updated successfully, but these errors were encountered: