rusty_racer 0.2.5 → 0.2.6
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/CHANGELOG.md +60 -0
- data/README.md +62 -0
- data/ext/rusty_racer/Cargo.toml +6 -1
- data/ext/rusty_racer/src/lib.rs +267 -77
- data/ext/rusty_racer/src/marshal.rs +356 -117
- data/ext/rusty_racer/src/ops.rs +244 -89
- data/lib/rusty_racer/version.rb +1 -1
- metadata +3 -2
data/ext/rusty_racer/src/lib.rs
CHANGED
|
@@ -93,8 +93,8 @@ use watchdog::{
|
|
|
93
93
|
// SAFETY of the manual Send: the !Send contents (and BoxValue's drop, which
|
|
94
94
|
// calls rb_gc_unregister_address) only run under the GVL. Two conventions
|
|
95
95
|
// keep that true — breaking either is silent UB, so don't:
|
|
96
|
-
// - Arc<Core> lives ONLY in the
|
|
97
|
-
// Module, Script). Never clone it into the V8/watchdog threads, or the
|
|
96
|
+
// - Arc<Core> lives ONLY in the five TypedData wrappers (Isolate, Context,
|
|
97
|
+
// Module, Script, Function). Never clone it into the V8/watchdog threads, or the
|
|
98
98
|
// last drop could run off a Ruby thread.
|
|
99
99
|
// - the wrappers must not set free_immediately: their dfree (and so Core's
|
|
100
100
|
// drop) has to run outside the GC sweep, where Ruby APIs are forbidden.
|
|
@@ -293,6 +293,8 @@ enum VmError {
|
|
|
293
293
|
},
|
|
294
294
|
Terminated, // watchdog/stop -> RustyRacer::ScriptTerminatedError
|
|
295
295
|
OutOfMemory, // memory_limit hit -> RustyRacer::V8OutOfMemoryError
|
|
296
|
+
// A handle whose realm was reset or disposed under it -> RustyRacer::DisposedError
|
|
297
|
+
Disposed(String),
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
// V8's near-heap-limit callback (registered on EVERY isolate). V8 calls this,
|
|
@@ -869,7 +871,8 @@ fn take_thrown<'s>(
|
|
|
869
871
|
|
|
870
872
|
// ---------------------------------------------------------------------------
|
|
871
873
|
// ES modules: V8's raw compile/instantiate/evaluate steps, with the embedder
|
|
872
|
-
// owning the url->Module registry
|
|
874
|
+
// owning the url->Module registry and the resolve policy (ModuleReg is only the
|
|
875
|
+
// binding's id and url bookkeeping).
|
|
873
876
|
// ---------------------------------------------------------------------------
|
|
874
877
|
fn module_origin<'s>(scope: &v8::PinScope<'s, '_>, url: &str) -> v8::ScriptOrigin<'s> {
|
|
875
878
|
let name = v8::String::new(scope, url).unwrap();
|
|
@@ -888,18 +891,47 @@ fn module_origin<'s>(scope: &v8::PinScope<'s, '_>, url: &str) -> v8::ScriptOrigi
|
|
|
888
891
|
)
|
|
889
892
|
}
|
|
890
893
|
|
|
891
|
-
// Registry for the thin compile_module/instantiate API
|
|
892
|
-
// addressed by an id, with its url kept for the resolve round-trip and a
|
|
893
|
-
// hash bucket to map a referrer Local<Module> back to its id.
|
|
894
|
+
// Registry for the thin compile_module/instantiate API.
|
|
894
895
|
#[derive(Default)]
|
|
895
896
|
struct ModuleReg {
|
|
896
|
-
// id -> (module,
|
|
897
|
-
// a module is bound to the v8::Context it
|
|
898
|
-
|
|
899
|
-
|
|
897
|
+
// id -> (module, owning context id): what a Module wrapper's ops address.
|
|
898
|
+
// The context id is needed because a module is bound to the v8::Context it
|
|
899
|
+
// was compiled in. Strong, and gone once the wrapper is disposed or collected
|
|
900
|
+
// (Core.released).
|
|
901
|
+
by_id: HashMap<i32, (v8::Global<v8::Module>, i32)>,
|
|
902
|
+
// identity hash -> (module, url): a Local<Module> V8 hands a callback
|
|
903
|
+
// (import.meta, a referrer being linked) back to the url it was compiled
|
|
904
|
+
// with. WEAK, so a module V8 still keeps — a dependency linked into a live
|
|
905
|
+
// graph, a function of a disposed realm's module another realm holds —
|
|
906
|
+
// answers whether its wrapper (or its realm) is still around or not, and
|
|
907
|
+
// one V8 collected is pruned instead of pinned (`add_url`).
|
|
908
|
+
urls: HashMap<i32, Vec<(v8::Weak<v8::Module>, String)>>,
|
|
909
|
+
// `add_url`'s amortization: entries added since the last prune, and the
|
|
910
|
+
// entries that prune left.
|
|
911
|
+
added_since_prune: usize,
|
|
912
|
+
kept_at_prune: usize,
|
|
900
913
|
next_id: i32,
|
|
901
914
|
}
|
|
902
915
|
|
|
916
|
+
impl ModuleReg {
|
|
917
|
+
// Index |module|'s url, first pruning the entries of collected modules once
|
|
918
|
+
// as many have been added since the last prune as it left — a scan
|
|
919
|
+
// amortized O(1) per compile, keeping the index within about twice the
|
|
920
|
+
// modules alive.
|
|
921
|
+
fn add_url(&mut self, hash: i32, module: v8::Weak<v8::Module>, url: String) {
|
|
922
|
+
if self.added_since_prune > self.kept_at_prune {
|
|
923
|
+
self.urls.retain(|_, bucket| {
|
|
924
|
+
bucket.retain(|(module, _)| !module.is_empty());
|
|
925
|
+
!bucket.is_empty()
|
|
926
|
+
});
|
|
927
|
+
self.kept_at_prune = self.urls.values().map(Vec::len).sum();
|
|
928
|
+
self.added_since_prune = 0;
|
|
929
|
+
}
|
|
930
|
+
self.added_since_prune += 1;
|
|
931
|
+
self.urls.entry(hash).or_default().push((module, url));
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
903
935
|
// Classic compiled scripts: id -> (unbound script, owning context id). An
|
|
904
936
|
// UnboundScript is context-independent, but we run it in the context it was
|
|
905
937
|
// compiled in (and reset/dispose of that context drops it).
|
|
@@ -909,6 +941,39 @@ struct ScriptReg {
|
|
|
909
941
|
next_id: i32,
|
|
910
942
|
}
|
|
911
943
|
|
|
944
|
+
// Context#function's handles: id -> the function its path resolved to, the
|
|
945
|
+
// holder it was read off (its `this`), and the realm it was resolved in. The
|
|
946
|
+
// Globals live HERE, not in the Ruby wrapper, so a reset or disposed realm drops
|
|
947
|
+
// them (drop_context_artifacts) whatever wrappers Ruby still holds — a handle
|
|
948
|
+
// must not keep its realm's v8::Context alive — and so they are only ever
|
|
949
|
+
// dropped on the owner thread with the isolate entered, which a wrapper's GC
|
|
950
|
+
// free can't promise. A wrapper that is disposed or collected queues its id on
|
|
951
|
+
// Core.released instead, and the next outermost request boundary removes it
|
|
952
|
+
// here. Ids are never reused, so a stale id can only miss.
|
|
953
|
+
//
|
|
954
|
+
// A path may lead into ANOTHER realm (`frames[0].fn`): the entry belongs to the
|
|
955
|
+
// realm it was resolved in, so it holds that other realm's function until this
|
|
956
|
+
// one is reset or the handle is released — as any reference this realm keeps
|
|
957
|
+
// to it would.
|
|
958
|
+
#[derive(Default)]
|
|
959
|
+
struct FunctionReg {
|
|
960
|
+
by_id: HashMap<u64, FunctionEntry>,
|
|
961
|
+
next_id: u64,
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
struct FunctionEntry {
|
|
965
|
+
function: v8::Global<v8::Function>,
|
|
966
|
+
receiver: v8::Global<v8::Value>,
|
|
967
|
+
context_id: i32,
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// A handle whose Ruby wrapper let go of it, queued on Core.released.
|
|
971
|
+
enum Released {
|
|
972
|
+
Function(u64),
|
|
973
|
+
Module(i32),
|
|
974
|
+
Script(i32),
|
|
975
|
+
}
|
|
976
|
+
|
|
912
977
|
// The V8 thread's realm registry: the main context (id 0, swappable by reset),
|
|
913
978
|
// the extra realms from create_context, and the host-namespace name to
|
|
914
979
|
// re-install on fresh realms. Thread-local (like MODULES/SCRIPTS) so
|
|
@@ -993,6 +1058,7 @@ struct IsolateState {
|
|
|
993
1058
|
realms: V8State,
|
|
994
1059
|
modules: ModuleReg,
|
|
995
1060
|
scripts: ScriptReg,
|
|
1061
|
+
functions: FunctionReg,
|
|
996
1062
|
active_realms: Vec<i32>,
|
|
997
1063
|
instantiating: bool,
|
|
998
1064
|
watchdog_fired: bool,
|
|
@@ -1046,6 +1112,7 @@ impl IsolateState {
|
|
|
1046
1112
|
},
|
|
1047
1113
|
modules: ModuleReg::default(),
|
|
1048
1114
|
scripts: ScriptReg::default(),
|
|
1115
|
+
functions: FunctionReg::default(),
|
|
1049
1116
|
active_realms: Vec::new(),
|
|
1050
1117
|
instantiating: false,
|
|
1051
1118
|
watchdog_fired: false,
|
|
@@ -1204,23 +1271,19 @@ fn auto_drain(scope: &mut v8::PinScope<'_, '_>, outermost: bool) {
|
|
|
1204
1271
|
}
|
|
1205
1272
|
}
|
|
1206
1273
|
|
|
1207
|
-
// Drop every module
|
|
1208
|
-
// going away — on reset or dispose — so those handles are now
|
|
1274
|
+
// Drop every module, script and function handle of `context_id` (its
|
|
1275
|
+
// v8::Context is going away — on reset or dispose — so those handles are now
|
|
1276
|
+
// dead, and a function's Globals would otherwise keep that context alive).
|
|
1209
1277
|
fn drop_context_artifacts(state: &mut IsolateState, context_id: i32) {
|
|
1210
1278
|
let m = &mut state.modules;
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
.filter(|(_, (_, _, cid))| *cid == context_id)
|
|
1215
|
-
.map(|(id, _)| *id)
|
|
1216
|
-
.collect();
|
|
1217
|
-
for id in dead {
|
|
1218
|
-
m.by_id.remove(&id);
|
|
1219
|
-
for bucket in m.by_hash.values_mut() {
|
|
1220
|
-
bucket.retain(|(_, mid)| *mid != id);
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1279
|
+
// (Not its url index: weak, it pins nothing, and a module of this realm that
|
|
1280
|
+
// another realm still reaches keeps its import.meta.url.)
|
|
1281
|
+
m.by_id.retain(|_, (_, cid)| *cid != context_id);
|
|
1223
1282
|
state.scripts.by_id.retain(|_, (_, cid)| *cid != context_id);
|
|
1283
|
+
state
|
|
1284
|
+
.functions
|
|
1285
|
+
.by_id
|
|
1286
|
+
.retain(|_, entry| entry.context_id != context_id);
|
|
1224
1287
|
// A promise-reject recorder created in this context is unusable now.
|
|
1225
1288
|
if state
|
|
1226
1289
|
.realms
|
|
@@ -1244,24 +1307,21 @@ fn script_handle(
|
|
|
1244
1307
|
.map(|(g, cid)| (g.clone(), *cid))
|
|
1245
1308
|
}
|
|
1246
1309
|
|
|
1247
|
-
// A
|
|
1248
|
-
//
|
|
1249
|
-
// url for the resolve round-trip and to fill
|
|
1310
|
+
// A compiled module's url (the filename it was compiled with), by identity in
|
|
1311
|
+
// the weak url index (ModuleReg.urls) — for as long as V8 keeps the module. Used
|
|
1312
|
+
// to give a referrer its url for the resolve round-trip and to fill
|
|
1313
|
+
// import.meta.url.
|
|
1250
1314
|
fn module_url(scope: &mut v8::PinScope<'_, '_>, module: v8::Local<v8::Module>) -> Option<String> {
|
|
1251
1315
|
let hash = module.get_identity_hash().get();
|
|
1252
|
-
//
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
let bucket = istate!(scope).modules.by_hash.get(&hash)?.clone();
|
|
1256
|
-
let id = bucket
|
|
1257
|
-
.iter()
|
|
1258
|
-
.find(|(g, _)| v8::Local::new(&*scope, g) == module)
|
|
1259
|
-
.map(|(_, id)| *id)?;
|
|
1260
|
-
istate!(scope)
|
|
1316
|
+
// A shared borrow of the slot beside the shared one to_local takes.
|
|
1317
|
+
scope
|
|
1318
|
+
.get_slot::<IsolateState>()?
|
|
1261
1319
|
.modules
|
|
1262
|
-
.
|
|
1263
|
-
.get(&
|
|
1264
|
-
.
|
|
1320
|
+
.urls
|
|
1321
|
+
.get(&hash)?
|
|
1322
|
+
.iter()
|
|
1323
|
+
.find(|(weak, _)| weak.to_local(scope) == Some(module))
|
|
1324
|
+
.map(|(_, url)| url.clone())
|
|
1265
1325
|
}
|
|
1266
1326
|
|
|
1267
1327
|
// V8 calls this the first time a module reads import.meta. Fills in
|
|
@@ -1385,7 +1445,7 @@ fn resolve_imported<'s>(
|
|
|
1385
1445
|
.modules
|
|
1386
1446
|
.by_id
|
|
1387
1447
|
.get(&dep_id)
|
|
1388
|
-
.map(|(g,
|
|
1448
|
+
.map(|(g, cid)| (g.clone(), *cid));
|
|
1389
1449
|
let g = match (here, found) {
|
|
1390
1450
|
(Some(here), Some((g, cid))) if cid == here => g,
|
|
1391
1451
|
(Some(_), Some(_)) => {
|
|
@@ -1462,8 +1522,8 @@ fn dynamic_import_cb<'s>(
|
|
|
1462
1522
|
.modules
|
|
1463
1523
|
.by_id
|
|
1464
1524
|
.get(&id)
|
|
1465
|
-
.filter(|(_,
|
|
1466
|
-
.map(|(g, _
|
|
1525
|
+
.filter(|(_, cid)| Some(*cid) == here)
|
|
1526
|
+
.map(|(g, _)| g.clone());
|
|
1467
1527
|
match g {
|
|
1468
1528
|
Some(g) => {
|
|
1469
1529
|
let module = v8::Local::new(scope, &g);
|
|
@@ -1644,7 +1704,7 @@ fn module_handle(state: &IsolateState, module_id: i32) -> Option<(v8::Global<v8:
|
|
|
1644
1704
|
.modules
|
|
1645
1705
|
.by_id
|
|
1646
1706
|
.get(&module_id)
|
|
1647
|
-
.map(|(g,
|
|
1707
|
+
.map(|(g, cid)| (g.clone(), *cid))
|
|
1648
1708
|
}
|
|
1649
1709
|
|
|
1650
1710
|
// ---------------------------------------------------------------------------
|
|
@@ -1710,6 +1770,14 @@ struct Core {
|
|
|
1710
1770
|
// reach it through Core (via the slot's core_ptr) while a &Core method also
|
|
1711
1771
|
// holds it. reset/dispose releases the slots and recycles them.
|
|
1712
1772
|
procs: Mutex<ProcTable>,
|
|
1773
|
+
// The handles (Function, Module, Script) whose Ruby wrappers were disposed or
|
|
1774
|
+
// collected, for the next outermost request boundary to remove from their
|
|
1775
|
+
// registries (`forget_released_handles`). A wrapper's free may run on any
|
|
1776
|
+
// Ruby thread, outside any op, so all it can do is queue its id here.
|
|
1777
|
+
released: Mutex<Vec<Released>>,
|
|
1778
|
+
// Set with each push there, so the request boundary looks at the lock only
|
|
1779
|
+
// when there is something under it.
|
|
1780
|
+
handles_released: AtomicBool,
|
|
1713
1781
|
// Default per-eval/call timeout (ms); 0 = none. eval(timeout_ms:)'s explicit
|
|
1714
1782
|
// value overrides it. Guards against an in-V8 infinite loop without a watchdog.
|
|
1715
1783
|
default_timeout_ms: u64,
|
|
@@ -1779,10 +1847,22 @@ struct Script {
|
|
|
1779
1847
|
cache_rejected: bool,
|
|
1780
1848
|
}
|
|
1781
1849
|
|
|
1782
|
-
//
|
|
1850
|
+
// Context#function result: a JS function resolved once, with its `this`, kept in
|
|
1851
|
+
// FunctionReg by id — the realm it belongs to rides along so a call enters it
|
|
1852
|
+
// without a lookup.
|
|
1853
|
+
#[derive(TypedData)]
|
|
1854
|
+
#[magnus(class = "RustyRacer::Function", mark)]
|
|
1855
|
+
struct JsFunction {
|
|
1856
|
+
core: Arc<Core>,
|
|
1857
|
+
context_id: i32,
|
|
1858
|
+
function_id: u64,
|
|
1859
|
+
disposed: AtomicBool,
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// The five wrappers that hold an Arc<Core> all mark the isolate's roots, because any
|
|
1783
1863
|
// one of them may be the last one reachable: Ruby code is free to keep only the
|
|
1784
1864
|
// Context a `Isolate#context` handed it, or only a Module, and the host fns must
|
|
1785
|
-
// stay callable through it. Marking the same array
|
|
1865
|
+
// stay callable through it. Marking the same array five times costs nothing (the GC
|
|
1786
1866
|
// stops at an already-marked object); missing it once would free a live proc.
|
|
1787
1867
|
macro_rules! marks_core_roots {
|
|
1788
1868
|
($t:ty) => {
|
|
@@ -1797,6 +1877,7 @@ marks_core_roots!(Isolate);
|
|
|
1797
1877
|
marks_core_roots!(Context);
|
|
1798
1878
|
marks_core_roots!(JsModule);
|
|
1799
1879
|
marks_core_roots!(Script);
|
|
1880
|
+
marks_core_roots!(JsFunction);
|
|
1800
1881
|
|
|
1801
1882
|
// Set true once V8 is initialized; Platform.set_flags! refuses after that
|
|
1802
1883
|
// (flags must be set before V8::initialize), like mini_racer's
|
|
@@ -2323,12 +2404,12 @@ fn install_host_namespace(
|
|
|
2323
2404
|
];
|
|
2324
2405
|
for (member, function) in members {
|
|
2325
2406
|
if let (Some(f), Some(k)) = (function, v8::String::new(scope, member)) {
|
|
2326
|
-
ns.
|
|
2407
|
+
ns.create_data_property(scope, k.into(), f.into());
|
|
2327
2408
|
}
|
|
2328
2409
|
}
|
|
2329
2410
|
if let Some(key) = v8::String::new(scope, name) {
|
|
2330
2411
|
let global = context.global(scope);
|
|
2331
|
-
global.
|
|
2412
|
+
global.create_data_property(scope, key.into(), ns.into());
|
|
2332
2413
|
}
|
|
2333
2414
|
}
|
|
2334
2415
|
|
|
@@ -2428,7 +2509,7 @@ fn build_snapshot(code: &str, base: Option<Vec<u8>>, warmup: bool) -> Result<Vec
|
|
|
2428
2509
|
)
|
|
2429
2510
|
{
|
|
2430
2511
|
err = Some(match e {
|
|
2431
|
-
VmError::Parse(m, _) | VmError::Runtime(m) => m,
|
|
2512
|
+
VmError::Parse(m, _) | VmError::Runtime(m) | VmError::Disposed(m) => m,
|
|
2432
2513
|
VmError::JsError { message, .. } => message,
|
|
2433
2514
|
VmError::Terminated => "snapshot code was terminated".to_string(),
|
|
2434
2515
|
// Unreachable: the snapshot-creator is a separate isolate
|
|
@@ -2565,6 +2646,8 @@ impl Isolate {
|
|
|
2565
2646
|
depth: std::sync::atomic::AtomicU32::new(0),
|
|
2566
2647
|
roots: Opaque::from(roots),
|
|
2567
2648
|
procs: Mutex::new(ProcTable::default()),
|
|
2649
|
+
released: Mutex::new(Vec::new()),
|
|
2650
|
+
handles_released: AtomicBool::new(false),
|
|
2568
2651
|
default_timeout_ms: timeout_ms,
|
|
2569
2652
|
memory_limit,
|
|
2570
2653
|
watchdog,
|
|
@@ -2890,7 +2973,7 @@ impl Core {
|
|
|
2890
2973
|
}
|
|
2891
2974
|
|
|
2892
2975
|
// Show the GC everything this isolate holds. Called from EVERY wrapper's mark
|
|
2893
|
-
// (Isolate, Context, Module, Script all keep an Arc<Core>, and any one of them
|
|
2976
|
+
// (Isolate, Context, Module, Script, Function all keep an Arc<Core>, and any one of them
|
|
2894
2977
|
// may be the last reachable): a wrapper that forgot to would leave a live
|
|
2895
2978
|
// isolate's host fns unmarked. `mark` PINS — an Array marks its own elements
|
|
2896
2979
|
// MOVABLE, and the copies Rust takes out of it (call_proc, root_proc) would go
|
|
@@ -2975,18 +3058,47 @@ impl Core {
|
|
|
2975
3058
|
"call requires a function name",
|
|
2976
3059
|
));
|
|
2977
3060
|
};
|
|
2978
|
-
let name = String::try_convert(*name)?;
|
|
2979
|
-
let jsargs: Vec<JsVal> = call_args
|
|
2980
|
-
.iter()
|
|
2981
|
-
.map(|v| ruby_to_jsval(*v))
|
|
2982
|
-
.collect::<Result<_, _>>()?;
|
|
2983
|
-
|
|
2984
3061
|
let reply = self.run(
|
|
2985
3062
|
ruby,
|
|
2986
3063
|
Request::Call {
|
|
2987
3064
|
context_id,
|
|
2988
|
-
name
|
|
2989
|
-
args:
|
|
3065
|
+
name: String::try_convert(*name)?,
|
|
3066
|
+
args: js_args(call_args)?,
|
|
3067
|
+
void,
|
|
3068
|
+
timeout_ms: self.default_timeout_ms,
|
|
3069
|
+
},
|
|
3070
|
+
)?;
|
|
3071
|
+
self.reply_value(ruby, reply)
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
// Context#function: resolve |path| in the realm once; the handle's id.
|
|
3075
|
+
fn resolve_function(&self, ruby: &Ruby, context_id: i32, path: String) -> Result<u64, Error> {
|
|
3076
|
+
let reply = self.run(
|
|
3077
|
+
ruby,
|
|
3078
|
+
Request::ResolveFunction {
|
|
3079
|
+
context_id,
|
|
3080
|
+
path,
|
|
3081
|
+
timeout_ms: self.default_timeout_ms,
|
|
3082
|
+
},
|
|
3083
|
+
)?;
|
|
3084
|
+
u64::try_convert(self.reply_value(ruby, reply)?)
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// Function#call (and call_void): Context#call's invocation, of a resolved handle.
|
|
3088
|
+
fn call_function(
|
|
3089
|
+
&self,
|
|
3090
|
+
ruby: &Ruby,
|
|
3091
|
+
context_id: i32,
|
|
3092
|
+
function_id: u64,
|
|
3093
|
+
args: &[Value],
|
|
3094
|
+
void: bool,
|
|
3095
|
+
) -> Result<Value, Error> {
|
|
3096
|
+
let reply = self.run(
|
|
3097
|
+
ruby,
|
|
3098
|
+
Request::CallFunction {
|
|
3099
|
+
context_id,
|
|
3100
|
+
function_id,
|
|
3101
|
+
args: js_args(args)?,
|
|
2990
3102
|
void,
|
|
2991
3103
|
timeout_ms: self.default_timeout_ms,
|
|
2992
3104
|
},
|
|
@@ -2994,6 +3106,13 @@ impl Core {
|
|
|
2994
3106
|
self.reply_value(ruby, reply)
|
|
2995
3107
|
}
|
|
2996
3108
|
|
|
3109
|
+
// A wrapper let go of its handle (see `released`). Takes only this lock,
|
|
3110
|
+
// briefly, and allocates no Ruby object — it runs from a GC free.
|
|
3111
|
+
fn release(&self, handle: Released) {
|
|
3112
|
+
self.released.lock().unwrap().push(handle);
|
|
3113
|
+
self.handles_released.store(true, Ordering::Relaxed);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
2997
3116
|
fn drain_microtasks(&self, ruby: &Ruby) -> Result<Value, Error> {
|
|
2998
3117
|
let reply = self.run(
|
|
2999
3118
|
ruby,
|
|
@@ -3302,11 +3421,6 @@ impl Core {
|
|
|
3302
3421
|
self.reply_value(ruby, reply)
|
|
3303
3422
|
}
|
|
3304
3423
|
|
|
3305
|
-
fn dispose_module(&self, ruby: &Ruby, module_id: i32) -> Result<(), Error> {
|
|
3306
|
-
let reply = self.run(ruby, Request::DisposeModule { module_id })?;
|
|
3307
|
-
self.reply_value(ruby, reply).map(|_| ())
|
|
3308
|
-
}
|
|
3309
|
-
|
|
3310
3424
|
// Classic script: compile, run, dispose.
|
|
3311
3425
|
#[allow(clippy::too_many_arguments)]
|
|
3312
3426
|
fn compile_script(
|
|
@@ -3352,11 +3466,6 @@ impl Core {
|
|
|
3352
3466
|
self.reply_value(ruby, reply)
|
|
3353
3467
|
}
|
|
3354
3468
|
|
|
3355
|
-
fn dispose_script(&self, ruby: &Ruby, script_id: i32) -> Result<(), Error> {
|
|
3356
|
-
let reply = self.run(ruby, Request::DisposeScript { script_id })?;
|
|
3357
|
-
self.reply_value(ruby, reply).map(|_| ())
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
3469
|
// Serialize a fresh bytecode cache from a compiled handle's current state
|
|
3361
3470
|
// (Script#/Module#create_code_cache). None = V8 couldn't produce one (or the
|
|
3362
3471
|
// realm is gone).
|
|
@@ -3427,6 +3536,7 @@ impl Core {
|
|
|
3427
3536
|
st.realms = V8State::default();
|
|
3428
3537
|
st.modules = ModuleReg::default();
|
|
3429
3538
|
st.scripts = ScriptReg::default();
|
|
3539
|
+
st.functions = FunctionReg::default();
|
|
3430
3540
|
st.instantiate_resolve = None;
|
|
3431
3541
|
}
|
|
3432
3542
|
// Unregister the near-heap-limit callback before disposal: dropping the box
|
|
@@ -3551,6 +3661,11 @@ fn pending_transfer_count() -> usize {
|
|
|
3551
3661
|
transfer_registry().lock().unwrap().len()
|
|
3552
3662
|
}
|
|
3553
3663
|
|
|
3664
|
+
// A call's Ruby arguments, marshalled for JS.
|
|
3665
|
+
fn js_args(args: &[Value]) -> Result<Vec<JsVal>, Error> {
|
|
3666
|
+
args.iter().map(|v| ruby_to_jsval(*v)).collect()
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3554
3669
|
// Thin magnus-method wrappers.
|
|
3555
3670
|
// Isolate = the VM and its isolate-level operations; it hands out Contexts.
|
|
3556
3671
|
impl Isolate {
|
|
@@ -3657,6 +3772,18 @@ impl Context {
|
|
|
3657
3772
|
rb_self.check_live(ruby)?;
|
|
3658
3773
|
rb_self.core.call(ruby, rb_self.id, args, true)
|
|
3659
3774
|
}
|
|
3775
|
+
// function(path) -> Function: what `call(path, ...)` would call, resolved
|
|
3776
|
+
// now, once — its holder kept as its `this` — so each call skips the path walk.
|
|
3777
|
+
fn function(ruby: &Ruby, rb_self: &Self, path: String) -> Result<JsFunction, Error> {
|
|
3778
|
+
rb_self.check_live(ruby)?;
|
|
3779
|
+
let function_id = rb_self.core.resolve_function(ruby, rb_self.id, path)?;
|
|
3780
|
+
Ok(JsFunction {
|
|
3781
|
+
core: rb_self.core.clone(),
|
|
3782
|
+
context_id: rb_self.id,
|
|
3783
|
+
function_id,
|
|
3784
|
+
disposed: AtomicBool::new(false),
|
|
3785
|
+
})
|
|
3786
|
+
}
|
|
3660
3787
|
fn attach(ruby: &Ruby, rb_self: &Self, name: String, proc: Proc) -> Result<Value, Error> {
|
|
3661
3788
|
rb_self.check_live(ruby)?;
|
|
3662
3789
|
rb_self.core.attach(ruby, rb_self.id, name, proc)
|
|
@@ -3885,12 +4012,12 @@ impl JsModule {
|
|
|
3885
4012
|
let bytes = rb_self.core.module_code_cache(ruby, rb_self.module_id)?;
|
|
3886
4013
|
Ok(code_cache_value(ruby, bytes.as_ref()))
|
|
3887
4014
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
4015
|
+
// Let go of the module now rather than when this wrapper is collected (see
|
|
4016
|
+
// JsFunction::dispose). Its url still answers while V8 keeps the module.
|
|
4017
|
+
fn dispose(&self) {
|
|
4018
|
+
if !self.disposed.swap(true, Ordering::SeqCst) {
|
|
4019
|
+
self.core.release(Released::Module(self.module_id));
|
|
3891
4020
|
}
|
|
3892
|
-
let _ = rb_self.core.dispose_module(ruby, rb_self.module_id);
|
|
3893
|
-
Ok(())
|
|
3894
4021
|
}
|
|
3895
4022
|
fn disposed(&self) -> bool {
|
|
3896
4023
|
self.disposed.load(Ordering::SeqCst)
|
|
@@ -3936,18 +4063,72 @@ impl Script {
|
|
|
3936
4063
|
let bytes = rb_self.core.script_code_cache(ruby, rb_self.script_id)?;
|
|
3937
4064
|
Ok(code_cache_value(ruby, bytes.as_ref()))
|
|
3938
4065
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
4066
|
+
// Let go of the script now rather than when this wrapper is collected (see
|
|
4067
|
+
// JsFunction::dispose).
|
|
4068
|
+
fn dispose(&self) {
|
|
4069
|
+
if !self.disposed.swap(true, Ordering::SeqCst) {
|
|
4070
|
+
self.core.release(Released::Script(self.script_id));
|
|
3942
4071
|
}
|
|
3943
|
-
let _ = rb_self.core.dispose_script(ruby, rb_self.script_id);
|
|
3944
|
-
Ok(())
|
|
3945
4072
|
}
|
|
3946
4073
|
fn disposed(&self) -> bool {
|
|
3947
4074
|
self.disposed.load(Ordering::SeqCst)
|
|
3948
4075
|
}
|
|
3949
4076
|
}
|
|
3950
4077
|
|
|
4078
|
+
impl JsFunction {
|
|
4079
|
+
fn check_live(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
4080
|
+
// Its realm's reset or dispose is found by the call itself (the handle is
|
|
4081
|
+
// gone from FunctionReg): that is not something this wrapper can see.
|
|
4082
|
+
if self.disposed.load(Ordering::SeqCst) || self.core.is_disposed() {
|
|
4083
|
+
return Err(Error::new(
|
|
4084
|
+
err_class(ruby, "DisposedError"),
|
|
4085
|
+
"disposed function",
|
|
4086
|
+
));
|
|
4087
|
+
}
|
|
4088
|
+
Ok(())
|
|
4089
|
+
}
|
|
4090
|
+
fn call(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
4091
|
+
rb_self.check_live(ruby)?;
|
|
4092
|
+
rb_self
|
|
4093
|
+
.core
|
|
4094
|
+
.call_function(ruby, rb_self.context_id, rb_self.function_id, args, false)
|
|
4095
|
+
}
|
|
4096
|
+
fn call_void(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
4097
|
+
rb_self.check_live(ruby)?;
|
|
4098
|
+
rb_self
|
|
4099
|
+
.core
|
|
4100
|
+
.call_function(ruby, rb_self.context_id, rb_self.function_id, args, true)
|
|
4101
|
+
}
|
|
4102
|
+
// Let go of the function now rather than when this wrapper is collected. Any
|
|
4103
|
+
// thread, any time: the Globals go at the end of the isolate's next op.
|
|
4104
|
+
fn dispose(&self) {
|
|
4105
|
+
if !self.disposed.swap(true, Ordering::SeqCst) {
|
|
4106
|
+
self.core.release(Released::Function(self.function_id));
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
fn disposed(&self) -> bool {
|
|
4110
|
+
self.disposed.load(Ordering::SeqCst) || self.core.is_disposed()
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
impl Drop for JsFunction {
|
|
4115
|
+
fn drop(&mut self) {
|
|
4116
|
+
self.dispose();
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
impl Drop for JsModule {
|
|
4121
|
+
fn drop(&mut self) {
|
|
4122
|
+
self.dispose();
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
impl Drop for Script {
|
|
4127
|
+
fn drop(&mut self) {
|
|
4128
|
+
self.dispose();
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
|
|
3951
4132
|
// A bytecode cache as a binary (ASCII-8BIT) Ruby String, or nil when there's
|
|
3952
4133
|
// none — the shared return shape of #cached_data and #create_code_cache.
|
|
3953
4134
|
fn code_cache_value(ruby: &Ruby, bytes: Option<&Vec<u8>>) -> Value {
|
|
@@ -4015,6 +4196,7 @@ fn vm_err(ruby: &Ruby, e: VmError) -> Error {
|
|
|
4015
4196
|
err_class(ruby, "V8OutOfMemoryError"),
|
|
4016
4197
|
"JavaScript exceeded the isolate memory_limit",
|
|
4017
4198
|
),
|
|
4199
|
+
VmError::Disposed(m) => Error::new(err_class(ruby, "DisposedError"), m),
|
|
4018
4200
|
}
|
|
4019
4201
|
}
|
|
4020
4202
|
|
|
@@ -4197,6 +4379,7 @@ pub fn install_classes(ruby: &Ruby) -> Result<(), Error> {
|
|
|
4197
4379
|
context.define_method("_eval", method!(Context::eval, 4))?;
|
|
4198
4380
|
context.define_method("call", method!(Context::call, -1))?;
|
|
4199
4381
|
context.define_method("call_void", method!(Context::call_void, -1))?;
|
|
4382
|
+
context.define_method("function", method!(Context::function, 1))?;
|
|
4200
4383
|
context.define_method("attach", method!(Context::attach, 2))?;
|
|
4201
4384
|
context.define_method("attach_many", method!(Context::attach_many, 1))?;
|
|
4202
4385
|
context.define_method("reset", method!(Context::reset, 0))?;
|
|
@@ -4217,6 +4400,13 @@ pub fn install_classes(ruby: &Ruby) -> Result<(), Error> {
|
|
|
4217
4400
|
script.define_method("dispose", method!(Script::dispose, 0))?;
|
|
4218
4401
|
script.define_method("disposed?", method!(Script::disposed, 0))?;
|
|
4219
4402
|
|
|
4403
|
+
// A resolved function: Context#function -> #call / #call_void.
|
|
4404
|
+
let function = module.define_class("Function", ruby.class_object())?;
|
|
4405
|
+
function.define_method("call", method!(JsFunction::call, -1))?;
|
|
4406
|
+
function.define_method("call_void", method!(JsFunction::call_void, -1))?;
|
|
4407
|
+
function.define_method("dispose", method!(JsFunction::dispose, 0))?;
|
|
4408
|
+
function.define_method("disposed?", method!(JsFunction::disposed, 0))?;
|
|
4409
|
+
|
|
4220
4410
|
// V8 startup blob: Snapshot.new(code) -> Isolate.new(snapshot:).
|
|
4221
4411
|
let snapshot = module.define_class("Snapshot", ruby.class_object())?;
|
|
4222
4412
|
snapshot.define_singleton_method("new", function!(Snapshot::new, -1))?;
|