rusty_racer 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d14d201ab91d7e00d99d448826a92ad13c5b082b3ebe4b9d1f03778f89abf04
4
- data.tar.gz: 3dc4d0bb904458984b24e12806dd756f17ae9f541f479df277e218fe8cb70478
3
+ metadata.gz: f6e242a3632078fa63367b6e4d81ee7d720b6cfaef8355067137444b6fd48441
4
+ data.tar.gz: 1996ff995ae82ca4dba4de4e17c2e577d820b606bed710813e27e32d7c7a178e
5
5
  SHA512:
6
- metadata.gz: 856d5dbbb8c3c70b1a4a574c85711c2bb89b3d343eaf4316d7714249ba5f6c04113187a728155627634a4eedcca0901cd427734e9fe9e89e9051fe0ffdaf4a6b
7
- data.tar.gz: 07dd013c094a65e09e2e32ce97a2ac874b2b1dd357e440914a4fdd282612bae3afce4c41bfcc62302457573a7d8681e91117186f0d32cd0aefff27160032bdfa
6
+ metadata.gz: 7bca218e9cb0384d7c4e61b8bb5d1c748eb38393163b5d104803698c8f51fe6fae9b4600d4a815be5b98b70a3dbce7ea60b2219d596a652e2f3b9c8144f50a55
7
+ data.tar.gz: 05b6aa55215bb555000fb9b99b698ac974f74d73a4c9687d04f6fa84039d4890d92be58e7edf68a78befb98919e329e34d8c95e938cd886cf05d469219d2039a
data/README.md CHANGED
@@ -154,19 +154,24 @@ Also available:
154
154
  isolate's heap. All realms are mutually same-origin (with a host namespace,
155
155
  `NS.contextGlobal(id)` reaches another realm's `globalThis`, like a same-origin
156
156
  `iframe.contentWindow`), so this is **not** an isolation boundary.
157
- - **Zero-copy buffer transfer between isolates** — with a host namespace,
158
- `NS.transferOut(arrayBufferOrView)` detaches a buffer (its `byteLength` 0) and
159
- returns an integer token; `NS.transferIn(token)` rebuilds an `ArrayBuffer` over
160
- the **same** memory in another isolate with no byte copy (the backing store is
161
- heap-external and atomic-refcounted, so the token stays importable even after the
162
- exporting isolate is disposed). `NS.transferOut` returns `0` for a non-buffer or
163
- non-detachable argument — including a `SharedArrayBuffer`, which is *shared*, not
164
- transferredso the caller can fall back to a copy; `NS.transferIn` returns
165
- `undefined` for an unknown token; `NS.transferDrop(token)` releases an exported
166
- buffer that is never imported. This is the engine primitive for
167
- implementing `postMessage` transferables; `RustyRacer.pending_transfer_count`
168
- reports exported-but-not-yet-imported buffers so dropped messages don't leak
169
- silently.
157
+ - **Zero-copy buffer transfer/share between isolates** — with a host namespace, the
158
+ `NS` object exposes the engine primitive for implementing `postMessage`
159
+ transferables and `SharedArrayBuffer` sharing, both with no byte copy:
160
+ - **Transfer** (the transfer list): `NS.transferOut(arrayBufferOrView)` detaches
161
+ a buffer (its `byteLength` 0) and returns an integer token; `NS.transferIn(token)`
162
+ rebuilds an `ArrayBuffer` over the **same** memory in another isolate.
163
+ - **Share** (a `SharedArrayBuffer`): `NS.shareOut(sharedArrayBuffer)` returns a
164
+ token *without* detaching — the source stays live; `NS.shareIn(token)` rebuilds
165
+ a `SharedArrayBuffer` over the same memory, so both isolates see each other's
166
+ writes and `Atomics` work across them (including across threads).
167
+
168
+ The backing store is heap-external and atomic-refcounted, so a token stays
169
+ importable even after the exporting isolate is disposed. `transferOut`/`shareOut`
170
+ return `0` for an unsuitable argument (so the caller can fall back to a copy);
171
+ `transferIn`/`shareIn` return `undefined` for an unknown or wrong-kind token;
172
+ `NS.transferDrop(token)` releases an exported buffer that is never imported.
173
+ `RustyRacer.pending_transfer_count` reports exported-but-not-yet-imported buffers
174
+ so dropped messages don't leak silently.
170
175
  - **`Isolate#perform_microtask_checkpoint`** — manual microtask drain. The default
171
176
  `microtasks: :auto` also drains at the end of each outermost eval/call/evaluate;
172
177
  `microtasks: :explicit` leaves it fully manual. There is no event loop or timers
@@ -4,7 +4,7 @@
4
4
  # libv8-rusty needed under the cibuildgem native-per-platform model).
5
5
  [package]
6
6
  name = "rusty_racer"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  edition = "2021"
9
9
  publish = false
10
10
 
@@ -649,23 +649,33 @@ static LEAKED_ISOLATES: std::sync::atomic::AtomicUsize = std::sync::atomic::Atom
649
649
 
650
650
  // ── zero-copy ArrayBuffer transfer registry ────────────────────────────────
651
651
  //
652
- // A SharedRef<BackingStore> parked here so a large ArrayBuffer can cross from one
653
- // isolate to another with NO byte copy. A backing store is V8's own heap-external,
652
+ // A SharedRef<BackingStore> parked here so a buffer can cross from one isolate to
653
+ // another with NO byte copy. A backing store is V8's own heap-external,
654
654
  // atomic-refcounted allocation, designed to be co-owned by live ArrayBuffers "even
655
655
  // across isolates" (rusty_v8 ArrayBuffer docs). The embedder (capybara-simulated's
656
- // cross-isolate postMessage transport) exports a buffer from the source realm via
657
- // NS.transferOut, ships the returned integer token through its normal message
658
- // plumbing, and rebuilds an ArrayBuffer over the SAME memory in the destination
659
- // realm via NS.transferIn — replacing the prior copy-out/copy-in of the bytes.
656
+ // cross-isolate postMessage transport) exports a buffer from the source realm,
657
+ // ships the returned integer token through its normal message plumbing, and
658
+ // rebuilds a buffer over the SAME memory in the destination realm — replacing the
659
+ // prior copy-out/copy-in of the bytes. Two flavours share this registry:
660
+ // - TRANSFER (NS.transferOut/transferIn): an ArrayBuffer; the source is detached
661
+ // (moved). The recipient gets an ArrayBuffer.
662
+ // - SHARE (NS.shareOut/shareIn): a SharedArrayBuffer; the source stays live and
663
+ // both realms see each other's writes (Atomics work across them). The
664
+ // recipient gets a SharedArrayBuffer.
665
+ // `shared` records which, so the matching import rebuilds the right type and a
666
+ // crossed import (transferIn of a share token, or vice versa) is refused.
660
667
  //
661
668
  // SharedRef<BackingStore> is NOT auto-Send (BackingStore is Send but not Sync, so
662
- // SharedPtrBase's `T: Sync` Send-bound is unmet). It rides in this newtype with a
669
+ // SharedPtrBase's `T: Sync` Send-bound is unmet). It rides in this struct with a
663
670
  // manual Send for the same reason as SendIso/IsoPtr: the registry only ever MOVES
664
671
  // the handle between owner threads (insert on the exporter's thread, remove on the
665
672
  // importer's) under the Mutex, never sharing &handle concurrently — and shared_ptr's
666
673
  // refcount is atomic, so dropping it on either thread is sound. No Sync impl: the
667
674
  // handle is never aliased across threads.
668
- struct SendBackingStore(v8::SharedRef<v8::BackingStore>);
675
+ struct SendBackingStore {
676
+ store: v8::SharedRef<v8::BackingStore>,
677
+ shared: bool,
678
+ }
669
679
  unsafe impl Send for SendBackingStore {}
670
680
 
671
681
  // Exported-but-not-yet-imported backing stores, keyed by token. A GLOBAL (not
@@ -1363,10 +1373,9 @@ fn set_promise_reject_handler(
1363
1373
  // loses access. A view transfers its whole underlying ArrayBuffer.
1364
1374
  //
1365
1375
  // Returns 0 (copy fallback) for: a non-buffer argument; a non-detachable buffer
1366
- // (WebAssembly/asm.js memory); and a SharedArrayBuffer or SAB-backed view — a
1367
- // SAB is *shared*, not transferred (detaching it would be a category error), so
1368
- // it has no place in this transfer primitive; sharing it zero-copy across
1369
- // isolates would be a separate feature.
1376
+ // (WebAssembly/asm.js memory); and a SharedArrayBuffer or SAB-backed view — a SAB
1377
+ // is *shared*, not transferred (detaching it would be a category error), so it
1378
+ // belongs to NS.shareOut, not here.
1370
1379
  fn transfer_out(
1371
1380
  scope: &mut v8::PinScope<'_, '_>,
1372
1381
  args: v8::FunctionCallbackArguments<'_>,
@@ -1404,12 +1413,36 @@ fn transfer_out(
1404
1413
  transfer_registry()
1405
1414
  .lock()
1406
1415
  .unwrap()
1407
- .insert(token, SendBackingStore(store));
1416
+ .insert(token, SendBackingStore { store, shared: false });
1417
+ rv.set(v8::Number::new(scope, token as f64).into());
1418
+ }
1419
+
1420
+ // NS.shareOut(sharedArrayBuffer) -> a positive integer token, or 0 for a
1421
+ // non-SharedArrayBuffer argument (so the caller falls back to a copy). Unlike
1422
+ // transferOut this does NOT detach: a SAB is *shared*, so the source realm keeps
1423
+ // its live SAB and the recipient's shareIn'd SAB sees the same memory (Atomics
1424
+ // work across them). Takes a bare SAB; for a SAB-backed view the caller passes
1425
+ // view.buffer.
1426
+ fn share_out(
1427
+ scope: &mut v8::PinScope<'_, '_>,
1428
+ args: v8::FunctionCallbackArguments<'_>,
1429
+ mut rv: v8::ReturnValue<'_, v8::Value>,
1430
+ ) {
1431
+ let Ok(sab) = v8::Local::<v8::SharedArrayBuffer>::try_from(args.get(0)) else {
1432
+ rv.set(v8::Number::new(scope, 0.0).into());
1433
+ return;
1434
+ };
1435
+ let store = sab.get_backing_store();
1436
+ let token = NEXT_TRANSFER_TOKEN.fetch_add(1, Ordering::Relaxed);
1437
+ transfer_registry()
1438
+ .lock()
1439
+ .unwrap()
1440
+ .insert(token, SendBackingStore { store, shared: true });
1408
1441
  rv.set(v8::Number::new(scope, token as f64).into());
1409
1442
  }
1410
1443
 
1411
1444
  // Parse a transfer-token argument: a finite, integral JS number >= 1 (0 is the
1412
- // "not transferable" sentinel transferOut never issues, and tokens are f64-exact
1445
+ // "not transferable" sentinel the *Out fns never issue, and tokens are f64-exact
1413
1446
  // only up to 2^53). Rejects NaN / Infinity / fractional / negative / out-of-range
1414
1447
  // values — which a bare `as u64` would truncate or saturate into a COLLISION with
1415
1448
  // a live token, stealing an unrelated transfer — so a malformed token instead
@@ -1422,10 +1455,23 @@ fn transfer_token(scope: &mut v8::PinScope<'_, '_>, value: v8::Local<v8::Value>)
1422
1455
  .then_some(n as u64)
1423
1456
  }
1424
1457
 
1458
+ // Consume a token whose kind matches |want_shared| (a transfer token for
1459
+ // transferIn, a share token for shareIn). Peeks the kind under the lock and only
1460
+ // removes on a match, so a crossed import (transferIn of a share token, or vice
1461
+ // versa) leaves the entry intact and reads as undefined instead of rebuilding the
1462
+ // wrong buffer type over the memory.
1463
+ fn take_store(token: u64, want_shared: bool) -> Option<v8::SharedRef<v8::BackingStore>> {
1464
+ let mut reg = transfer_registry().lock().unwrap();
1465
+ match reg.get(&token) {
1466
+ Some(e) if e.shared == want_shared => reg.remove(&token).map(|e| e.store),
1467
+ _ => None,
1468
+ }
1469
+ }
1470
+
1425
1471
  // NS.transferIn(token) -> a fresh ArrayBuffer over the transferred backing store
1426
- // (no byte copy), or undefined for an unknown token (already imported, or the
1427
- // message was dropped). Consumes the token: the new buffer co-owns the store, so
1428
- // the registry's reference is released.
1472
+ // (no byte copy), or undefined for an unknown/crossed token (already imported, the
1473
+ // message was dropped, or it was a share token). Consumes the token: the new
1474
+ // buffer co-owns the store, so the registry's reference is released.
1429
1475
  fn transfer_in(
1430
1476
  scope: &mut v8::PinScope<'_, '_>,
1431
1477
  args: v8::FunctionCallbackArguments<'_>,
@@ -1434,16 +1480,30 @@ fn transfer_in(
1434
1480
  let Some(token) = transfer_token(scope, args.get(0)) else {
1435
1481
  return;
1436
1482
  };
1437
- let entry = transfer_registry().lock().unwrap().remove(&token);
1438
- if let Some(SendBackingStore(store)) = entry {
1483
+ if let Some(store) = take_store(token, false) {
1439
1484
  rv.set(v8::ArrayBuffer::with_backing_store(scope, &store).into());
1440
1485
  }
1441
1486
  }
1442
1487
 
1443
- // NS.transferDrop(token): release a transferred backing store WITHOUT importing
1444
- // it for when the embedder discards a message whose buffer was exported.
1445
- // A no-op for an unknown token. Without this, an exported-but-never-imported
1446
- // buffer would pin its memory until process exit.
1488
+ // NS.shareIn(token) -> a fresh SharedArrayBuffer over the shared backing store
1489
+ // (same memory as the source's SAB), or undefined for an unknown/crossed token.
1490
+ fn share_in(
1491
+ scope: &mut v8::PinScope<'_, '_>,
1492
+ args: v8::FunctionCallbackArguments<'_>,
1493
+ mut rv: v8::ReturnValue<'_, v8::Value>,
1494
+ ) {
1495
+ let Some(token) = transfer_token(scope, args.get(0)) else {
1496
+ return;
1497
+ };
1498
+ if let Some(store) = take_store(token, true) {
1499
+ rv.set(v8::SharedArrayBuffer::with_backing_store(scope, &store).into());
1500
+ }
1501
+ }
1502
+
1503
+ // NS.transferDrop(token): release a parked backing store (transfer OR share)
1504
+ // WITHOUT importing it — for when the embedder discards a message whose buffer was
1505
+ // exported. A no-op for an unknown token. Without this, an exported-but-never-
1506
+ // imported buffer would pin its memory until process exit.
1447
1507
  fn transfer_drop(
1448
1508
  scope: &mut v8::PinScope<'_, '_>,
1449
1509
  args: v8::FunctionCallbackArguments<'_>,
@@ -1570,7 +1630,7 @@ fn install_host_namespace(
1570
1630
  let context = v8::Local::new(scope, ctx);
1571
1631
  let scope = &mut v8::ContextScope::new(scope, context);
1572
1632
  let ns = v8::Object::new(scope);
1573
- let members: [(&str, Option<v8::Local<v8::Function>>); 7] = [
1633
+ let members: [(&str, Option<v8::Local<v8::Function>>); 9] = [
1574
1634
  ("drainMicrotasks", v8::Function::new(scope, drain_microtasks)),
1575
1635
  ("contextGlobal", v8::Function::new(scope, context_global)),
1576
1636
  ("contextOf", v8::Function::new(scope, context_of)),
@@ -1580,6 +1640,8 @@ fn install_host_namespace(
1580
1640
  ),
1581
1641
  ("transferOut", v8::Function::new(scope, transfer_out)),
1582
1642
  ("transferIn", v8::Function::new(scope, transfer_in)),
1643
+ ("shareOut", v8::Function::new(scope, share_out)),
1644
+ ("shareIn", v8::Function::new(scope, share_in)),
1583
1645
  ("transferDrop", v8::Function::new(scope, transfer_drop)),
1584
1646
  ];
1585
1647
  for (member, function) in members {
@@ -6,6 +6,9 @@
6
6
  // STACK_DEBUG (set at init); everything else is private to this module.
7
7
 
8
8
  use std::ffi::c_void;
9
+ // Only native_stack_bounds (linux) needs it; gated so non-linux builds (macOS)
10
+ // don't warn it unused.
11
+ #[cfg(target_os = "linux")]
9
12
  use std::ptr::null_mut;
10
13
  use std::sync::atomic::{AtomicBool, Ordering};
11
14
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RustyRacer
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rusty_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima