rusty_racer 0.2.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc72e8a18f16fc78197991e4e29ff01ba94afb83ae26d3ea162903654f485dae
4
- data.tar.gz: 06a00da38cb052af3a29fa772f93fa14f3e8f267f8a7c88723aad827f908a3d7
3
+ metadata.gz: ebbfddd8204b62eed228d53d476247e3b76bf08a894b02fb3e8ffe78836b6b02
4
+ data.tar.gz: 77d0190f0387cbdf240d49ef4ac3b75c1550a18a74de8ceed78a8c97c1956d96
5
5
  SHA512:
6
- metadata.gz: 3e86f16c51dbe2b1096a7710ea105da2f95f68b640f9a3265f63b85ffcd6f630fb9642305a3afd88da1a559ce50f1238b4b1ff8c8e573d1c8ab20bfd684e03f0
7
- data.tar.gz: aa1020630d83aa95c83837c8d1aa14bd3e56a207d955de314892f1c8e247a3519ad7101f9b6d6f1a9dab8f3f535c92bb59ab11cd342245be8f777bbdb21a60d3
6
+ metadata.gz: e0a3df4af6404d9357a008e2adcb4d8303a39db35ad9d3d4ef0240217222bd0fd0be40c9b18b36b9778e30e9eac8a5ce9b20514ecc962975cc25b1466d504aac
7
+ data.tar.gz: 497c28687de4bc14c7b666e99a3205406e84384459b923dca24b8d2080db552d3266bbffbfbecfb0a4e61bde9c17aa4413f39e321d1c866339df6944782c3930
data/CHANGELOG.md ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## 0.2.6
4
+
5
+ ### Added
6
+
7
+ - `Context#function(path)` returns a `RustyRacer::Function`: the function a
8
+ dotted path names, resolved once, with the object it was read off kept as its
9
+ `this`. `#call` / `#call_void` invoke it as `Context#call` / `#call_void`
10
+ would — same marshalling, timeout, errors and re-entrancy — without walking
11
+ the path or making a property name per call. It raises `DisposedError` once
12
+ its realm is reset or disposed and never keeps that realm alive; `#dispose`
13
+ releases it early, and garbage collection releases it too.
14
+
15
+ ### Changed
16
+
17
+ - Ruby → JS marshalling no longer runs the page's JavaScript. Arrays and
18
+ objects built for JS have their elements and entries defined
19
+ (`CreateDataProperty`) rather than assigned, so an accessor on
20
+ `Array.prototype` or `Object.prototype` can no longer intercept or swallow
21
+ call arguments, host function results or the host namespace's members. A
22
+ `"__proto__"` key in a Hash now becomes an own property of the object, as
23
+ `JSON.parse` makes it, instead of setting its prototype.
24
+ - Ruby → JS marshalling dispatches on the value's built-in type first: a String,
25
+ Symbol, Array or Hash argument no longer pays for a raised-and-rescued
26
+ conversion probe or a constant lookup (a String argument cost ~1.2 µs more
27
+ than none; now ~0.07 µs). Subclasses, `to_f` (an `ActiveSupport::Duration`),
28
+ `to_str`, `to_ary` and `to_hash` conversions marshal as before — except that
29
+ an Array or Hash subclass is now always an array or an object, where one that
30
+ also defined `to_str` (or a Hash subclass `to_ary`) used to cross as that.
31
+ - A getter or `Proxy` trap that throws while `Context#call` resolves its path
32
+ now raises a `RuntimeError` carrying the JS stack, and the thrown value
33
+ propagates through a host function like any other JS throw.
34
+ - A `Module` or `Script` whose wrapper is garbage-collected without `#dispose`
35
+ is released too (at the end of the isolate's next operation), where 0.2.5 kept
36
+ it until its realm was reset or disposed. `#dispose` itself now only queues
37
+ the release, as `Function#dispose` does, so it works from any thread — in
38
+ 0.2.5 a dispose from another thread marked the wrapper disposed but silently
39
+ kept the module or script for good.
40
+ - A module's url (its `import.meta.url`, its url as a referrer) is kept for as
41
+ long as V8 keeps the module, not as long as its wrapper or its realm: a
42
+ dependency the resolver compiled and Ruby dropped or disposed, or a function
43
+ of a disposed realm's module that another realm still holds, still reads it.
44
+ In 0.2.5 either read `undefined`.
45
+
46
+ ### Documented
47
+
48
+ - What reading a JS value into Ruby runs (getters, `Proxy` traps, holes read
49
+ through the prototype chain, `toString`) and the binary-String ↔ `Uint8Array`
50
+ rule — README, "Marshalling and the page's JavaScript".
51
+
52
+ ### Deliberately unchanged
53
+
54
+ - JS → Ruby still renders a value with no structural form — a function, a
55
+ RegExp, a depth-truncated object — through its ToString, which a page can
56
+ override (`Function.prototype.toString`) to run code or throw during the read.
57
+ V8's side-effect-free renderer (`Value::ToDetailString`, already used for
58
+ Symbols) would run nothing, but it truncates a function's source past 128
59
+ characters and renders objects as `#<Object>`, so switching is a behaviour
60
+ change left for a release that decides it on purpose.
data/README.md CHANGED
@@ -67,6 +67,11 @@ ctx.call("add", 20, 22) # => 42
67
67
  ctx.call_void("doSideEffect") # runs it; never marshals the return
68
68
  ctx.eval_void("globalThis.app = boot()") # ditto for the completion value
69
69
 
70
+ # Resolve a function once and keep it: no path walk or name per call, and its
71
+ # holder stays its `this` (see "Function handles" below).
72
+ add = ctx.function("add")
73
+ add.call(20, 22) # => 42
74
+
70
75
  # Ruby callbacks into JS; a raised Ruby exception becomes a JS exception.
71
76
  ctx.attach("rubyUpcase", ->(s) { s.upcase })
72
77
  ctx.eval("rubyUpcase('hi')") # => "HI"
@@ -93,6 +98,65 @@ ran to completion. When the value is incidental, say so: `#eval_void`,
93
98
  result. A statement list has a completion value whether you want one or not, so
94
99
  plumbing an object into a global (`globalThis.win = someProxy`) is enough to
95
100
  make a plain `#eval` fail after every one of its writes has landed.
101
+ `#eval_primitive` sits between the two: it returns the completion value only
102
+ where it is a primitive, and nil for an object or a symbol, which it never reads.
103
+
104
+ ### Function handles
105
+
106
+ `Context#function(path)` resolves a dotted path (`"app.router.go"`) the way
107
+ `#call` does — each segment an ordinary property read off the context's
108
+ `globalThis` — once, and returns a `RustyRacer::Function` bound to what it found:
109
+ the function, and the object it was read off as its `this`. `#call(*args)` and
110
+ `#call_void(*args)` then invoke it exactly as `Context#call` / `#call_void` would
111
+ — same argument and result marshalling, same timeout, same errors, and they work
112
+ from inside a host function like any other op — without walking the path or
113
+ making a property name per call. Reassigning or deleting the path afterwards
114
+ does not change what the handle calls.
115
+
116
+ A handle lives as long as its realm does. Once its context is `reset` or
117
+ `dispose`d (or the isolate is), `#call` raises `RustyRacer::DisposedError`, and
118
+ the handle no longer keeps that realm alive. (`#disposed?` answers for the handle
119
+ and its isolate only: a realm's reset is found by the next call.) `#dispose` lets
120
+ go of one early, and a handle that is simply garbage-collected is released too,
121
+ at the end of the isolate's next operation. A path that leads into *another*
122
+ realm (`frames[0].fn` in an embedder whose realms see each other) holds that
123
+ realm's function for as long as the handle's own realm lives, as any reference
124
+ kept there would.
125
+
126
+ ### Releasing handles
127
+
128
+ A `Function`, `Script` or `Module` handle is released when it is `#dispose`d —
129
+ from any thread — or garbage-collected, at the end of the isolate's next
130
+ operation, and with its realm's `reset` or `dispose` at the latest. Releasing a
131
+ handle only drops the isolate's hold on what it names: functions a script
132
+ defined keep working, and a module V8 still keeps — linked into a live graph, or
133
+ reached from another realm — keeps its `import.meta.url` and its url as a
134
+ referrer, wrapper or realm or not.
135
+
136
+ ### Marshalling and the page's JavaScript
137
+
138
+ Values cross by copy, not by reference.
139
+
140
+ - **Ruby → JS builds new values without running any JavaScript.** An array's
141
+ elements and an object's entries are *defined* on the new container
142
+ (`CreateDataProperty`), never assigned, so an accessor a script has put on
143
+ `Array.prototype[0]` or `Object.prototype.x` neither sees nor swallows them,
144
+ and a `"__proto__"` key is an own property, as `JSON.parse` makes it. Maps and
145
+ Sets are filled by V8's original builtins, whatever `Map.prototype.set` is now.
146
+ - **JS → Ruby reads values the way a script would.** An object's own enumerable
147
+ string keys are listed and each is read with an ordinary `[[Get]]`, so a getter
148
+ runs; a `Proxy` is read through its `ownKeys`, `getOwnPropertyDescriptor` and
149
+ `get` traps; an array's holes read through its prototype chain, as `arr[i]`
150
+ does; a function, a RegExp or any other value with no structural form becomes
151
+ its `toString()`, which may be the page's. A throw anywhere in that fails the op
152
+ (see above), and `#call_void` / `#eval_void` skip the read entirely. Map and Set
153
+ contents, typed arrays and buffers are read without running JavaScript.
154
+ - **A String's encoding tag is its type.** A binary (`ASCII-8BIT`) String crosses
155
+ as a `Uint8Array`, and a `Uint8Array`, `ArrayBuffer` or `SharedArrayBuffer`
156
+ comes back as a binary String. Any text encoding crosses as a JS string, and
157
+ must hold valid characters — invalid UTF-8 raises `EncodingError` rather than
158
+ turn into U+FFFD — so text read with `File.binread` or off a socket needs its
159
+ real encoding (`force_encoding`) before it crosses as text.
96
160
 
97
161
  ES modules (the embedder owns the URL→module registry):
98
162
 
@@ -114,6 +178,12 @@ loop here (see below), a `true` graph whose `await` never settles returns from
114
178
  `evaluate` with the module body still suspended, and `status` reports
115
179
  `:evaluated` either way — so `graph_async?` is what distinguishes the two.
116
180
 
181
+ A rejected evaluation — a top-level throw, or a top-level `await` that rejects —
182
+ raises from `evaluate`. Pass `report:` the name of a function on the context's
183
+ global to have it called with the reason instead (HTML's "report the
184
+ exception"), at once for a throw and as it settles for an `await`:
185
+ `app.evaluate(report: "reportError")`.
186
+
117
187
  Classic `<script>`s work the same way: `ctx.compile("1 + 1").run` # => 2.
118
188
 
119
189
  ### Resource limits
@@ -203,6 +273,12 @@ Also available:
203
273
  `microtasks: :auto` also drains at the end of each outermost eval/call/evaluate;
204
274
  `microtasks: :explicit` leaves it fully manual. There is no event loop or timers
205
275
  either way.
276
+ - **`Isolate#pump_message_loop`** — runs the tasks V8 posts instead of running
277
+ inline, most visibly `FinalizationRegistry` cleanup callbacks: a microtask
278
+ checkpoint never runs them, so without a pump a collected target's callback
279
+ never fires. It drains every pending task without blocking, then a microtask
280
+ checkpoint; with nothing pending it is a no-op. The callbacks are ordinary JS,
281
+ so the pump is capped by `timeout_ms` like an `eval`.
206
282
  - **`Isolate#terminate`**, **`Isolate#dynamic_import_resolver=`**,
207
283
  **`Context#reset`** (below), and **`Platform.set_flags!`**.
208
284
 
@@ -218,7 +294,9 @@ warm isolate — a per-visit reset that avoids rebuilding the VM. Its contract:
218
294
  - **Runtime mutations are dropped.** Anything set on the realm at runtime is gone.
219
295
  - **Host fns are dropped.** Functions `attach`/`attach_many`'d into the realm are
220
296
  released — the isolate stops holding them, so whatever they captured becomes
221
- collectable; re-attach them after a reset.
297
+ collectable; re-attach them after a reset. One the embedder kept hold of (through
298
+ another realm, say) throws `unknown host function` when called; it never runs
299
+ what was attached after it.
222
300
  - **Modules and classic scripts are dropped.** Handles compiled in the realm die
223
301
  with the old context.
224
302
  - **The realm id and the shared same-origin token are preserved** — the id keeps
@@ -5,7 +5,7 @@
5
5
  # links and loads with no custom archive.
6
6
  [package]
7
7
  name = "rusty_racer"
8
- version = "0.2.4"
8
+ version = "0.2.6"
9
9
  edition = "2024"
10
10
  publish = false
11
11
 
@@ -31,3 +31,8 @@ v8 = { version = "150.4.0", features = ["simdutf"] }
31
31
  # pthread stack-bounds query, to set V8's stack limit per op relative to the
32
32
  # current native thread (in-thread V8 runs on the Ruby thread's stack).
33
33
  libc = "0.2"
34
+
35
+ # `cargo test` links libruby: the crate calls into it, though the marshalling tests
36
+ # that run without a VM never do.
37
+ [dev-dependencies]
38
+ rb-sys = { version = "0.9", features = ["link-ruby"] }