Skip to content

Commit f9a68b5

Browse files
Sujay JayakarConvex, Inc.
Sujay Jayakar
authored and
Convex, Inc.
committed
Wire up error handling to isolate2 (#24731)
GitOrigin-RevId: 7214af8b4ad6faf6c204feec96b16d1009052763
1 parent cb31d46 commit f9a68b5

File tree

11 files changed

+566
-468
lines changed

11 files changed

+566
-468
lines changed

crates/isolate/src/environment/helpers/mod.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ mod syscall_trace;
99
pub mod validation;
1010
mod version;
1111

12-
use common::runtime::Runtime;
1312
use deno_core::{
1413
serde_v8,
1514
v8,
@@ -28,10 +27,6 @@ pub use self::{
2827
syscall_trace::SyscallTrace,
2928
version::parse_version,
3029
};
31-
use crate::{
32-
environment::IsolateEnvironment,
33-
execution_scope::ExecutionScope,
34-
};
3530

3631
pub const MAX_LOG_LINE_LENGTH: usize = 32768;
3732
pub const MAX_LOG_LINES: usize = 256;
@@ -65,8 +60,8 @@ pub enum Phase {
6560
Executing,
6661
}
6762

68-
pub fn json_to_v8<'a, 'b: 'a, RT: Runtime, E: IsolateEnvironment<RT>>(
69-
scope: &mut ExecutionScope<'a, 'b, RT, E>,
63+
pub fn json_to_v8<'a>(
64+
scope: &mut v8::HandleScope<'a>,
7065
json: JsonValue,
7166
) -> anyhow::Result<v8::Local<'a, v8::Value>> {
7267
let value_v8 = serde_v8::to_v8(scope, json)?;

crates/isolate/src/environment/helpers/promise.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
use anyhow::anyhow;
2-
use common::{
3-
errors::{
4-
report_error,
5-
JsError,
6-
},
7-
runtime::Runtime,
2+
use common::errors::{
3+
report_error,
4+
JsError,
85
};
96
use deno_core::v8;
107
use errors::ErrorMetadataAnyhowExt;
118
use serde_json::Value as JsonValue;
129

1310
use super::json_to_v8;
14-
use crate::{
15-
environment::IsolateEnvironment,
16-
execution_scope::ExecutionScope,
17-
strings,
18-
};
11+
use crate::strings;
1912

20-
pub fn resolve_promise<RT: Runtime, E: IsolateEnvironment<RT>>(
21-
scope: &mut ExecutionScope<RT, E>,
13+
pub fn resolve_promise(
14+
scope: &mut v8::HandleScope<'_>,
2215
resolver: v8::Global<v8::PromiseResolver>,
2316
result: anyhow::Result<v8::Local<v8::Value>>,
2417
) -> anyhow::Result<()> {
@@ -27,16 +20,16 @@ pub fn resolve_promise<RT: Runtime, E: IsolateEnvironment<RT>>(
2720

2821
// Like `resolve_promise` but returns JS error even when the
2922
// error might have been caused by Convex, not by the user.
30-
pub fn resolve_promise_allow_all_errors<RT: Runtime, E: IsolateEnvironment<RT>>(
31-
scope: &mut ExecutionScope<RT, E>,
23+
pub fn resolve_promise_allow_all_errors(
24+
scope: &mut v8::HandleScope<'_>,
3225
resolver: v8::Global<v8::PromiseResolver>,
3326
result: anyhow::Result<v8::Local<v8::Value>>,
3427
) -> anyhow::Result<()> {
3528
resolve_promise_inner(scope, resolver, result, true)
3629
}
3730

38-
fn resolve_promise_inner<RT: Runtime, E: IsolateEnvironment<RT>>(
39-
scope: &mut ExecutionScope<RT, E>,
31+
fn resolve_promise_inner(
32+
scope: &mut v8::HandleScope<'_>,
4033
resolver: v8::Global<v8::PromiseResolver>,
4134
result: anyhow::Result<v8::Local<v8::Value>>,
4235
allow_all_errors: bool,

crates/isolate/src/error.rs

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use deno_core::{
1010
ModuleSpecifier,
1111
};
1212
use sourcemap::SourceMap;
13+
use value::ConvexValue;
1314

1415
use crate::{
1516
environment::IsolateEnvironment,
@@ -43,6 +44,16 @@ impl<'a, 'b, RT: Runtime, E: IsolateEnvironment<RT>> ExecutionScope<'a, 'b, RT,
4344
Ok(error)
4445
}
4546

47+
fn extract_source_mapped_error(
48+
&mut self,
49+
exception: v8::Local<v8::Value>,
50+
) -> anyhow::Result<JsError> {
51+
let (message, frame_data, custom_data) = extract_source_mapped_error(self, exception)?;
52+
JsError::from_frames(message, frame_data, custom_data, |s| {
53+
self.lookup_source_map(s)
54+
})
55+
}
56+
4657
pub fn lookup_source_map(
4758
&mut self,
4859
specifier: &ModuleSpecifier,
@@ -56,64 +67,59 @@ impl<'a, 'b, RT: Runtime, E: IsolateEnvironment<RT>> ExecutionScope<'a, 'b, RT,
5667
};
5768
Ok(Some(SourceMap::from_slice(source_map.as_bytes())?))
5869
}
70+
}
5971

60-
fn extract_source_mapped_error(
61-
&mut self,
62-
exception: v8::Local<v8::Value>,
63-
) -> anyhow::Result<JsError> {
64-
if !(is_instance_of_error(self, exception)) {
65-
anyhow::bail!("Exception wasn't an instance of `Error`");
66-
}
67-
let exception_obj: v8::Local<v8::Object> = exception.try_into()?;
68-
69-
// Get the message by formatting error.name and error.message.
70-
let name = get_property(self, exception_obj, "name")?
71-
.filter(|v| !v.is_undefined())
72-
.and_then(|m| m.to_string(self))
73-
.map(|s| s.to_rust_string_lossy(self))
74-
.unwrap_or_else(|| "Error".to_string());
75-
let message_prop = get_property(self, exception_obj, "message")?
76-
.filter(|v| !v.is_undefined())
77-
.and_then(|m| m.to_string(self))
78-
.map(|s| s.to_rust_string_lossy(self))
79-
.unwrap_or_else(|| "".to_string());
80-
let message = format_uncaught_error(message_prop, name);
72+
pub fn extract_source_mapped_error(
73+
scope: &mut v8::HandleScope<'_>,
74+
exception: v8::Local<v8::Value>,
75+
) -> anyhow::Result<(String, Vec<FrameData>, Option<ConvexValue>)> {
76+
if !(is_instance_of_error(scope, exception)) {
77+
anyhow::bail!("Exception wasn't an instance of `Error`");
78+
}
79+
let exception_obj: v8::Local<v8::Object> = exception.try_into()?;
8180

82-
// Access the `stack` property to ensure `prepareStackTrace` has been called.
83-
// NOTE if this is the first time accessing `stack`, it will call the op
84-
// `error/stack` which does a redundant source map lookup.
85-
let _stack: v8::Local<v8::String> = get_property(self, exception_obj, "stack")?
86-
.ok_or_else(|| anyhow::anyhow!("Exception was missing the `stack` property"))?
87-
.try_into()?;
81+
// Get the message by formatting error.name and error.message.
82+
let name = get_property(scope, exception_obj, "name")?
83+
.filter(|v| !v.is_undefined())
84+
.and_then(|m| m.to_string(scope))
85+
.map(|s| s.to_rust_string_lossy(scope))
86+
.unwrap_or_else(|| "Error".to_string());
87+
let message_prop = get_property(scope, exception_obj, "message")?
88+
.filter(|v| !v.is_undefined())
89+
.and_then(|m| m.to_string(scope))
90+
.map(|s| s.to_rust_string_lossy(scope))
91+
.unwrap_or_else(|| "".to_string());
92+
let message = format_uncaught_error(message_prop, name);
8893

89-
let frame_data: v8::Local<v8::String> = get_property(self, exception_obj, "__frameData")?
90-
.ok_or_else(|| anyhow::anyhow!("Exception was missing the `__frameData` property"))?
91-
.try_into()?;
92-
let frame_data = to_rust_string(self, &frame_data)?;
93-
let frame_data: Vec<FrameData> = serde_json::from_str(&frame_data)?;
94+
// Access the `stack` property to ensure `prepareStackTrace` has been called.
95+
// NOTE if this is the first time accessing `stack`, it will call the op
96+
// `error/stack` which does a redundant source map lookup.
97+
let _stack: v8::Local<v8::String> = get_property(scope, exception_obj, "stack")?
98+
.ok_or_else(|| anyhow::anyhow!("Exception was missing the `stack` property"))?
99+
.try_into()?;
94100

95-
// error[error.ConvexErrorSymbol] === true
96-
let convex_error_symbol = get_property(self, exception_obj, "ConvexErrorSymbol")?;
97-
let is_convex_error = convex_error_symbol.map_or(false, |symbol| {
98-
exception_obj
99-
.get(self, symbol)
100-
.map_or(false, |v| v.is_true())
101-
});
101+
let frame_data: v8::Local<v8::String> = get_property(scope, exception_obj, "__frameData")?
102+
.ok_or_else(|| anyhow::anyhow!("Exception was missing the `__frameData` property"))?
103+
.try_into()?;
104+
let frame_data = to_rust_string(scope, &frame_data)?;
105+
let frame_data: Vec<FrameData> = serde_json::from_str(&frame_data)?;
102106

103-
let custom_data = if is_convex_error {
104-
let custom_data: v8::Local<v8::String> = get_property(self, exception_obj, "data")?
105-
.ok_or_else(|| {
106-
anyhow::anyhow!("The thrown ConvexError is missing `data` property")
107-
})?
108-
.try_into()?;
109-
Some(to_rust_string(self, &custom_data)?)
110-
} else {
111-
None
112-
};
113-
let (message, custom_data) = deserialize_udf_custom_error(message, custom_data)?;
107+
// error[error.ConvexErrorSymbol] === true
108+
let convex_error_symbol = get_property(scope, exception_obj, "ConvexErrorSymbol")?;
109+
let is_convex_error = convex_error_symbol.map_or(false, |symbol| {
110+
exception_obj
111+
.get(scope, symbol)
112+
.map_or(false, |v| v.is_true())
113+
});
114114

115-
JsError::from_frames(message, frame_data, custom_data, |s| {
116-
self.lookup_source_map(s)
117-
})
118-
}
115+
let custom_data = if is_convex_error {
116+
let custom_data: v8::Local<v8::String> = get_property(scope, exception_obj, "data")?
117+
.ok_or_else(|| anyhow::anyhow!("The thrown ConvexError is missing `data` property"))?
118+
.try_into()?;
119+
Some(to_rust_string(scope, &custom_data)?)
120+
} else {
121+
None
122+
};
123+
let (message, custom_data) = deserialize_udf_custom_error(message, custom_data)?;
124+
Ok((message, frame_data, custom_data))
119125
}

crates/isolate/src/execution_scope.rs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use common::{
1919
types::UdfType,
2020
};
2121
use deno_core::{
22-
serde_v8,
2322
v8::{
2423
self,
2524
HeapStatistics,
@@ -374,33 +373,6 @@ impl<'a, 'b: 'a, RT: Runtime, E: IsolateEnvironment<RT>> ExecutionScope<'a, 'b,
374373
Ok(id)
375374
}
376375

377-
pub fn start_dynamic_import<'s>(
378-
&mut self,
379-
resource_name: v8::Local<'s, v8::Value>,
380-
specifier: v8::Local<'s, v8::String>,
381-
) -> anyhow::Result<v8::Local<'_, v8::Promise>> {
382-
let promise_resolver = v8::PromiseResolver::new(self)
383-
.ok_or_else(|| anyhow::anyhow!("Failed to create v8::PromiseResolver"))?;
384-
385-
let promise = promise_resolver.get_promise(self);
386-
let resolver = v8::Global::new(self, promise_resolver);
387-
388-
let referrer_name: String = serde_v8::from_v8(self, resource_name)?;
389-
let specifier_str = helpers::to_rust_string(self, &specifier)?;
390-
391-
let resolved_specifier = deno_core::resolve_import(&specifier_str, &referrer_name)
392-
.map_err(|e| ErrorMetadata::bad_request("InvalidImport", e.to_string()))?;
393-
394-
let dynamic_imports = self.pending_dynamic_imports_mut();
395-
anyhow::ensure!(
396-
dynamic_imports.allow_dynamic_imports,
397-
"dynamic_import_callback registered without allow_dynamic_imports?"
398-
);
399-
dynamic_imports.push(resolved_specifier, resolver);
400-
401-
Ok(promise)
402-
}
403-
404376
async fn lookup_source(
405377
&mut self,
406378
module_specifier: &ModuleSpecifier,

crates/isolate/src/isolate2/client.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::{
77
};
88

99
use common::{
10-
errors::JsError,
1110
runtime::Runtime,
1211
types::UdfType,
1312
};
@@ -36,38 +35,41 @@ pub enum IsolateThreadRequest {
3635
RegisterModule {
3736
name: ModuleSpecifier,
3837
source: String,
39-
response: oneshot::Sender<Vec<ModuleSpecifier>>,
38+
source_map: Option<String>,
39+
response: oneshot::Sender<anyhow::Result<Vec<ModuleSpecifier>>>,
4040
},
4141
EvaluateModule {
4242
name: ModuleSpecifier,
43-
// XXX: how do we want to pipe through JS errors across threads?
44-
response: oneshot::Sender<()>,
43+
response: oneshot::Sender<anyhow::Result<()>>,
4544
},
4645
StartFunction {
4746
udf_type: UdfType,
4847
module: ModuleSpecifier,
4948
name: String,
5049
args: ConvexObject,
51-
response: oneshot::Sender<(FunctionId, EvaluateResult)>,
50+
response: oneshot::Sender<anyhow::Result<(FunctionId, EvaluateResult)>>,
5251
},
5352
PollFunction {
5453
function_id: FunctionId,
5554
completions: Vec<AsyncSyscallCompletion>,
56-
response: oneshot::Sender<EvaluateResult>,
55+
response: oneshot::Sender<anyhow::Result<EvaluateResult>>,
5756
},
5857
}
5958

6059
#[derive(Debug)]
6160
pub enum EvaluateResult {
62-
Ready {
63-
result: ConvexValue,
64-
outcome: EnvironmentOutcome,
65-
},
61+
Ready(ReadyEvaluateResult),
6662
Pending {
6763
async_syscalls: Vec<PendingAsyncSyscall>,
6864
},
6965
}
7066

67+
#[derive(Debug)]
68+
pub struct ReadyEvaluateResult {
69+
pub result: ConvexValue,
70+
pub outcome: EnvironmentOutcome,
71+
}
72+
7173
#[derive(Debug)]
7274
pub struct PendingAsyncSyscall {
7375
pub promise_id: PromiseId,
@@ -77,7 +79,7 @@ pub struct PendingAsyncSyscall {
7779

7880
pub struct AsyncSyscallCompletion {
7981
pub promise_id: PromiseId,
80-
pub result: Result<JsonValue, JsError>,
82+
pub result: anyhow::Result<String>,
8183
}
8284

8385
pub struct IsolateThreadClient<RT: Runtime> {
@@ -105,7 +107,7 @@ impl<RT: Runtime> IsolateThreadClient<RT> {
105107
pub async fn send<T>(
106108
&mut self,
107109
request: IsolateThreadRequest,
108-
mut rx: oneshot::Receiver<T>,
110+
mut rx: oneshot::Receiver<anyhow::Result<T>>,
109111
) -> anyhow::Result<T> {
110112
if self.user_time_remaining.is_zero() {
111113
anyhow::bail!("User time exhausted");
@@ -136,19 +138,21 @@ impl<RT: Runtime> IsolateThreadClient<RT> {
136138
// Tokio thread to talk to its V8 thread.
137139
drop(permit);
138140

139-
Ok(result?)
141+
result?
140142
}
141143

142144
pub async fn register_module(
143145
&mut self,
144146
name: ModuleSpecifier,
145147
source: String,
148+
source_map: Option<String>,
146149
) -> anyhow::Result<Vec<ModuleSpecifier>> {
147150
let (tx, rx) = oneshot::channel();
148151
self.send(
149152
IsolateThreadRequest::RegisterModule {
150153
name,
151154
source,
155+
source_map,
152156
response: tx,
153157
},
154158
rx,

0 commit comments

Comments
 (0)