Skip to content

Commit 26bab56

Browse files
[deno] Rework error and device loss handling (#7693)
1 parent 85001b2 commit 26bab56

File tree

3 files changed

+111
-100
lines changed

3 files changed

+111
-100
lines changed

deno_webgpu/adapter.rs

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
#[allow(clippy::disallowed_types)]
44
use std::collections::HashSet;
55
use std::rc::Rc;
6-
use std::sync::Arc;
76

87
use deno_core::cppgc::SameObject;
98
use deno_core::op2;
109
use deno_core::v8;
1110
use deno_core::GarbageCollected;
1211
use deno_core::OpState;
12+
use deno_core::V8TaskSpawner;
1313
use deno_core::WebIDL;
14-
use tokio::sync::Mutex;
1514

1615
use super::device::GPUDevice;
1716
use crate::webidl::features_to_feature_names;
@@ -108,7 +107,6 @@ impl GPUAdapter {
108107
fn request_device(
109108
&self,
110109
state: &mut OpState,
111-
isolate_ptr: *mut v8::Isolate,
112110
scope: &mut v8::HandleScope,
113111
#[webidl] descriptor: GPUDeviceDescriptor,
114112
) -> Result<v8::Global<v8::Value>, CreateDeviceError> {
@@ -144,29 +142,27 @@ impl GPUAdapter {
144142
self.instance
145143
.adapter_request_device(self.id, &wgpu_descriptor, None, None)?;
146144

147-
let (lost_sender, lost_receiver) = tokio::sync::oneshot::channel();
148-
let (uncaptured_sender, mut uncaptured_receiver) = tokio::sync::mpsc::unbounded_channel();
149-
let (uncaptured_sender_is_closed_sender, mut uncaptured_sender_is_closed_receiver) =
150-
tokio::sync::oneshot::channel::<()>();
151-
145+
let spawner = state.borrow::<V8TaskSpawner>().clone();
146+
let lost_resolver = v8::PromiseResolver::new(scope).unwrap();
147+
let lost_promise = lost_resolver.get_promise(scope);
152148
let device = GPUDevice {
153149
instance: self.instance.clone(),
154150
id: device,
155151
queue,
156152
label: descriptor.label,
157153
queue_obj: SameObject::new(),
158154
adapter_info: self.info.clone(),
159-
error_handler: Arc::new(super::error::DeviceErrorHandler::new(
160-
lost_sender,
161-
uncaptured_sender,
162-
uncaptured_sender_is_closed_sender,
155+
error_handler: Rc::new(super::error::DeviceErrorHandler::new(
156+
v8::Global::new(scope, lost_resolver),
157+
spawner,
163158
)),
164159
adapter: self.id,
165-
lost_receiver: Mutex::new(Some(lost_receiver)),
160+
lost_promise: v8::Global::new(scope, lost_promise),
166161
limits: SameObject::new(),
167162
features: SameObject::new(),
168163
};
169164
let device = deno_core::cppgc::make_cppgc_object(scope, device);
165+
let weak_device = v8::Weak::new(scope, device);
170166
let event_target_setup = state.borrow::<crate::EventTargetSetup>();
171167
let webidl_brand = v8::Local::new(scope, event_target_setup.brand.clone());
172168
device.set(scope, webidl_brand, webidl_brand);
@@ -176,52 +172,15 @@ impl GPUAdapter {
176172
let null = v8::null(scope);
177173
set_event_target_data.call(scope, null.into(), &[device.into()]);
178174

179-
let key = v8::String::new(scope, "dispatchEvent").unwrap();
180-
let val = device.get(scope, key.into()).unwrap();
181-
let func = v8::Global::new(scope, val.try_cast::<v8::Function>().unwrap());
182-
let device = v8::Global::new(scope, device.cast::<v8::Value>());
183-
let error_event_class = state.borrow::<crate::ErrorEventClass>().0.clone();
184-
185-
let context = scope.get_current_context();
186-
let context = v8::Global::new(scope, context);
187-
188-
let task_device = device.clone();
189-
deno_unsync::spawn(async move {
190-
loop {
191-
// TODO(@crowlKats): check for uncaptured_receiver.is_closed instead once tokio is upgraded
192-
if !matches!(
193-
uncaptured_sender_is_closed_receiver.try_recv(),
194-
Err(tokio::sync::oneshot::error::TryRecvError::Empty)
195-
) {
196-
break;
197-
}
198-
let Some(error) = uncaptured_receiver.recv().await else {
199-
break;
200-
};
201-
202-
// SAFETY: eh, it's safe
203-
let isolate: &mut v8::Isolate = unsafe { &mut *isolate_ptr };
204-
let scope = &mut v8::HandleScope::with_context(isolate, &context);
205-
let error = deno_core::error::to_v8_error(scope, &error);
206-
207-
let error_event_class = v8::Local::new(scope, error_event_class.clone());
208-
let constructor = v8::Local::<v8::Function>::try_from(error_event_class).unwrap();
209-
let kind = v8::String::new(scope, "uncapturederror").unwrap();
210-
211-
let obj = v8::Object::new(scope);
212-
let key = v8::String::new(scope, "error").unwrap();
213-
obj.set(scope, key.into(), error);
214-
215-
let event = constructor
216-
.new_instance(scope, &[kind.into(), obj.into()])
217-
.unwrap();
218-
219-
let recv = v8::Local::new(scope, task_device.clone());
220-
func.open(scope).call(scope, recv, &[event.into()]);
221-
}
222-
});
175+
// Now that the device is fully constructed, give the error handler a
176+
// weak reference to it.
177+
let device = device.cast::<v8::Value>();
178+
deno_core::cppgc::try_unwrap_cppgc_object::<GPUDevice>(scope, device)
179+
.unwrap()
180+
.error_handler
181+
.set_device(weak_device);
223182

224-
Ok(device)
183+
Ok(v8::Global::new(scope, device))
225184
}
226185
}
227186

deno_webgpu/device.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::adapter::GPUAdapterInfo;
2929
use crate::adapter::GPUSupportedFeatures;
3030
use crate::adapter::GPUSupportedLimits;
3131
use crate::command_encoder::GPUCommandEncoder;
32+
use crate::error::GPUError;
3233
use crate::query_set::GPUQuerySet;
3334
use crate::render_bundle::GPURenderBundleEncoder;
3435
use crate::render_pipeline::GPURenderPipeline;
@@ -50,7 +51,7 @@ pub struct GPUDevice {
5051
pub queue_obj: SameObject<GPUQueue>,
5152

5253
pub error_handler: super::error::ErrorHandler,
53-
pub lost_receiver: tokio::sync::Mutex<Option<tokio::sync::oneshot::Receiver<()>>>,
54+
pub lost_promise: v8::Global<v8::Promise>,
5455
}
5556

5657
impl Drop for GPUDevice {
@@ -127,6 +128,8 @@ impl GPUDevice {
127128
#[fast]
128129
fn destroy(&self) {
129130
self.instance.device_destroy(self.id);
131+
self.error_handler
132+
.push_error(Some(GPUError::Lost(GPUDeviceLostReason::Destroyed)));
130133
}
131134

132135
#[required(1)]
@@ -560,16 +563,10 @@ impl GPUDevice {
560563
}
561564
}
562565

563-
// TODO(@crowlKats): support returning same promise
564-
#[async_method]
565566
#[getter]
566-
#[cppgc]
567-
async fn lost(&self) -> GPUDeviceLostInfo {
568-
if let Some(lost_receiver) = self.lost_receiver.lock().await.take() {
569-
let _ = lost_receiver.await;
570-
}
571-
572-
GPUDeviceLostInfo
567+
#[global]
568+
fn lost(&self) -> v8::Global<v8::Promise> {
569+
self.lost_promise.clone()
573570
}
574571

575572
#[required(1)]
@@ -826,7 +823,17 @@ impl GPUDevice {
826823
}
827824
}
828825

829-
pub struct GPUDeviceLostInfo;
826+
#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
827+
pub enum GPUDeviceLostReason {
828+
#[default]
829+
Unknown,
830+
Destroyed,
831+
}
832+
833+
#[derive(Default)]
834+
pub struct GPUDeviceLostInfo {
835+
pub reason: GPUDeviceLostReason,
836+
}
830837

831838
impl GarbageCollected for GPUDeviceLostInfo {}
832839

@@ -835,7 +842,11 @@ impl GPUDeviceLostInfo {
835842
#[getter]
836843
#[string]
837844
fn reason(&self) -> &'static str {
838-
"unknown"
845+
use GPUDeviceLostReason::*;
846+
match self.reason {
847+
Unknown => "unknown",
848+
Destroyed => "destroyed",
849+
}
839850
}
840851

841852
#[getter]

deno_webgpu/error.rs

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ use std::fmt::Formatter;
55
use std::sync::Mutex;
66
use std::sync::OnceLock;
77

8+
use deno_core::cppgc::make_cppgc_object;
9+
use deno_core::v8;
10+
11+
use deno_core::JsRuntime;
12+
use deno_core::V8TaskSpawner;
813
use wgpu_core::binding_model::CreateBindGroupError;
914
use wgpu_core::binding_model::CreateBindGroupLayoutError;
1015
use wgpu_core::binding_model::CreatePipelineLayoutError;
@@ -31,41 +36,38 @@ use wgpu_core::resource::CreateSamplerError;
3136
use wgpu_core::resource::CreateTextureError;
3237
use wgpu_core::resource::CreateTextureViewError;
3338

34-
pub type ErrorHandler = std::sync::Arc<DeviceErrorHandler>;
39+
use crate::device::GPUDeviceLostInfo;
40+
use crate::device::GPUDeviceLostReason;
41+
42+
pub type ErrorHandler = std::rc::Rc<DeviceErrorHandler>;
3543

3644
pub struct DeviceErrorHandler {
3745
pub is_lost: OnceLock<()>,
38-
lost_sender: Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
39-
uncaptured_sender_is_closed: Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
40-
41-
pub uncaptured_sender: tokio::sync::mpsc::UnboundedSender<GPUError>,
42-
4346
pub scopes: Mutex<Vec<(GPUErrorFilter, Vec<GPUError>)>>,
44-
}
47+
lost_resolver: Mutex<Option<v8::Global<v8::PromiseResolver>>>,
48+
spawner: V8TaskSpawner,
4549

46-
impl Drop for DeviceErrorHandler {
47-
fn drop(&mut self) {
48-
if let Some(sender) = self.uncaptured_sender_is_closed.lock().unwrap().take() {
49-
let _ = sender.send(());
50-
}
51-
}
50+
// The error handler is constructed before the device. A weak
51+
// reference to the device is placed here with `set_device`
52+
// after the device is constructed.
53+
device: OnceLock<v8::Weak<v8::Object>>,
5254
}
5355

5456
impl DeviceErrorHandler {
55-
pub fn new(
56-
lost_sender: tokio::sync::oneshot::Sender<()>,
57-
uncaptured_sender: tokio::sync::mpsc::UnboundedSender<GPUError>,
58-
uncaptured_sender_is_closed: tokio::sync::oneshot::Sender<()>,
59-
) -> Self {
57+
pub fn new(lost_resolver: v8::Global<v8::PromiseResolver>, spawner: V8TaskSpawner) -> Self {
6058
Self {
6159
is_lost: Default::default(),
62-
lost_sender: Mutex::new(Some(lost_sender)),
63-
uncaptured_sender,
64-
uncaptured_sender_is_closed: Mutex::new(Some(uncaptured_sender_is_closed)),
6560
scopes: Mutex::new(vec![]),
61+
lost_resolver: Mutex::new(Some(lost_resolver)),
62+
device: OnceLock::new(),
63+
spawner,
6664
}
6765
}
6866

67+
pub fn set_device(&self, device: v8::Weak<v8::Object>) {
68+
self.device.set(device).unwrap()
69+
}
70+
6971
pub fn push_error<E: Into<GPUError>>(&self, err: Option<E>) {
7072
let Some(err) = err else {
7173
return;
@@ -77,17 +79,22 @@ impl DeviceErrorHandler {
7779

7880
let err = err.into();
7981

80-
if matches!(err, GPUError::Lost) {
82+
if let GPUError::Lost(reason) = err {
8183
let _ = self.is_lost.set(());
82-
83-
if let Some(sender) = self.lost_sender.lock().unwrap().take() {
84-
let _ = sender.send(());
84+
if let Some(resolver) = self.lost_resolver.lock().unwrap().take() {
85+
self.spawner.spawn(move |scope| {
86+
let resolver = v8::Local::new(scope, resolver);
87+
let info = make_cppgc_object(scope, GPUDeviceLostInfo { reason });
88+
let info = v8::Local::new(scope, info);
89+
resolver.resolve(scope, info.into());
90+
});
8591
}
92+
8693
return;
8794
}
8895

8996
let error_filter = match err {
90-
GPUError::Lost => unreachable!(),
97+
GPUError::Lost(_) => unreachable!(),
9198
GPUError::Validation(_) => GPUErrorFilter::Validation,
9299
GPUError::OutOfMemory => GPUErrorFilter::OutOfMemory,
93100
GPUError::Internal => GPUErrorFilter::Internal,
@@ -101,7 +108,41 @@ impl DeviceErrorHandler {
101108
if let Some(scope) = scope {
102109
scope.1.push(err);
103110
} else {
104-
self.uncaptured_sender.send(err).unwrap();
111+
let device = self
112+
.device
113+
.get()
114+
.expect("set_device was not called")
115+
.clone();
116+
self.spawner.spawn(move |scope| {
117+
let state = JsRuntime::op_state_from(&*scope);
118+
let Some(device) = device.to_local(scope) else {
119+
// The device has already gone away, so we don't have
120+
// anywhere to report the error.
121+
return;
122+
};
123+
let key = v8::String::new(scope, "dispatchEvent").unwrap();
124+
let val = device.get(scope, key.into()).unwrap();
125+
let func = v8::Global::new(scope, val.try_cast::<v8::Function>().unwrap());
126+
let device = v8::Global::new(scope, device.cast::<v8::Value>());
127+
let error_event_class = state.borrow().borrow::<crate::ErrorEventClass>().0.clone();
128+
129+
let error = deno_core::error::to_v8_error(scope, &err);
130+
131+
let error_event_class = v8::Local::new(scope, error_event_class.clone());
132+
let constructor = v8::Local::<v8::Function>::try_from(error_event_class).unwrap();
133+
let kind = v8::String::new(scope, "uncapturederror").unwrap();
134+
135+
let obj = v8::Object::new(scope);
136+
let key = v8::String::new(scope, "error").unwrap();
137+
obj.set(scope, key.into(), error);
138+
139+
let event = constructor
140+
.new_instance(scope, &[kind.into(), obj.into()])
141+
.unwrap();
142+
143+
let recv = v8::Local::new(scope, device);
144+
func.open(scope).call(scope, recv, &[event.into()]);
145+
});
105146
}
106147
}
107148
}
@@ -118,7 +159,7 @@ pub enum GPUErrorFilter {
118159
pub enum GPUError {
119160
// TODO(@crowlKats): consider adding an unreachable value that uses unreachable!()
120161
#[class("UNREACHABLE")]
121-
Lost,
162+
Lost(GPUDeviceLostReason),
122163
#[class("GPUValidationError")]
123164
Validation(String),
124165
#[class("GPUOutOfMemoryError")]
@@ -131,7 +172,7 @@ pub enum GPUError {
131172
impl Display for GPUError {
132173
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
133174
match self {
134-
GPUError::Lost => Ok(()),
175+
GPUError::Lost(_) => Ok(()),
135176
GPUError::Validation(s) => f.write_str(s),
136177
GPUError::OutOfMemory => f.write_str("not enough memory left"),
137178
GPUError::Internal => Ok(()),
@@ -170,7 +211,7 @@ impl From<CreateBufferError> for GPUError {
170211
impl From<DeviceError> for GPUError {
171212
fn from(err: DeviceError) -> Self {
172213
match err {
173-
DeviceError::Lost => GPUError::Lost,
214+
DeviceError::Lost => GPUError::Lost(GPUDeviceLostReason::Unknown),
174215
DeviceError::OutOfMemory => GPUError::OutOfMemory,
175216
_ => GPUError::Validation(fmt_err(&err)),
176217
}

0 commit comments

Comments
 (0)