rusty_racer 0.1.14 → 0.1.16
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/ext/rusty_racer/Cargo.toml +1 -1
- data/ext/rusty_racer/src/lib.rs +281 -144
- 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 +238 -50
- data/ext/rusty_racer/src/watchdog.rs +8 -2
- data/lib/rusty_racer/version.rb +1 -1
- metadata +1 -1
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
|
|
@@ -1501,10 +1527,13 @@ fn transfer_out(
|
|
|
1501
1527
|
return;
|
|
1502
1528
|
}
|
|
1503
1529
|
let token = NEXT_TRANSFER_TOKEN.fetch_add(1, Ordering::Relaxed);
|
|
1504
|
-
transfer_registry()
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1530
|
+
transfer_registry().lock().unwrap().insert(
|
|
1531
|
+
token,
|
|
1532
|
+
SendBackingStore {
|
|
1533
|
+
store,
|
|
1534
|
+
shared: false,
|
|
1535
|
+
},
|
|
1536
|
+
);
|
|
1508
1537
|
rv.set(v8::Number::new(scope, token as f64).into());
|
|
1509
1538
|
}
|
|
1510
1539
|
|
|
@@ -1525,10 +1554,13 @@ fn share_out(
|
|
|
1525
1554
|
};
|
|
1526
1555
|
let store = sab.get_backing_store();
|
|
1527
1556
|
let token = NEXT_TRANSFER_TOKEN.fetch_add(1, Ordering::Relaxed);
|
|
1528
|
-
transfer_registry()
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1557
|
+
transfer_registry().lock().unwrap().insert(
|
|
1558
|
+
token,
|
|
1559
|
+
SendBackingStore {
|
|
1560
|
+
store,
|
|
1561
|
+
shared: true,
|
|
1562
|
+
},
|
|
1563
|
+
);
|
|
1532
1564
|
rv.set(v8::Number::new(scope, token as f64).into());
|
|
1533
1565
|
}
|
|
1534
1566
|
|
|
@@ -1671,12 +1703,15 @@ fn new_realm(
|
|
|
1671
1703
|
) -> (v8::Global<v8::Context>, v8::UniqueRef<v8::MicrotaskQueue>) {
|
|
1672
1704
|
// Explicit policy like the isolate's: rusty drives every drain by hand
|
|
1673
1705
|
// (auto_drain / NS.drainMicrotasks), so V8 must never auto-run this queue.
|
|
1674
|
-
let mut queue = v8::MicrotaskQueue::new(
|
|
1706
|
+
let mut queue = v8::MicrotaskQueue::new(scope, v8::MicrotasksPolicy::Explicit);
|
|
1675
1707
|
let fresh = {
|
|
1676
|
-
let context = v8::Context::new(
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1708
|
+
let context = v8::Context::new(
|
|
1709
|
+
scope,
|
|
1710
|
+
v8::ContextOptions {
|
|
1711
|
+
microtask_queue: Some(&mut *queue as *mut _),
|
|
1712
|
+
..Default::default()
|
|
1713
|
+
},
|
|
1714
|
+
);
|
|
1680
1715
|
v8::Global::new(scope, context)
|
|
1681
1716
|
};
|
|
1682
1717
|
// DESIGN DECISION: every realm of an isolate shares ONE security token, so
|
|
@@ -1729,7 +1764,8 @@ fn flush_retiring(scope: &mut v8::PinScope<'_, '_, ()>) {
|
|
|
1729
1764
|
if istate!(scope).realms.retiring.is_empty() {
|
|
1730
1765
|
return;
|
|
1731
1766
|
}
|
|
1732
|
-
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
|
+
{
|
|
1733
1769
|
Some(q) => &**q as *const _,
|
|
1734
1770
|
// The graveyard is created at boot and never cleared, so with entries
|
|
1735
1771
|
// waiting this is unreachable; assert so a future refactor that defers its
|
|
@@ -1769,7 +1805,10 @@ fn install_host_namespace(
|
|
|
1769
1805
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
1770
1806
|
let ns = v8::Object::new(scope);
|
|
1771
1807
|
let members: [(&str, Option<v8::Local<v8::Function>>); 9] = [
|
|
1772
|
-
(
|
|
1808
|
+
(
|
|
1809
|
+
"drainMicrotasks",
|
|
1810
|
+
v8::Function::new(scope, drain_microtasks),
|
|
1811
|
+
),
|
|
1773
1812
|
("contextGlobal", v8::Function::new(scope, context_global)),
|
|
1774
1813
|
("contextOf", v8::Function::new(scope, context_of)),
|
|
1775
1814
|
(
|
|
@@ -1813,8 +1852,8 @@ fn attach_at_path(
|
|
|
1813
1852
|
if part.is_empty() {
|
|
1814
1853
|
return Err(VmError::Runtime(format!("invalid attach name `{name}`")));
|
|
1815
1854
|
}
|
|
1816
|
-
let key =
|
|
1817
|
-
|
|
1855
|
+
let key = v8::String::new(scope, part)
|
|
1856
|
+
.ok_or_else(|| VmError::Runtime("name too large".into()))?;
|
|
1818
1857
|
holder = match holder.get(scope, key.into()) {
|
|
1819
1858
|
Some(v) if v.is_object() => v8::Local::<v8::Object>::try_from(v).unwrap(),
|
|
1820
1859
|
// Don't clobber an existing non-object (e.g. a primitive global that
|
|
@@ -1827,7 +1866,9 @@ fn attach_at_path(
|
|
|
1827
1866
|
_ => {
|
|
1828
1867
|
let obj = v8::Object::new(scope);
|
|
1829
1868
|
if holder.set(scope, key.into(), obj.into()) != Some(true) {
|
|
1830
|
-
return Err(VmError::Runtime(format!(
|
|
1869
|
+
return Err(VmError::Runtime(format!(
|
|
1870
|
+
"`{name}`: cannot create `{part}`"
|
|
1871
|
+
)));
|
|
1831
1872
|
}
|
|
1832
1873
|
obj
|
|
1833
1874
|
}
|
|
@@ -1874,17 +1915,18 @@ fn build_snapshot(code: &str, base: Option<Vec<u8>>, warmup: bool) -> Result<Vec
|
|
|
1874
1915
|
let context = v8::Context::new(scope, Default::default());
|
|
1875
1916
|
{
|
|
1876
1917
|
let cscope = &mut v8::ContextScope::new(scope, context);
|
|
1877
|
-
if !code.is_empty()
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
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
|
+
});
|
|
1888
1930
|
}
|
|
1889
1931
|
}
|
|
1890
1932
|
// Mark the context to deserialize on boot (after the ContextScope is
|
|
@@ -1970,7 +2012,7 @@ impl Isolate {
|
|
|
1970
2012
|
istate!(scope).realms.main_context = Some(main_context);
|
|
1971
2013
|
istate!(scope).realms.main_queue = Some(main_queue);
|
|
1972
2014
|
// The shared graveyard for retired realms' contexts (see V8State).
|
|
1973
|
-
let graveyard = v8::MicrotaskQueue::new(
|
|
2015
|
+
let graveyard = v8::MicrotaskQueue::new(scope, v8::MicrotasksPolicy::Explicit);
|
|
1974
2016
|
istate!(scope).realms.graveyard_queue = Some(graveyard);
|
|
1975
2017
|
}
|
|
1976
2018
|
// Box the OwnedIsolate so it has a STABLE address, then capture a raw ptr
|
|
@@ -1998,7 +2040,10 @@ impl Isolate {
|
|
|
1998
2040
|
let owner_thread = unsafe { Value::from_raw(rb_sys::rb_thread_current()) };
|
|
1999
2041
|
let core = Arc::new_cyclic(|me| Core {
|
|
2000
2042
|
me: me.clone(),
|
|
2001
|
-
shared: Mutex::new(Shared {
|
|
2043
|
+
shared: Mutex::new(Shared {
|
|
2044
|
+
handle,
|
|
2045
|
+
disposed: false,
|
|
2046
|
+
}),
|
|
2002
2047
|
iso_id,
|
|
2003
2048
|
owner: owner_thread.as_raw() as usize,
|
|
2004
2049
|
_owner_root: RootedThread(BoxValue::new(owner_thread)),
|
|
@@ -2058,7 +2103,10 @@ impl Core {
|
|
|
2058
2103
|
));
|
|
2059
2104
|
}
|
|
2060
2105
|
if self.shared.lock().unwrap().disposed {
|
|
2061
|
-
return Err(Error::new(
|
|
2106
|
+
return Err(Error::new(
|
|
2107
|
+
ruby.exception_runtime_error(),
|
|
2108
|
+
"disposed context",
|
|
2109
|
+
));
|
|
2062
2110
|
}
|
|
2063
2111
|
Ok(())
|
|
2064
2112
|
}
|
|
@@ -2265,15 +2313,12 @@ impl Core {
|
|
|
2265
2313
|
// access pattern as swap_instantiate. Does NOT clear it (run() clears at the
|
|
2266
2314
|
// next outermost op) so a nested timeout's outer frame can read it too.
|
|
2267
2315
|
fn peek_timeout_backtrace(&self) -> Option<Vec<String>> {
|
|
2268
|
-
istate!(unsafe { &mut *self.iso_ptr.0 })
|
|
2316
|
+
istate!(unsafe { &mut *self.iso_ptr.0 })
|
|
2317
|
+
.timeout_backtrace
|
|
2318
|
+
.clone()
|
|
2269
2319
|
}
|
|
2270
2320
|
|
|
2271
|
-
fn call_proc(
|
|
2272
|
-
&self,
|
|
2273
|
-
ruby: &Ruby,
|
|
2274
|
-
host_fn_id: usize,
|
|
2275
|
-
args: &[JsVal],
|
|
2276
|
-
) -> Result<JsVal, String> {
|
|
2321
|
+
fn call_proc(&self, ruby: &Ruby, host_fn_id: usize, args: &[JsVal]) -> Result<JsVal, String> {
|
|
2277
2322
|
let proc = {
|
|
2278
2323
|
let procs = self.procs.lock().unwrap();
|
|
2279
2324
|
procs
|
|
@@ -2309,7 +2354,13 @@ impl Core {
|
|
|
2309
2354
|
// Context#call (and call_void). Resolves a dotted function path
|
|
2310
2355
|
// on globalThis and invokes it via v8::Function::call. |void| skips
|
|
2311
2356
|
// marshalling the return for fire-and-forget calls.
|
|
2312
|
-
fn call(
|
|
2357
|
+
fn call(
|
|
2358
|
+
&self,
|
|
2359
|
+
ruby: &Ruby,
|
|
2360
|
+
context_id: i32,
|
|
2361
|
+
args: &[Value],
|
|
2362
|
+
void: bool,
|
|
2363
|
+
) -> Result<Value, Error> {
|
|
2313
2364
|
let Some((name, call_args)) = args.split_first() else {
|
|
2314
2365
|
return Err(Error::new(
|
|
2315
2366
|
ruby.exception_arg_error(),
|
|
@@ -2322,20 +2373,26 @@ impl Core {
|
|
|
2322
2373
|
.map(|v| ruby_to_jsval(*v))
|
|
2323
2374
|
.collect::<Result<_, _>>()?;
|
|
2324
2375
|
|
|
2325
|
-
let reply = self.run(
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
+
)?;
|
|
2332
2386
|
self.reply_value(ruby, reply)
|
|
2333
2387
|
}
|
|
2334
2388
|
|
|
2335
2389
|
fn drain_microtasks(&self, ruby: &Ruby) -> Result<Value, Error> {
|
|
2336
|
-
let reply = self.run(
|
|
2337
|
-
|
|
2338
|
-
|
|
2390
|
+
let reply = self.run(
|
|
2391
|
+
ruby,
|
|
2392
|
+
Request::DrainMicrotasks {
|
|
2393
|
+
timeout_ms: self.default_timeout_ms,
|
|
2394
|
+
},
|
|
2395
|
+
)?;
|
|
2339
2396
|
self.reply_value(ruby, reply)
|
|
2340
2397
|
}
|
|
2341
2398
|
|
|
@@ -2354,10 +2411,19 @@ impl Core {
|
|
|
2354
2411
|
h.aset(ruby.to_symbol("total_heap_size"), s.total_heap_size)?;
|
|
2355
2412
|
h.aset(ruby.to_symbol("heap_size_limit"), s.heap_size_limit)?;
|
|
2356
2413
|
h.aset(ruby.to_symbol("malloced_memory"), s.malloced_memory)?;
|
|
2357
|
-
h.aset(
|
|
2414
|
+
h.aset(
|
|
2415
|
+
ruby.to_symbol("peak_malloced_memory"),
|
|
2416
|
+
s.peak_malloced_memory,
|
|
2417
|
+
)?;
|
|
2358
2418
|
h.aset(ruby.to_symbol("external_memory"), s.external_memory)?;
|
|
2359
|
-
h.aset(
|
|
2360
|
-
|
|
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
|
+
)?;
|
|
2361
2427
|
Ok(h.as_value())
|
|
2362
2428
|
}
|
|
2363
2429
|
|
|
@@ -2376,26 +2442,38 @@ impl Core {
|
|
|
2376
2442
|
filename: String,
|
|
2377
2443
|
timeout_ms: u64,
|
|
2378
2444
|
) -> Result<Value, Error> {
|
|
2379
|
-
let reply = self.run(
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2445
|
+
let reply = self.run(
|
|
2446
|
+
ruby,
|
|
2447
|
+
Request::Eval {
|
|
2448
|
+
context_id,
|
|
2449
|
+
source,
|
|
2450
|
+
filename,
|
|
2451
|
+
timeout_ms,
|
|
2452
|
+
},
|
|
2453
|
+
)?;
|
|
2385
2454
|
self.reply_value(ruby, reply)
|
|
2386
2455
|
}
|
|
2387
2456
|
|
|
2388
|
-
fn attach(
|
|
2457
|
+
fn attach(
|
|
2458
|
+
&self,
|
|
2459
|
+
ruby: &Ruby,
|
|
2460
|
+
context_id: i32,
|
|
2461
|
+
name: String,
|
|
2462
|
+
proc: Proc,
|
|
2463
|
+
) -> Result<Value, Error> {
|
|
2389
2464
|
let host_fn_id = self.procs.lock().unwrap().alloc(ProcSlot {
|
|
2390
2465
|
context_id,
|
|
2391
2466
|
proc: Some(RootedProc(BoxValue::new(proc))),
|
|
2392
2467
|
});
|
|
2393
|
-
let reply = self.run(
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
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
|
+
)?;
|
|
2399
2477
|
self.reply_value(ruby, reply)
|
|
2400
2478
|
}
|
|
2401
2479
|
|
|
@@ -2406,7 +2484,12 @@ impl Core {
|
|
|
2406
2484
|
// (name-tagged) error WITHOUT rolling back earlier entries (see the
|
|
2407
2485
|
// AttachMany arm). On that error path the unused slots are reclaimed at the
|
|
2408
2486
|
// next reset/dispose of the realm, like single attach.
|
|
2409
|
-
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> {
|
|
2410
2493
|
if entries.is_empty() {
|
|
2411
2494
|
return Ok(ruby.qnil().as_value()); // nothing to install, skip the round-trip
|
|
2412
2495
|
}
|
|
@@ -2423,11 +2506,14 @@ impl Core {
|
|
|
2423
2506
|
})
|
|
2424
2507
|
.collect()
|
|
2425
2508
|
};
|
|
2426
|
-
let reply = self.run(
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
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
|
+
)?;
|
|
2431
2517
|
self.reply_value(ruby, reply)
|
|
2432
2518
|
}
|
|
2433
2519
|
|
|
@@ -2475,14 +2561,17 @@ impl Core {
|
|
|
2475
2561
|
produce_cache: bool,
|
|
2476
2562
|
eager: bool,
|
|
2477
2563
|
) -> Result<Compiled, Error> {
|
|
2478
|
-
let reply = self.run(
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
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
|
+
)?;
|
|
2486
2575
|
match reply {
|
|
2487
2576
|
VmReply::ModuleCompiled(Ok(cm)) => Ok(cm),
|
|
2488
2577
|
VmReply::ModuleCompiled(Err(e)) => Err(vm_err(ruby, e)),
|
|
@@ -2517,7 +2606,12 @@ impl Core {
|
|
|
2517
2606
|
// it per import edge (it may compile a dependency lazily — a re-entrant op
|
|
2518
2607
|
// that just recurses into run). A resolver that RAISED is re-raised here with
|
|
2519
2608
|
// its original class.
|
|
2520
|
-
fn instantiate_module(
|
|
2609
|
+
fn instantiate_module(
|
|
2610
|
+
&self,
|
|
2611
|
+
ruby: &Ruby,
|
|
2612
|
+
module_id: i32,
|
|
2613
|
+
resolve: Proc,
|
|
2614
|
+
) -> Result<Value, Error> {
|
|
2521
2615
|
// Guard BEFORE touching the slot via iso_ptr: a foreign-thread or
|
|
2522
2616
|
// post-dispose call must be refused, not deref a freed/foreign isolate.
|
|
2523
2617
|
self.ensure_owner_and_live(ruby)?;
|
|
@@ -2535,10 +2629,13 @@ impl Core {
|
|
|
2535
2629
|
}
|
|
2536
2630
|
|
|
2537
2631
|
fn evaluate_module(&self, ruby: &Ruby, module_id: i32) -> Result<Value, Error> {
|
|
2538
|
-
let reply = self.run(
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2632
|
+
let reply = self.run(
|
|
2633
|
+
ruby,
|
|
2634
|
+
Request::EvaluateModule {
|
|
2635
|
+
module_id,
|
|
2636
|
+
timeout_ms: self.default_timeout_ms,
|
|
2637
|
+
},
|
|
2638
|
+
)?;
|
|
2542
2639
|
self.reply_value(ruby, reply)
|
|
2543
2640
|
}
|
|
2544
2641
|
|
|
@@ -2569,14 +2666,17 @@ impl Core {
|
|
|
2569
2666
|
produce_cache: bool,
|
|
2570
2667
|
eager: bool,
|
|
2571
2668
|
) -> Result<Compiled, Error> {
|
|
2572
|
-
let reply = self.run(
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
+
)?;
|
|
2580
2680
|
match reply {
|
|
2581
2681
|
VmReply::ScriptCompiled(Ok(cs)) => Ok(cs),
|
|
2582
2682
|
VmReply::ScriptCompiled(Err(e)) => Err(vm_err(ruby, e)),
|
|
@@ -2588,10 +2688,13 @@ impl Core {
|
|
|
2588
2688
|
}
|
|
2589
2689
|
|
|
2590
2690
|
fn run_script(&self, ruby: &Ruby, script_id: i32) -> Result<Value, Error> {
|
|
2591
|
-
let reply = self.run(
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2691
|
+
let reply = self.run(
|
|
2692
|
+
ruby,
|
|
2693
|
+
Request::RunScript {
|
|
2694
|
+
script_id,
|
|
2695
|
+
timeout_ms: self.default_timeout_ms,
|
|
2696
|
+
},
|
|
2697
|
+
)?;
|
|
2595
2698
|
self.reply_value(ruby, reply)
|
|
2596
2699
|
}
|
|
2597
2700
|
|
|
@@ -2811,7 +2914,10 @@ impl Context {
|
|
|
2811
2914
|
fn check_live(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
2812
2915
|
// id 0's lifetime is the isolate's; extras also track their own dispose.
|
|
2813
2916
|
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
2814
|
-
return Err(Error::new(
|
|
2917
|
+
return Err(Error::new(
|
|
2918
|
+
ruby.exception_runtime_error(),
|
|
2919
|
+
"disposed context",
|
|
2920
|
+
));
|
|
2815
2921
|
}
|
|
2816
2922
|
Ok(())
|
|
2817
2923
|
}
|
|
@@ -2829,7 +2935,9 @@ impl Context {
|
|
|
2829
2935
|
} else {
|
|
2830
2936
|
timeout_ms
|
|
2831
2937
|
};
|
|
2832
|
-
rb_self
|
|
2938
|
+
rb_self
|
|
2939
|
+
.core
|
|
2940
|
+
.eval_t(ruby, rb_self.id, source, filename, timeout)
|
|
2833
2941
|
}
|
|
2834
2942
|
fn call(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
2835
2943
|
rb_self.check_live(ruby)?;
|
|
@@ -2872,9 +2980,15 @@ impl Context {
|
|
|
2872
2980
|
) -> Result<JsModule, Error> {
|
|
2873
2981
|
rb_self.check_live(ruby)?;
|
|
2874
2982
|
let cache_in = binary_bytes(ruby, cached_data)?;
|
|
2875
|
-
let cm = rb_self
|
|
2876
|
-
|
|
2877
|
-
|
|
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
|
+
)?;
|
|
2878
2992
|
Ok(JsModule {
|
|
2879
2993
|
core: rb_self.core.clone(),
|
|
2880
2994
|
module_id: cm.id,
|
|
@@ -2896,9 +3010,15 @@ impl Context {
|
|
|
2896
3010
|
) -> Result<Script, Error> {
|
|
2897
3011
|
rb_self.check_live(ruby)?;
|
|
2898
3012
|
let cache_in = binary_bytes(ruby, cached_data)?;
|
|
2899
|
-
let cs = rb_self
|
|
2900
|
-
|
|
2901
|
-
|
|
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
|
+
)?;
|
|
2902
3022
|
Ok(Script {
|
|
2903
3023
|
core: rb_self.core.clone(),
|
|
2904
3024
|
script_id: cs.id,
|
|
@@ -2991,7 +3111,10 @@ impl JsModule {
|
|
|
2991
3111
|
// before run's guard) is gone — without this, instantiate after
|
|
2992
3112
|
// iso.dispose is a use-after-free.
|
|
2993
3113
|
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
2994
|
-
return Err(Error::new(
|
|
3114
|
+
return Err(Error::new(
|
|
3115
|
+
ruby.exception_runtime_error(),
|
|
3116
|
+
"disposed module",
|
|
3117
|
+
));
|
|
2995
3118
|
}
|
|
2996
3119
|
Ok(())
|
|
2997
3120
|
}
|
|
@@ -2999,7 +3122,9 @@ impl JsModule {
|
|
|
2999
3122
|
// the lib wrapper). resolver.(specifier, referrer_url) must return a Module.
|
|
3000
3123
|
fn instantiate(ruby: &Ruby, rb_self: &Self, resolver: Proc) -> Result<Value, Error> {
|
|
3001
3124
|
rb_self.check_live(ruby)?;
|
|
3002
|
-
rb_self
|
|
3125
|
+
rb_self
|
|
3126
|
+
.core
|
|
3127
|
+
.instantiate_module(ruby, rb_self.module_id, resolver)
|
|
3003
3128
|
}
|
|
3004
3129
|
fn evaluate(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
|
|
3005
3130
|
rb_self.check_live(ruby)?;
|
|
@@ -3050,7 +3175,10 @@ impl Script {
|
|
|
3050
3175
|
fn check_live(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
3051
3176
|
// Also refuse once the isolate is disposed (see JsModule::check_live).
|
|
3052
3177
|
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
3053
|
-
return Err(Error::new(
|
|
3178
|
+
return Err(Error::new(
|
|
3179
|
+
ruby.exception_runtime_error(),
|
|
3180
|
+
"disposed script",
|
|
3181
|
+
));
|
|
3054
3182
|
}
|
|
3055
3183
|
Ok(())
|
|
3056
3184
|
}
|
|
@@ -3112,7 +3240,10 @@ fn code_cache_from_reply(ruby: &Ruby, reply: VmReply) -> Result<Option<Vec<u8>>,
|
|
|
3112
3240
|
// Read a Ruby cached_data arg as raw bytes, refusing a non-binary string so a
|
|
3113
3241
|
// cache file read without 'rb' (silently transcoded) fails loudly rather than
|
|
3114
3242
|
// being consumed as garbage and rejected with no signal.
|
|
3115
|
-
fn binary_bytes(
|
|
3243
|
+
fn binary_bytes(
|
|
3244
|
+
ruby: &Ruby,
|
|
3245
|
+
cached_data: Option<magnus::RString>,
|
|
3246
|
+
) -> Result<Option<Vec<u8>>, Error> {
|
|
3116
3247
|
match cached_data {
|
|
3117
3248
|
None => Ok(None),
|
|
3118
3249
|
Some(s) => {
|
|
@@ -3324,7 +3455,10 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
3324
3455
|
// JS Map instead of a plain object. It behaves exactly like a Hash (so
|
|
3325
3456
|
// `assert_kind_of Hash` / `h[k]` still work); users may also construct one to
|
|
3326
3457
|
// hand a JS Map to JS. See marshal.rs.
|
|
3327
|
-
module.define_class(
|
|
3458
|
+
module.define_class(
|
|
3459
|
+
"JSMap",
|
|
3460
|
+
ruby.class_object().const_get::<_, magnus::RClass>("Hash")?,
|
|
3461
|
+
)?;
|
|
3328
3462
|
|
|
3329
3463
|
let platform = module.define_module("Platform")?;
|
|
3330
3464
|
platform.define_singleton_method("set_flags!", function!(platform_set_flags, -1))?;
|
|
@@ -3338,6 +3472,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
3338
3472
|
// Observability for the thread-confined lifecycle (see Drop for Core).
|
|
3339
3473
|
module.define_singleton_method("live_isolate_count", function!(live_isolate_count, 0))?;
|
|
3340
3474
|
module.define_singleton_method("leaked_isolate_count", function!(leaked_isolate_count, 0))?;
|
|
3341
|
-
module.define_singleton_method(
|
|
3475
|
+
module.define_singleton_method(
|
|
3476
|
+
"pending_transfer_count",
|
|
3477
|
+
function!(pending_transfer_count, 0),
|
|
3478
|
+
)?;
|
|
3342
3479
|
Ok(())
|
|
3343
3480
|
}
|