-
Notifications
You must be signed in to change notification settings - Fork 345
ErrorHandling.md: Fatal Errors with Promises #198
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
Conversation
kenrussell
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kai and I talked through these scenarios in person, and Kai iterated on the API design multiple times as he wrote example code which would recover from the various failure scenarios. Looks good to me.
|
Thanks for putting this up, it trades synchronicity of create device in favor of making it easy to write robust device and adapter loss handling. Nice! |
9d67aa9 to
49c447b
Compare
|
What do you think of using an event handler instead of this devicelost callback function? Mixing callback functions and promises isn't something I've seen a lot in Web APIs. Note that this proposal would be similar to how Web Bluetooth and Web USB handles device disconnections. See my proposal below Promise<GPUDevice> requestDevice(GPUDeviceDescriptor descriptor);partial interface GPUDevice : EventTarget {
attribute EventHandler ondevicelost;
};
[
Constructor(DOMString type, GPUDeviceLostEventInit gpuDeviceLostEventInitDict),
Exposed=Window
]
interface GPUDeviceLostEvent : Event {
readonly attribute DOMString message;
};
dictionary GPUDeviceLostEventInit : EventInit {
required DOMString message;
};Fire an event named "devicelost" at GPUDevice using GPUDeviceLostEvent with its message attribute initialized to the error message that triggered device lost. Example would have to be updated as well. Note that try {
this.device = await this.adapter.requestDevice({ /* options */ });
this.device.addEventListener('devicelost', function(event) {
// Device was lost.
console.error("device lost", info);
// Try to get a device again.
this.ensureDevice();
});
this.rendering = true;
} catch (e) { |
|
For a while before I opened this PR I had it as an event rather than a callback. However I tentatively changed it to a callback and left this TODO:
Do you have specific advice?
(() => {
const cvs = document.createElement('canvas');
const gl = cvs.getContext('webgl');
const ext = gl.getExtension('WEBGL_lose_context');
ext.loseContext();
cvs.addEventListener('webglcontextlost', (e) => { console.warn(e); });
})();This is necessary because the app has to know it's never going to leave a context loss on the floor, because if it does it won't know to reinitialize. |
design/ErrorHandling.md
Outdated
| callback GPUDeviceLostCallback = void (GPUDeviceLostInfo info); | ||
| interface GPUDeviceLostInfo { | ||
| readonly attribute GPUDevice device; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RC: remove or rename to lostDevice
In that case, we could have a partial interface GPUDevice : EventTarget {
readonly attribute boolean lost;
attribute EventHandler ondevicelost;
}; |
|
I've been trying hard for there to not be two separate signals the app has to check to make sure they do things right, so I'd strongly like to avoid it. If it were something like |
|
Discussed at the 11 Feb 2019 WebGPU Meeting |
|
Discussed requestAdapter vs listAdapters at the meeting. The issue is somewhat orthogonal. I will consider at Rafael's other comments, try to describe the meaning of requestAdapter rejection, and then we'll try to merge this ASAP so it doesn't collide with requestAdapter vs listAdapters. |
RafaelCintron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though we made the decision to leave requestAdapter in the API for now, there are changes I'd like to see to this PR in light of that decision.
design/ErrorHandling.md
Outdated
| }); | ||
| this.rendering = true; | ||
| } catch (e) { | ||
| // Request failed (likely due to adapter loss). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the adapter can also become lost, we should change requestAdapter so that it accepts onAdapterLost callback like you've done for requestDevice. If we assume that all devices become lost when an adapter becomes lost, we can even dispense with onDeviceLost in favor of just having onAdapterLost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually considered and specifically avoided this additional signal. I don't think there is any case in which the application would need to know about an adapter loss, aside from knowing that the device(s) were lost. They can try adapter.createDevice to discover whether the adapter will no longer vend any new devices.
Also, since one device on an adapter can be lost without other devices on the same adapter being lost, the signal needed to be on the device.
design/ErrorHandling.md
Outdated
| // Device was lost. | ||
| console.error("device lost", info); | ||
| // Try to get a device again. | ||
| this.ensureDevice(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the device becomes lost because the adapter becomes lost, this code will end up in an infinite loop of rejected promises at the hands of requestDevice. I think if either the device or the adapter becomes lost, the developer should re-request an adapter.
Update: Re-reading the code sample code a second time, I realize that it will not result in an infinite loop. The first requestDevice after receiving the callback will get rejected, causing the catch clause to run and clear out the adapter.
As @kainino0x points out below, there are cases where the device can become lost without the adapter becoming lost so this seems warranted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This said, the sample code could just always try on a newly requested adapter instead of retrying on the same adapter before trying on a new adapter. Both would be valid choices for an app, but the distinction also seems minimal. Maybe it would be better for the sample to be simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decided not to simplify the example, because it's useful to demonstrate the use case. However, I did refactor the example to be iterative instead of recursive, which is clearer. I think it is equivalent.
7dcac62 to
653eb9a
Compare
|
FYI: Updated the example (and rebased), but haven't resolved onDeviceLost vs event vs promise yet. So that part is not ready for review. |
653eb9a to
3346548
Compare
3346548 to
9b29cbb
Compare
|
Here is a more comprehensive investigation of the options I've considered so far. @beaufortfrancois and @jungkees in particular, would you be able to comment on this? Premises:
Options:
let device;
adapter.addEventListener('ondevicelost', (e) => {
if (e.lostDevice !== device) { return; }
// ...
});
device = await adapter.createDevice(...);
const device = await adapter.createDevice(...); // Note that this awaits
device.addEventListener('ondevicelost', (e) => { /* ... */ }); // Not guaranteed?
const device = await adapter.createDevice(...);
device.onlost.then(() => { /* ... */ }); // Guaranteed, no matter how late it is added. |
|
@kainino0x , thank you for the comprehensive investigation. Given the fact that a device cannot become lost more than once, I think the promise approach is best because it handles multiple clients and guarantees notifications will not be missed. I'm not concerned about breaking premise 4 because in the other APIs you list, a 'device' can become, restored/reconnected, etc. Hence, a callback or event is the only viable option because there can be multiple notifications. WebGPU does not have this problem. |
|
I think I forgot to mention above: the first option (webglcontextlost-style) would work if requestAdapter always vends a new Adapter object instead of returning the same one for the same physical adapter. |
|
For #198 (comment), I think the promise option fits very well. As @RafaelCintron mentioned, it's a state transition rather than an event that can fire multiple times. I'd like to suggest And actually we have a similar attribute in the platform: https://w3c.github.io/ServiceWorker/#dom-serviceworkercontainer-ready. People initially proposed FYI, there's a patter for option 2 (event handler on device) as well. We can define a task source on the EventTarget (like https://w3c.github.io/ServiceWorker/#dfn-client-message-queue) and queue events before the event handlers or event listeners are attached. We can make the task source hold that events until it is enabled. And we can enable it when the first event handler is attached and/or by addig an API to enable it like https://w3c.github.io/ServiceWorker/#navigator-service-worker-startMessages. |
|
Thank you very much for the review, and pointer to the prior work with ServiceWorkerContainer.ready and option 2. It'll be helpful to take a look at how those work. |
|
Applied changes to have Also changed the example again to remove the loop; ensureDevice doesn't need to try more than twice to get a device, because it always starts from the top on device loss. I think(?) the loop was an artifact of an old intermediate state. |
|
Changes are not too major; see 8ecd8bb. Maybe we can cover and finish this up at the upcoming meeting. Apologies again for the late review request. |
beaufortfrancois
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the GPUDevice.lost promise pattern. The thing that is unusual is the fact that the promise may never resolve during the life of the web app if everything goes well.
Maybe adding a note like:
The returned
lostpromise will never reject and may be pending forever.
8ecd8bb to
fa6f30b
Compare
|
Thanks for the review! |
Kangz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very good, thanks for the many many iterations @kainino0x ! Hopefully we can resolve any remaining issues in today's call and merge the PR.
fa6f30b to
352bf6c
Compare
|
Done. I'm going to go ahead with merge. Thanks all! |
Adds the most minimal API I could find for handling device/adapter loss cases. Please see the "case studies" section and example code to better understand how this works.
Also fixes #11.