rusty_racer 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +13 -8
- data/ext/rusty_racer/Cargo.toml +1 -1
- data/ext/rusty_racer/src/lib.rs +365 -205
- data/ext/rusty_racer/src/marshal.rs +155 -124
- data/ext/rusty_racer/src/ops.rs +410 -272
- data/ext/rusty_racer/src/stack.rs +383 -106
- data/ext/rusty_racer/src/watchdog.rs +8 -2
- data/lib/rusty_racer/version.rb +1 -1
- metadata +2 -2
data/ext/rusty_racer/src/lib.rs
CHANGED
|
@@ -46,18 +46,18 @@ use std::sync::{Arc, Mutex, Once, Weak};
|
|
|
46
46
|
use magnus::block::Proc;
|
|
47
47
|
use magnus::value::{BoxValue, ReprValue};
|
|
48
48
|
use magnus::{
|
|
49
|
-
|
|
49
|
+
Error, Exception, ExceptionClass, RHash, Ruby, TryConvert, Value, function, method, prelude::*,
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
mod marshal;
|
|
53
|
-
use marshal::{js_to_jsval, jsval_to_js, jsval_to_ruby, ruby_to_jsval
|
|
53
|
+
use marshal::{JsVal, js_to_jsval, jsval_to_js, jsval_to_ruby, ruby_to_jsval};
|
|
54
54
|
mod ops;
|
|
55
|
-
use ops::{
|
|
55
|
+
use ops::{Compiled, Request, VmReply, run_source, service_request};
|
|
56
56
|
mod stack;
|
|
57
|
-
use stack::{
|
|
57
|
+
use stack::{STACK_DEBUG, StackScope, current_real_isolate, discover_scan_start_field};
|
|
58
58
|
mod watchdog;
|
|
59
59
|
use watchdog::{
|
|
60
|
-
arm_watchdog, disarm_watchdog, run_js_bracketed, watchdog_loop,
|
|
60
|
+
WATCHDOG_DEBUG, WatchdogShared, arm_watchdog, disarm_watchdog, run_js_bracketed, watchdog_loop,
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
// A Ruby Proc rooted for as long as the Core holds it. BoxValue registers a
|
|
@@ -203,7 +203,11 @@ enum VmError {
|
|
|
203
203
|
// Core::run, once the op has unwound, forces a GC to reclaim and resets the
|
|
204
204
|
// ceiling — see the OOM recovery there. The bump is a no-op-after-the-fact:
|
|
205
205
|
// doubling here, GC + reset after, so the limit keeps protecting later ops.
|
|
206
|
-
unsafe extern "C" fn near_heap_limit_cb(
|
|
206
|
+
unsafe extern "C" fn near_heap_limit_cb(
|
|
207
|
+
data: *mut c_void,
|
|
208
|
+
current_heap_limit: usize,
|
|
209
|
+
initial: usize,
|
|
210
|
+
) -> usize {
|
|
207
211
|
let isolate = unsafe { &mut *(data as *mut v8::Isolate) };
|
|
208
212
|
// get_slot_mut (not istate!): this runs as an extern "C" callback from V8's
|
|
209
213
|
// C++ allocator, where a panic would unwind across the FFI boundary. The slot
|
|
@@ -263,7 +267,10 @@ where
|
|
|
263
267
|
job.r = Some((job.f.take().unwrap())());
|
|
264
268
|
null_mut()
|
|
265
269
|
}
|
|
266
|
-
let mut job = Job::<F, R> {
|
|
270
|
+
let mut job = Job::<F, R> {
|
|
271
|
+
f: Some(f),
|
|
272
|
+
r: None,
|
|
273
|
+
};
|
|
267
274
|
unsafe {
|
|
268
275
|
rb_sys::rb_thread_call_without_gvl(
|
|
269
276
|
Some(run::<F, R>),
|
|
@@ -295,7 +302,10 @@ where
|
|
|
295
302
|
job.r = Some((job.f.take().unwrap())());
|
|
296
303
|
null_mut()
|
|
297
304
|
}
|
|
298
|
-
let mut job = Job::<F, R> {
|
|
305
|
+
let mut job = Job::<F, R> {
|
|
306
|
+
f: Some(f),
|
|
307
|
+
r: None,
|
|
308
|
+
};
|
|
299
309
|
unsafe {
|
|
300
310
|
rb_sys::rb_thread_call_with_gvl(Some(run::<F, R>), &mut job as *mut _ as *mut c_void);
|
|
301
311
|
}
|
|
@@ -433,12 +443,12 @@ fn parse_js_stack(stack: &str) -> Vec<String> {
|
|
|
433
443
|
}
|
|
434
444
|
// "NAME (LOC)" -> "LOC:in 'NAME'". Split on the FIRST " (" so a LOC
|
|
435
445
|
// path that itself contains parentheses stays intact.
|
|
436
|
-
if frame.ends_with(')')
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
446
|
+
if frame.ends_with(')')
|
|
447
|
+
&& let Some(open) = frame.find(" (")
|
|
448
|
+
{
|
|
449
|
+
let name = &frame[..open];
|
|
450
|
+
let loc = &frame[open + 2..frame.len() - 1];
|
|
451
|
+
return Some(format!("{loc}:in '{name}'"));
|
|
442
452
|
}
|
|
443
453
|
Some(frame.to_string())
|
|
444
454
|
})
|
|
@@ -458,21 +468,18 @@ fn capture_js_error(
|
|
|
458
468
|
.map(|e| e.to_rust_string_lossy(scope))
|
|
459
469
|
.unwrap_or_else(|| "unexpected failure".to_string());
|
|
460
470
|
let mut stack_str = None;
|
|
461
|
-
if let Some(e) = exception
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
+
if let Some(e) = exception
|
|
472
|
+
&& let Some(obj) = e.to_object(scope)
|
|
473
|
+
&& let Some(key) = v8::String::new(scope, "stack")
|
|
474
|
+
&& let Some(s) = obj.get(scope, key.into())
|
|
475
|
+
&& s.is_string()
|
|
476
|
+
{
|
|
477
|
+
stack_str = Some(s.to_rust_string_lossy(scope));
|
|
471
478
|
}
|
|
472
|
-
if stack_str.is_none()
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
479
|
+
if stack_str.is_none()
|
|
480
|
+
&& let Some(s) = fallback_stack
|
|
481
|
+
{
|
|
482
|
+
stack_str = Some(s.to_rust_string_lossy(scope));
|
|
476
483
|
}
|
|
477
484
|
let backtrace = stack_str
|
|
478
485
|
.map(|s| parse_js_stack(s.as_str()))
|
|
@@ -487,7 +494,17 @@ fn capture_js_error(
|
|
|
487
494
|
fn module_origin<'s>(scope: &v8::PinScope<'s, '_>, url: &str) -> v8::ScriptOrigin<'s> {
|
|
488
495
|
let name = v8::String::new(scope, url).unwrap();
|
|
489
496
|
v8::ScriptOrigin::new(
|
|
490
|
-
scope,
|
|
497
|
+
scope,
|
|
498
|
+
name.into(),
|
|
499
|
+
0,
|
|
500
|
+
0,
|
|
501
|
+
false,
|
|
502
|
+
-1,
|
|
503
|
+
None,
|
|
504
|
+
false,
|
|
505
|
+
false,
|
|
506
|
+
/*is_module*/ true,
|
|
507
|
+
None,
|
|
491
508
|
)
|
|
492
509
|
}
|
|
493
510
|
|
|
@@ -773,7 +790,7 @@ fn drain_all_realms(scope: &mut v8::PinScope<'_, '_>) {
|
|
|
773
790
|
.collect()
|
|
774
791
|
};
|
|
775
792
|
for q in queues {
|
|
776
|
-
unsafe { (*q).perform_checkpoint(
|
|
793
|
+
unsafe { (*q).perform_checkpoint(scope) };
|
|
777
794
|
}
|
|
778
795
|
}
|
|
779
796
|
|
|
@@ -833,7 +850,10 @@ fn drop_context_artifacts(state: &mut IsolateState, context_id: i32) {
|
|
|
833
850
|
}
|
|
834
851
|
|
|
835
852
|
// A script's (unbound handle, owning context id), for running it in that context.
|
|
836
|
-
fn script_handle(
|
|
853
|
+
fn script_handle(
|
|
854
|
+
state: &IsolateState,
|
|
855
|
+
script_id: i32,
|
|
856
|
+
) -> Option<(v8::Global<v8::UnboundScript>, i32)> {
|
|
837
857
|
state
|
|
838
858
|
.scripts
|
|
839
859
|
.by_id
|
|
@@ -1081,7 +1101,9 @@ fn finish_dynamic_import(
|
|
|
1081
1101
|
Some(exc) => {
|
|
1082
1102
|
resolver.reject(tc, exc);
|
|
1083
1103
|
}
|
|
1084
|
-
None =>
|
|
1104
|
+
None => {
|
|
1105
|
+
reject_with_error(tc, resolver, "failed to link dynamically imported module")
|
|
1106
|
+
}
|
|
1085
1107
|
}
|
|
1086
1108
|
return;
|
|
1087
1109
|
}
|
|
@@ -1097,7 +1119,11 @@ fn finish_dynamic_import(
|
|
|
1097
1119
|
match module.evaluate(tc) {
|
|
1098
1120
|
Some(value) => {
|
|
1099
1121
|
let Ok(eval_promise) = v8::Local::<v8::Promise>::try_from(value) else {
|
|
1100
|
-
reject_with_error(
|
|
1122
|
+
reject_with_error(
|
|
1123
|
+
tc,
|
|
1124
|
+
resolver,
|
|
1125
|
+
"module evaluation did not yield a promise",
|
|
1126
|
+
);
|
|
1101
1127
|
return;
|
|
1102
1128
|
};
|
|
1103
1129
|
// Settle the import() promise from the evaluation promise
|
|
@@ -1229,10 +1255,16 @@ struct Core {
|
|
|
1229
1255
|
// the owner thread.
|
|
1230
1256
|
iso_ptr: IsoPtr,
|
|
1231
1257
|
// Address of V8's conservative-GC-scan stack_start field (see
|
|
1232
|
-
// discover_scan_start_field), or 0 if discovery failed.
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1258
|
+
// discover_scan_start_field), or 0 if discovery failed. StackScope writes the
|
|
1259
|
+
// fiber region top here per fiber op so a GC scan stays mapped. Set once at
|
|
1260
|
+
// creation, then read-only; AtomicUsize for shared &Core access.
|
|
1235
1261
|
scan_start_field: std::sync::atomic::AtomicUsize,
|
|
1262
|
+
// The stack limit currently installed in V8 for this isolate. StackScope
|
|
1263
|
+
// keeps it here because V8 exposes no getter and a nested op running on
|
|
1264
|
+
// another stack has to put the enclosing op's limit back. 0 before the first
|
|
1265
|
+
// op. Owner-thread only, like the isolate itself; AtomicUsize for shared
|
|
1266
|
+
// &Core access.
|
|
1267
|
+
installed_stack_limit: std::sync::atomic::AtomicUsize,
|
|
1236
1268
|
// Re-entry depth for THIS isolate, readable without a scope (the runner needs
|
|
1237
1269
|
// it to choose the scope kind before any scope exists): 0 = top-level op
|
|
1238
1270
|
// (open a fresh HandleScope from iso_ptr); >0 = a host callback is on the V8
|
|
@@ -1495,10 +1527,13 @@ fn transfer_out(
|
|
|
1495
1527
|
return;
|
|
1496
1528
|
}
|
|
1497
1529
|
let token = NEXT_TRANSFER_TOKEN.fetch_add(1, Ordering::Relaxed);
|
|
1498
|
-
transfer_registry()
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1530
|
+
transfer_registry().lock().unwrap().insert(
|
|
1531
|
+
token,
|
|
1532
|
+
SendBackingStore {
|
|
1533
|
+
store,
|
|
1534
|
+
shared: false,
|
|
1535
|
+
},
|
|
1536
|
+
);
|
|
1502
1537
|
rv.set(v8::Number::new(scope, token as f64).into());
|
|
1503
1538
|
}
|
|
1504
1539
|
|
|
@@ -1519,10 +1554,13 @@ fn share_out(
|
|
|
1519
1554
|
};
|
|
1520
1555
|
let store = sab.get_backing_store();
|
|
1521
1556
|
let token = NEXT_TRANSFER_TOKEN.fetch_add(1, Ordering::Relaxed);
|
|
1522
|
-
transfer_registry()
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1557
|
+
transfer_registry().lock().unwrap().insert(
|
|
1558
|
+
token,
|
|
1559
|
+
SendBackingStore {
|
|
1560
|
+
store,
|
|
1561
|
+
shared: true,
|
|
1562
|
+
},
|
|
1563
|
+
);
|
|
1526
1564
|
rv.set(v8::Number::new(scope, token as f64).into());
|
|
1527
1565
|
}
|
|
1528
1566
|
|
|
@@ -1665,12 +1703,15 @@ fn new_realm(
|
|
|
1665
1703
|
) -> (v8::Global<v8::Context>, v8::UniqueRef<v8::MicrotaskQueue>) {
|
|
1666
1704
|
// Explicit policy like the isolate's: rusty drives every drain by hand
|
|
1667
1705
|
// (auto_drain / NS.drainMicrotasks), so V8 must never auto-run this queue.
|
|
1668
|
-
let mut queue = v8::MicrotaskQueue::new(
|
|
1706
|
+
let mut queue = v8::MicrotaskQueue::new(scope, v8::MicrotasksPolicy::Explicit);
|
|
1669
1707
|
let fresh = {
|
|
1670
|
-
let context = v8::Context::new(
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1708
|
+
let context = v8::Context::new(
|
|
1709
|
+
scope,
|
|
1710
|
+
v8::ContextOptions {
|
|
1711
|
+
microtask_queue: Some(&mut *queue as *mut _),
|
|
1712
|
+
..Default::default()
|
|
1713
|
+
},
|
|
1714
|
+
);
|
|
1674
1715
|
v8::Global::new(scope, context)
|
|
1675
1716
|
};
|
|
1676
1717
|
// DESIGN DECISION: every realm of an isolate shares ONE security token, so
|
|
@@ -1723,7 +1764,8 @@ fn flush_retiring(scope: &mut v8::PinScope<'_, '_, ()>) {
|
|
|
1723
1764
|
if istate!(scope).realms.retiring.is_empty() {
|
|
1724
1765
|
return;
|
|
1725
1766
|
}
|
|
1726
|
-
let graveyard: *const v8::MicrotaskQueue = match istate!(scope).realms.graveyard_queue.as_ref()
|
|
1767
|
+
let graveyard: *const v8::MicrotaskQueue = match istate!(scope).realms.graveyard_queue.as_ref()
|
|
1768
|
+
{
|
|
1727
1769
|
Some(q) => &**q as *const _,
|
|
1728
1770
|
// The graveyard is created at boot and never cleared, so with entries
|
|
1729
1771
|
// waiting this is unreachable; assert so a future refactor that defers its
|
|
@@ -1763,7 +1805,10 @@ fn install_host_namespace(
|
|
|
1763
1805
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
1764
1806
|
let ns = v8::Object::new(scope);
|
|
1765
1807
|
let members: [(&str, Option<v8::Local<v8::Function>>); 9] = [
|
|
1766
|
-
(
|
|
1808
|
+
(
|
|
1809
|
+
"drainMicrotasks",
|
|
1810
|
+
v8::Function::new(scope, drain_microtasks),
|
|
1811
|
+
),
|
|
1767
1812
|
("contextGlobal", v8::Function::new(scope, context_global)),
|
|
1768
1813
|
("contextOf", v8::Function::new(scope, context_of)),
|
|
1769
1814
|
(
|
|
@@ -1807,8 +1852,8 @@ fn attach_at_path(
|
|
|
1807
1852
|
if part.is_empty() {
|
|
1808
1853
|
return Err(VmError::Runtime(format!("invalid attach name `{name}`")));
|
|
1809
1854
|
}
|
|
1810
|
-
let key =
|
|
1811
|
-
|
|
1855
|
+
let key = v8::String::new(scope, part)
|
|
1856
|
+
.ok_or_else(|| VmError::Runtime("name too large".into()))?;
|
|
1812
1857
|
holder = match holder.get(scope, key.into()) {
|
|
1813
1858
|
Some(v) if v.is_object() => v8::Local::<v8::Object>::try_from(v).unwrap(),
|
|
1814
1859
|
// Don't clobber an existing non-object (e.g. a primitive global that
|
|
@@ -1821,7 +1866,9 @@ fn attach_at_path(
|
|
|
1821
1866
|
_ => {
|
|
1822
1867
|
let obj = v8::Object::new(scope);
|
|
1823
1868
|
if holder.set(scope, key.into(), obj.into()) != Some(true) {
|
|
1824
|
-
return Err(VmError::Runtime(format!(
|
|
1869
|
+
return Err(VmError::Runtime(format!(
|
|
1870
|
+
"`{name}`: cannot create `{part}`"
|
|
1871
|
+
)));
|
|
1825
1872
|
}
|
|
1826
1873
|
obj
|
|
1827
1874
|
}
|
|
@@ -1868,17 +1915,18 @@ fn build_snapshot(code: &str, base: Option<Vec<u8>>, warmup: bool) -> Result<Vec
|
|
|
1868
1915
|
let context = v8::Context::new(scope, Default::default());
|
|
1869
1916
|
{
|
|
1870
1917
|
let cscope = &mut v8::ContextScope::new(scope, context);
|
|
1871
|
-
if !code.is_empty()
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1918
|
+
if !code.is_empty()
|
|
1919
|
+
&& let Err(e) =
|
|
1920
|
+
run_source(cscope, code, if warmup { "<warmup>" } else { "<snapshot>" })
|
|
1921
|
+
{
|
|
1922
|
+
err = Some(match e {
|
|
1923
|
+
VmError::Parse(m) | VmError::Runtime(m) => m,
|
|
1924
|
+
VmError::JsError { message, .. } => message,
|
|
1925
|
+
VmError::Terminated => "snapshot code was terminated".to_string(),
|
|
1926
|
+
// Unreachable: the snapshot-creator is a separate isolate
|
|
1927
|
+
// that never registers near_heap_limit_cb.
|
|
1928
|
+
VmError::OutOfMemory => "snapshot code ran out of memory".to_string(),
|
|
1929
|
+
});
|
|
1882
1930
|
}
|
|
1883
1931
|
}
|
|
1884
1932
|
// Mark the context to deserialize on boot (after the ContextScope is
|
|
@@ -1964,7 +2012,7 @@ impl Isolate {
|
|
|
1964
2012
|
istate!(scope).realms.main_context = Some(main_context);
|
|
1965
2013
|
istate!(scope).realms.main_queue = Some(main_queue);
|
|
1966
2014
|
// The shared graveyard for retired realms' contexts (see V8State).
|
|
1967
|
-
let graveyard = v8::MicrotaskQueue::new(
|
|
2015
|
+
let graveyard = v8::MicrotaskQueue::new(scope, v8::MicrotasksPolicy::Explicit);
|
|
1968
2016
|
istate!(scope).realms.graveyard_queue = Some(graveyard);
|
|
1969
2017
|
}
|
|
1970
2018
|
// Box the OwnedIsolate so it has a STABLE address, then capture a raw ptr
|
|
@@ -1992,12 +2040,16 @@ impl Isolate {
|
|
|
1992
2040
|
let owner_thread = unsafe { Value::from_raw(rb_sys::rb_thread_current()) };
|
|
1993
2041
|
let core = Arc::new_cyclic(|me| Core {
|
|
1994
2042
|
me: me.clone(),
|
|
1995
|
-
shared: Mutex::new(Shared {
|
|
2043
|
+
shared: Mutex::new(Shared {
|
|
2044
|
+
handle,
|
|
2045
|
+
disposed: false,
|
|
2046
|
+
}),
|
|
1996
2047
|
iso_id,
|
|
1997
2048
|
owner: owner_thread.as_raw() as usize,
|
|
1998
2049
|
_owner_root: RootedThread(BoxValue::new(owner_thread)),
|
|
1999
2050
|
iso_ptr,
|
|
2000
2051
|
scan_start_field: std::sync::atomic::AtomicUsize::new(0),
|
|
2052
|
+
installed_stack_limit: std::sync::atomic::AtomicUsize::new(0),
|
|
2001
2053
|
depth: std::sync::atomic::AtomicU32::new(0),
|
|
2002
2054
|
procs: Mutex::new(ProcTable::default()),
|
|
2003
2055
|
default_timeout_ms: timeout_ms,
|
|
@@ -2051,7 +2103,10 @@ impl Core {
|
|
|
2051
2103
|
));
|
|
2052
2104
|
}
|
|
2053
2105
|
if self.shared.lock().unwrap().disposed {
|
|
2054
|
-
return Err(Error::new(
|
|
2106
|
+
return Err(Error::new(
|
|
2107
|
+
ruby.exception_runtime_error(),
|
|
2108
|
+
"disposed context",
|
|
2109
|
+
));
|
|
2055
2110
|
}
|
|
2056
2111
|
Ok(())
|
|
2057
2112
|
}
|
|
@@ -2084,29 +2139,69 @@ impl Core {
|
|
|
2084
2139
|
// poison the isolate and raise instead of crashing the process.
|
|
2085
2140
|
use std::panic::AssertUnwindSafe;
|
|
2086
2141
|
let reply: Option<VmReply> = without_gvl(|| {
|
|
2087
|
-
|
|
2142
|
+
// iso_ptr is *mut v8::Isolate = *mut NonNull<RealIsolate>; the raw
|
|
2143
|
+
// v8::Isolate* the C++ entry points want is that NonNull's value.
|
|
2144
|
+
let real_isolate = unsafe { *(iso as *const *mut c_void) };
|
|
2145
|
+
// Whether this op has to ENTER the isolate itself. At depth 0 always:
|
|
2146
|
+
// between ops the isolate is exited, so each op enters around its run.
|
|
2147
|
+
//
|
|
2148
|
+
// A re-entrant op (a host callback or module resolver, having
|
|
2149
|
+
// reacquired the GVL to run a proc that issued this op, is on the V8
|
|
2150
|
+
// stack) USUALLY does not: the JS on the stack belongs to THIS
|
|
2151
|
+
// isolate, which is therefore already entered on THIS native thread,
|
|
2152
|
+
// and we bootstrap onto the ambient HandleScope instead.
|
|
2153
|
+
//
|
|
2154
|
+
// But an embedder driving MANY isolates (e.g. capybara-simulated's
|
|
2155
|
+
// windows) can interleave them: a host callback on isolate A runs
|
|
2156
|
+
// Ruby that evals isolate B, whose own callback re-enters A. Now A is
|
|
2157
|
+
// on the V8 stack (depth > 0) yet B — not A — is the isolate
|
|
2158
|
+
// CURRENTLY entered on this thread, so bootstrapping a scope on A and
|
|
2159
|
+
// opening a ContextScope would trip V8's "scope and Context do not
|
|
2160
|
+
// belong to the same Isolate" panic (it checks the scope's isolate
|
|
2161
|
+
// against Isolate::GetCurrent()). Detect that case and properly enter
|
|
2162
|
+
// A on top of B, restoring B on exit.
|
|
2163
|
+
let entered = depth == 0 || real_isolate != current_real_isolate();
|
|
2164
|
+
// Snapshot the ENCLOSING op's conservative-GC-scan start BEFORE
|
|
2165
|
+
// entering: Isolate::Enter re-points it at the native top, so reading
|
|
2166
|
+
// it afterwards would hand StackScope the clobbered value to
|
|
2167
|
+
// "restore" — stranding an enclosing FIBER op with a start on the
|
|
2168
|
+
// native stack, whose next GC walks off the fiber's mapped top and
|
|
2169
|
+
// SEGVs. None at depth 0, where the value in the field belongs to the
|
|
2170
|
+
// previous, already-finished op and so is nothing to preserve.
|
|
2171
|
+
let scan_start_field = self.scan_start_field.load(Ordering::Relaxed);
|
|
2172
|
+
let enclosing_scan_start = if depth > 0 && scan_start_field != 0 {
|
|
2173
|
+
Some(unsafe { *(scan_start_field as *const usize) })
|
|
2174
|
+
} else {
|
|
2175
|
+
None
|
|
2176
|
+
};
|
|
2177
|
+
if entered {
|
|
2088
2178
|
unsafe { (*iso).enter() };
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2179
|
+
}
|
|
2180
|
+
// In-thread: V8 runs on whatever stack the Ruby caller is on — this
|
|
2181
|
+
// native thread's, or a Ruby Fiber's if a host callback resumed one.
|
|
2182
|
+
// Describe that stack to the isolate for the duration of the op, and
|
|
2183
|
+
// put the enclosing op's description back afterwards. Both the
|
|
2184
|
+
// create-time limit (fixed at a shallow, possibly other-thread frame)
|
|
2185
|
+
// and an enclosing op's are stale here, and a stale limit doesn't
|
|
2186
|
+
// merely false-overflow — it makes the resulting throw FATAL. See
|
|
2187
|
+
// StackScope.
|
|
2188
|
+
//
|
|
2189
|
+
// stack_top_marker is a live address ABOVE every V8 frame of this op
|
|
2190
|
+
// (the scope and service_request below run in deeper frames); on a
|
|
2191
|
+
// fiber it becomes V8's conservative-GC-scan start so the scan stays
|
|
2192
|
+
// within the live, mapped stack.
|
|
2193
|
+
let stack_top_marker = 0u8;
|
|
2194
|
+
// Word-align (down): V8 CHECKs the scan start is pointer-aligned.
|
|
2195
|
+
// Down stays above all V8 frames (they're a full frame below).
|
|
2196
|
+
let stack_top = (&stack_top_marker as *const u8 as usize) & !(size_of::<usize>() - 1);
|
|
2197
|
+
let stack = StackScope::enter(
|
|
2198
|
+
real_isolate,
|
|
2199
|
+
scan_start_field,
|
|
2200
|
+
enclosing_scan_start,
|
|
2201
|
+
&self.installed_stack_limit,
|
|
2202
|
+
stack_top,
|
|
2203
|
+
);
|
|
2204
|
+
if depth == 0 {
|
|
2110
2205
|
let mut reply = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
|
2111
2206
|
v8::scope!(let scope, unsafe { &mut *iso });
|
|
2112
2207
|
service_request(scope, request, true)
|
|
@@ -2145,45 +2240,20 @@ impl Core {
|
|
|
2145
2240
|
istate!(iso_ref).oom_fired = false;
|
|
2146
2241
|
reply = reply.map(relabel_oom);
|
|
2147
2242
|
}
|
|
2243
|
+
// Drop the stack description BEFORE popping the isolate — exit()
|
|
2244
|
+
// can leave a different one current. At depth 0 the drop restores
|
|
2245
|
+
// nothing (no enclosing op), so this just releases the scope.
|
|
2246
|
+
drop(stack);
|
|
2148
2247
|
unsafe { (*iso).exit() };
|
|
2149
2248
|
reply
|
|
2150
2249
|
} else {
|
|
2151
|
-
// Re-entrant
|
|
2152
|
-
//
|
|
2153
|
-
//
|
|
2154
|
-
//
|
|
2155
|
-
//
|
|
2156
|
-
// HandleScope without re-entering.
|
|
2157
|
-
//
|
|
2158
|
-
// But an embedder driving MANY isolates (e.g. capybara-simulated's
|
|
2159
|
-
// windows) can interleave them: a host callback on isolate A runs
|
|
2160
|
-
// Ruby that evals isolate B, whose own callback re-enters A. Now A
|
|
2161
|
-
// is on the V8 stack (depth > 0) yet B — not A — is the isolate
|
|
2162
|
-
// CURRENTLY entered on this thread, so bootstrapping a scope on A
|
|
2163
|
-
// and opening a ContextScope would trip V8's "scope and Context do
|
|
2164
|
-
// not belong to the same Isolate" panic (it checks the scope's
|
|
2165
|
-
// isolate against Isolate::GetCurrent()). Detect that case and
|
|
2166
|
-
// properly enter A on top of B, restoring B on exit. Entering an
|
|
2167
|
-
// already-current isolate is also harmless (V8's entered-isolate
|
|
2168
|
-
// stack nests), so this is correct for same-isolate reentry too —
|
|
2169
|
-
// we only pay the enter/exit when a FOREIGN isolate is on top.
|
|
2170
|
-
//
|
|
2171
|
-
// The stack limit + scan-start set at depth 0 are NOT re-pointed
|
|
2172
|
-
// here: reentry runs in DEEPER frames of the SAME stack, so the
|
|
2173
|
-
// depth-0 values still bound it correctly (whichever isolate is
|
|
2174
|
-
// current — each tracks its own limit). The one exception is a
|
|
2175
|
-
// host callback that SWITCHES stacks — e.g. resumes a Ruby Fiber
|
|
2176
|
-
// that itself evals — where the depth-0 (native) settings are
|
|
2177
|
-
// stale for the fiber; that nested-fiber-under-callback case is an
|
|
2178
|
-
// unsupported edge (the realistic fiber path is a depth-0 eval).
|
|
2179
|
-
let foreign = unsafe { *(iso as *const *mut c_void) } != current_real_isolate();
|
|
2180
|
-
if foreign {
|
|
2181
|
-
unsafe { (*iso).enter() };
|
|
2182
|
-
}
|
|
2250
|
+
// Re-entrant. `entered` is true only when a FOREIGN isolate was
|
|
2251
|
+
// on top (see above): that one had no ambient scope of ours under
|
|
2252
|
+
// its entry, so it opens a fresh HandleScope exactly as the
|
|
2253
|
+
// depth-0 path does. Same-isolate reentry bootstraps onto the
|
|
2254
|
+
// ambient one instead.
|
|
2183
2255
|
let reply = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
|
2184
|
-
if
|
|
2185
|
-
// A had no ambient scope under B's entry — open a fresh
|
|
2186
|
-
// HandleScope on A, exactly as the depth-0 path does.
|
|
2256
|
+
if entered {
|
|
2187
2257
|
v8::scope!(let scope, unsafe { &mut *iso });
|
|
2188
2258
|
service_request(scope, request, false)
|
|
2189
2259
|
} else {
|
|
@@ -2195,7 +2265,8 @@ impl Core {
|
|
|
2195
2265
|
// Pop A back off (restoring B as current). Safe even after a
|
|
2196
2266
|
// panic unwind: the scope's Drop ran but left A entered, and
|
|
2197
2267
|
// exit() asserts A == GetCurrent(), which holds here.
|
|
2198
|
-
|
|
2268
|
+
drop(stack);
|
|
2269
|
+
if entered {
|
|
2199
2270
|
unsafe { (*iso).exit() };
|
|
2200
2271
|
}
|
|
2201
2272
|
reply
|
|
@@ -2242,15 +2313,12 @@ impl Core {
|
|
|
2242
2313
|
// access pattern as swap_instantiate. Does NOT clear it (run() clears at the
|
|
2243
2314
|
// next outermost op) so a nested timeout's outer frame can read it too.
|
|
2244
2315
|
fn peek_timeout_backtrace(&self) -> Option<Vec<String>> {
|
|
2245
|
-
istate!(unsafe { &mut *self.iso_ptr.0 })
|
|
2316
|
+
istate!(unsafe { &mut *self.iso_ptr.0 })
|
|
2317
|
+
.timeout_backtrace
|
|
2318
|
+
.clone()
|
|
2246
2319
|
}
|
|
2247
2320
|
|
|
2248
|
-
fn call_proc(
|
|
2249
|
-
&self,
|
|
2250
|
-
ruby: &Ruby,
|
|
2251
|
-
host_fn_id: usize,
|
|
2252
|
-
args: &[JsVal],
|
|
2253
|
-
) -> Result<JsVal, String> {
|
|
2321
|
+
fn call_proc(&self, ruby: &Ruby, host_fn_id: usize, args: &[JsVal]) -> Result<JsVal, String> {
|
|
2254
2322
|
let proc = {
|
|
2255
2323
|
let procs = self.procs.lock().unwrap();
|
|
2256
2324
|
procs
|
|
@@ -2286,7 +2354,13 @@ impl Core {
|
|
|
2286
2354
|
// Context#call (and call_void). Resolves a dotted function path
|
|
2287
2355
|
// on globalThis and invokes it via v8::Function::call. |void| skips
|
|
2288
2356
|
// marshalling the return for fire-and-forget calls.
|
|
2289
|
-
fn call(
|
|
2357
|
+
fn call(
|
|
2358
|
+
&self,
|
|
2359
|
+
ruby: &Ruby,
|
|
2360
|
+
context_id: i32,
|
|
2361
|
+
args: &[Value],
|
|
2362
|
+
void: bool,
|
|
2363
|
+
) -> Result<Value, Error> {
|
|
2290
2364
|
let Some((name, call_args)) = args.split_first() else {
|
|
2291
2365
|
return Err(Error::new(
|
|
2292
2366
|
ruby.exception_arg_error(),
|
|
@@ -2299,20 +2373,26 @@ impl Core {
|
|
|
2299
2373
|
.map(|v| ruby_to_jsval(*v))
|
|
2300
2374
|
.collect::<Result<_, _>>()?;
|
|
2301
2375
|
|
|
2302
|
-
let reply = self.run(
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2376
|
+
let reply = self.run(
|
|
2377
|
+
ruby,
|
|
2378
|
+
Request::Call {
|
|
2379
|
+
context_id,
|
|
2380
|
+
name,
|
|
2381
|
+
args: jsargs,
|
|
2382
|
+
void,
|
|
2383
|
+
timeout_ms: self.default_timeout_ms,
|
|
2384
|
+
},
|
|
2385
|
+
)?;
|
|
2309
2386
|
self.reply_value(ruby, reply)
|
|
2310
2387
|
}
|
|
2311
2388
|
|
|
2312
2389
|
fn drain_microtasks(&self, ruby: &Ruby) -> Result<Value, Error> {
|
|
2313
|
-
let reply = self.run(
|
|
2314
|
-
|
|
2315
|
-
|
|
2390
|
+
let reply = self.run(
|
|
2391
|
+
ruby,
|
|
2392
|
+
Request::DrainMicrotasks {
|
|
2393
|
+
timeout_ms: self.default_timeout_ms,
|
|
2394
|
+
},
|
|
2395
|
+
)?;
|
|
2316
2396
|
self.reply_value(ruby, reply)
|
|
2317
2397
|
}
|
|
2318
2398
|
|
|
@@ -2331,10 +2411,19 @@ impl Core {
|
|
|
2331
2411
|
h.aset(ruby.to_symbol("total_heap_size"), s.total_heap_size)?;
|
|
2332
2412
|
h.aset(ruby.to_symbol("heap_size_limit"), s.heap_size_limit)?;
|
|
2333
2413
|
h.aset(ruby.to_symbol("malloced_memory"), s.malloced_memory)?;
|
|
2334
|
-
h.aset(
|
|
2414
|
+
h.aset(
|
|
2415
|
+
ruby.to_symbol("peak_malloced_memory"),
|
|
2416
|
+
s.peak_malloced_memory,
|
|
2417
|
+
)?;
|
|
2335
2418
|
h.aset(ruby.to_symbol("external_memory"), s.external_memory)?;
|
|
2336
|
-
h.aset(
|
|
2337
|
-
|
|
2419
|
+
h.aset(
|
|
2420
|
+
ruby.to_symbol("number_of_native_contexts"),
|
|
2421
|
+
s.number_of_native_contexts,
|
|
2422
|
+
)?;
|
|
2423
|
+
h.aset(
|
|
2424
|
+
ruby.to_symbol("number_of_detached_contexts"),
|
|
2425
|
+
s.number_of_detached_contexts,
|
|
2426
|
+
)?;
|
|
2338
2427
|
Ok(h.as_value())
|
|
2339
2428
|
}
|
|
2340
2429
|
|
|
@@ -2353,26 +2442,38 @@ impl Core {
|
|
|
2353
2442
|
filename: String,
|
|
2354
2443
|
timeout_ms: u64,
|
|
2355
2444
|
) -> Result<Value, Error> {
|
|
2356
|
-
let reply = self.run(
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2445
|
+
let reply = self.run(
|
|
2446
|
+
ruby,
|
|
2447
|
+
Request::Eval {
|
|
2448
|
+
context_id,
|
|
2449
|
+
source,
|
|
2450
|
+
filename,
|
|
2451
|
+
timeout_ms,
|
|
2452
|
+
},
|
|
2453
|
+
)?;
|
|
2362
2454
|
self.reply_value(ruby, reply)
|
|
2363
2455
|
}
|
|
2364
2456
|
|
|
2365
|
-
fn attach(
|
|
2457
|
+
fn attach(
|
|
2458
|
+
&self,
|
|
2459
|
+
ruby: &Ruby,
|
|
2460
|
+
context_id: i32,
|
|
2461
|
+
name: String,
|
|
2462
|
+
proc: Proc,
|
|
2463
|
+
) -> Result<Value, Error> {
|
|
2366
2464
|
let host_fn_id = self.procs.lock().unwrap().alloc(ProcSlot {
|
|
2367
2465
|
context_id,
|
|
2368
2466
|
proc: Some(RootedProc(BoxValue::new(proc))),
|
|
2369
2467
|
});
|
|
2370
|
-
let reply = self.run(
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2468
|
+
let reply = self.run(
|
|
2469
|
+
ruby,
|
|
2470
|
+
Request::Attach {
|
|
2471
|
+
context_id,
|
|
2472
|
+
name,
|
|
2473
|
+
host_fn_id,
|
|
2474
|
+
timeout_ms: self.default_timeout_ms,
|
|
2475
|
+
},
|
|
2476
|
+
)?;
|
|
2376
2477
|
self.reply_value(ruby, reply)
|
|
2377
2478
|
}
|
|
2378
2479
|
|
|
@@ -2383,7 +2484,12 @@ impl Core {
|
|
|
2383
2484
|
// (name-tagged) error WITHOUT rolling back earlier entries (see the
|
|
2384
2485
|
// AttachMany arm). On that error path the unused slots are reclaimed at the
|
|
2385
2486
|
// next reset/dispose of the realm, like single attach.
|
|
2386
|
-
fn attach_many(
|
|
2487
|
+
fn attach_many(
|
|
2488
|
+
&self,
|
|
2489
|
+
ruby: &Ruby,
|
|
2490
|
+
context_id: i32,
|
|
2491
|
+
entries: Vec<(String, Proc)>,
|
|
2492
|
+
) -> Result<Value, Error> {
|
|
2387
2493
|
if entries.is_empty() {
|
|
2388
2494
|
return Ok(ruby.qnil().as_value()); // nothing to install, skip the round-trip
|
|
2389
2495
|
}
|
|
@@ -2400,11 +2506,14 @@ impl Core {
|
|
|
2400
2506
|
})
|
|
2401
2507
|
.collect()
|
|
2402
2508
|
};
|
|
2403
|
-
let reply = self.run(
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2509
|
+
let reply = self.run(
|
|
2510
|
+
ruby,
|
|
2511
|
+
Request::AttachMany {
|
|
2512
|
+
context_id,
|
|
2513
|
+
entries: named_ids,
|
|
2514
|
+
timeout_ms: self.default_timeout_ms,
|
|
2515
|
+
},
|
|
2516
|
+
)?;
|
|
2408
2517
|
self.reply_value(ruby, reply)
|
|
2409
2518
|
}
|
|
2410
2519
|
|
|
@@ -2452,14 +2561,17 @@ impl Core {
|
|
|
2452
2561
|
produce_cache: bool,
|
|
2453
2562
|
eager: bool,
|
|
2454
2563
|
) -> Result<Compiled, Error> {
|
|
2455
|
-
let reply = self.run(
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2564
|
+
let reply = self.run(
|
|
2565
|
+
ruby,
|
|
2566
|
+
Request::CompileModule {
|
|
2567
|
+
context_id,
|
|
2568
|
+
source,
|
|
2569
|
+
filename,
|
|
2570
|
+
cached_data,
|
|
2571
|
+
produce_cache,
|
|
2572
|
+
eager,
|
|
2573
|
+
},
|
|
2574
|
+
)?;
|
|
2463
2575
|
match reply {
|
|
2464
2576
|
VmReply::ModuleCompiled(Ok(cm)) => Ok(cm),
|
|
2465
2577
|
VmReply::ModuleCompiled(Err(e)) => Err(vm_err(ruby, e)),
|
|
@@ -2494,7 +2606,12 @@ impl Core {
|
|
|
2494
2606
|
// it per import edge (it may compile a dependency lazily — a re-entrant op
|
|
2495
2607
|
// that just recurses into run). A resolver that RAISED is re-raised here with
|
|
2496
2608
|
// its original class.
|
|
2497
|
-
fn instantiate_module(
|
|
2609
|
+
fn instantiate_module(
|
|
2610
|
+
&self,
|
|
2611
|
+
ruby: &Ruby,
|
|
2612
|
+
module_id: i32,
|
|
2613
|
+
resolve: Proc,
|
|
2614
|
+
) -> Result<Value, Error> {
|
|
2498
2615
|
// Guard BEFORE touching the slot via iso_ptr: a foreign-thread or
|
|
2499
2616
|
// post-dispose call must be refused, not deref a freed/foreign isolate.
|
|
2500
2617
|
self.ensure_owner_and_live(ruby)?;
|
|
@@ -2512,10 +2629,13 @@ impl Core {
|
|
|
2512
2629
|
}
|
|
2513
2630
|
|
|
2514
2631
|
fn evaluate_module(&self, ruby: &Ruby, module_id: i32) -> Result<Value, Error> {
|
|
2515
|
-
let reply = self.run(
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2632
|
+
let reply = self.run(
|
|
2633
|
+
ruby,
|
|
2634
|
+
Request::EvaluateModule {
|
|
2635
|
+
module_id,
|
|
2636
|
+
timeout_ms: self.default_timeout_ms,
|
|
2637
|
+
},
|
|
2638
|
+
)?;
|
|
2519
2639
|
self.reply_value(ruby, reply)
|
|
2520
2640
|
}
|
|
2521
2641
|
|
|
@@ -2546,14 +2666,17 @@ impl Core {
|
|
|
2546
2666
|
produce_cache: bool,
|
|
2547
2667
|
eager: bool,
|
|
2548
2668
|
) -> Result<Compiled, Error> {
|
|
2549
|
-
let reply = self.run(
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2669
|
+
let reply = self.run(
|
|
2670
|
+
ruby,
|
|
2671
|
+
Request::CompileScript {
|
|
2672
|
+
context_id,
|
|
2673
|
+
source,
|
|
2674
|
+
filename,
|
|
2675
|
+
cached_data,
|
|
2676
|
+
produce_cache,
|
|
2677
|
+
eager,
|
|
2678
|
+
},
|
|
2679
|
+
)?;
|
|
2557
2680
|
match reply {
|
|
2558
2681
|
VmReply::ScriptCompiled(Ok(cs)) => Ok(cs),
|
|
2559
2682
|
VmReply::ScriptCompiled(Err(e)) => Err(vm_err(ruby, e)),
|
|
@@ -2565,10 +2688,13 @@ impl Core {
|
|
|
2565
2688
|
}
|
|
2566
2689
|
|
|
2567
2690
|
fn run_script(&self, ruby: &Ruby, script_id: i32) -> Result<Value, Error> {
|
|
2568
|
-
let reply = self.run(
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2691
|
+
let reply = self.run(
|
|
2692
|
+
ruby,
|
|
2693
|
+
Request::RunScript {
|
|
2694
|
+
script_id,
|
|
2695
|
+
timeout_ms: self.default_timeout_ms,
|
|
2696
|
+
},
|
|
2697
|
+
)?;
|
|
2572
2698
|
self.reply_value(ruby, reply)
|
|
2573
2699
|
}
|
|
2574
2700
|
|
|
@@ -2788,7 +2914,10 @@ impl Context {
|
|
|
2788
2914
|
fn check_live(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
2789
2915
|
// id 0's lifetime is the isolate's; extras also track their own dispose.
|
|
2790
2916
|
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
2791
|
-
return Err(Error::new(
|
|
2917
|
+
return Err(Error::new(
|
|
2918
|
+
ruby.exception_runtime_error(),
|
|
2919
|
+
"disposed context",
|
|
2920
|
+
));
|
|
2792
2921
|
}
|
|
2793
2922
|
Ok(())
|
|
2794
2923
|
}
|
|
@@ -2806,7 +2935,9 @@ impl Context {
|
|
|
2806
2935
|
} else {
|
|
2807
2936
|
timeout_ms
|
|
2808
2937
|
};
|
|
2809
|
-
rb_self
|
|
2938
|
+
rb_self
|
|
2939
|
+
.core
|
|
2940
|
+
.eval_t(ruby, rb_self.id, source, filename, timeout)
|
|
2810
2941
|
}
|
|
2811
2942
|
fn call(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
2812
2943
|
rb_self.check_live(ruby)?;
|
|
@@ -2849,9 +2980,15 @@ impl Context {
|
|
|
2849
2980
|
) -> Result<JsModule, Error> {
|
|
2850
2981
|
rb_self.check_live(ruby)?;
|
|
2851
2982
|
let cache_in = binary_bytes(ruby, cached_data)?;
|
|
2852
|
-
let cm = rb_self
|
|
2853
|
-
|
|
2854
|
-
|
|
2983
|
+
let cm = rb_self.core.compile_module(
|
|
2984
|
+
ruby,
|
|
2985
|
+
rb_self.id,
|
|
2986
|
+
source,
|
|
2987
|
+
filename,
|
|
2988
|
+
cache_in,
|
|
2989
|
+
produce_cache,
|
|
2990
|
+
eager,
|
|
2991
|
+
)?;
|
|
2855
2992
|
Ok(JsModule {
|
|
2856
2993
|
core: rb_self.core.clone(),
|
|
2857
2994
|
module_id: cm.id,
|
|
@@ -2873,9 +3010,15 @@ impl Context {
|
|
|
2873
3010
|
) -> Result<Script, Error> {
|
|
2874
3011
|
rb_self.check_live(ruby)?;
|
|
2875
3012
|
let cache_in = binary_bytes(ruby, cached_data)?;
|
|
2876
|
-
let cs = rb_self
|
|
2877
|
-
|
|
2878
|
-
|
|
3013
|
+
let cs = rb_self.core.compile_script(
|
|
3014
|
+
ruby,
|
|
3015
|
+
rb_self.id,
|
|
3016
|
+
source,
|
|
3017
|
+
filename,
|
|
3018
|
+
cache_in,
|
|
3019
|
+
produce_cache,
|
|
3020
|
+
eager,
|
|
3021
|
+
)?;
|
|
2879
3022
|
Ok(Script {
|
|
2880
3023
|
core: rb_self.core.clone(),
|
|
2881
3024
|
script_id: cs.id,
|
|
@@ -2968,7 +3111,10 @@ impl JsModule {
|
|
|
2968
3111
|
// before run's guard) is gone — without this, instantiate after
|
|
2969
3112
|
// iso.dispose is a use-after-free.
|
|
2970
3113
|
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
2971
|
-
return Err(Error::new(
|
|
3114
|
+
return Err(Error::new(
|
|
3115
|
+
ruby.exception_runtime_error(),
|
|
3116
|
+
"disposed module",
|
|
3117
|
+
));
|
|
2972
3118
|
}
|
|
2973
3119
|
Ok(())
|
|
2974
3120
|
}
|
|
@@ -2976,7 +3122,9 @@ impl JsModule {
|
|
|
2976
3122
|
// the lib wrapper). resolver.(specifier, referrer_url) must return a Module.
|
|
2977
3123
|
fn instantiate(ruby: &Ruby, rb_self: &Self, resolver: Proc) -> Result<Value, Error> {
|
|
2978
3124
|
rb_self.check_live(ruby)?;
|
|
2979
|
-
rb_self
|
|
3125
|
+
rb_self
|
|
3126
|
+
.core
|
|
3127
|
+
.instantiate_module(ruby, rb_self.module_id, resolver)
|
|
2980
3128
|
}
|
|
2981
3129
|
fn evaluate(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
2982
3130
|
rb_self.check_live(ruby)?;
|
|
@@ -3027,7 +3175,10 @@ impl Script {
|
|
|
3027
3175
|
fn check_live(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
3028
3176
|
// Also refuse once the isolate is disposed (see JsModule::check_live).
|
|
3029
3177
|
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
3030
|
-
return Err(Error::new(
|
|
3178
|
+
return Err(Error::new(
|
|
3179
|
+
ruby.exception_runtime_error(),
|
|
3180
|
+
"disposed script",
|
|
3181
|
+
));
|
|
3031
3182
|
}
|
|
3032
3183
|
Ok(())
|
|
3033
3184
|
}
|
|
@@ -3089,7 +3240,10 @@ fn code_cache_from_reply(ruby: &Ruby, reply: VmReply) -> Result<Option<Vec<u8>>,
|
|
|
3089
3240
|
// Read a Ruby cached_data arg as raw bytes, refusing a non-binary string so a
|
|
3090
3241
|
// cache file read without 'rb' (silently transcoded) fails loudly rather than
|
|
3091
3242
|
// being consumed as garbage and rejected with no signal.
|
|
3092
|
-
fn binary_bytes(
|
|
3243
|
+
fn binary_bytes(
|
|
3244
|
+
ruby: &Ruby,
|
|
3245
|
+
cached_data: Option<magnus::RString>,
|
|
3246
|
+
) -> Result<Option<Vec<u8>>, Error> {
|
|
3093
3247
|
match cached_data {
|
|
3094
3248
|
None => Ok(None),
|
|
3095
3249
|
Some(s) => {
|
|
@@ -3301,7 +3455,10 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
3301
3455
|
// JS Map instead of a plain object. It behaves exactly like a Hash (so
|
|
3302
3456
|
// `assert_kind_of Hash` / `h[k]` still work); users may also construct one to
|
|
3303
3457
|
// hand a JS Map to JS. See marshal.rs.
|
|
3304
|
-
module.define_class(
|
|
3458
|
+
module.define_class(
|
|
3459
|
+
"JSMap",
|
|
3460
|
+
ruby.class_object().const_get::<_, magnus::RClass>("Hash")?,
|
|
3461
|
+
)?;
|
|
3305
3462
|
|
|
3306
3463
|
let platform = module.define_module("Platform")?;
|
|
3307
3464
|
platform.define_singleton_method("set_flags!", function!(platform_set_flags, -1))?;
|
|
@@ -3315,6 +3472,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
3315
3472
|
// Observability for the thread-confined lifecycle (see Drop for Core).
|
|
3316
3473
|
module.define_singleton_method("live_isolate_count", function!(live_isolate_count, 0))?;
|
|
3317
3474
|
module.define_singleton_method("leaked_isolate_count", function!(leaked_isolate_count, 0))?;
|
|
3318
|
-
module.define_singleton_method(
|
|
3475
|
+
module.define_singleton_method(
|
|
3476
|
+
"pending_transfer_count",
|
|
3477
|
+
function!(pending_transfer_count, 0),
|
|
3478
|
+
)?;
|
|
3319
3479
|
Ok(())
|
|
3320
3480
|
}
|