rusty_racer 0.2.5 → 0.2.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: 488967c6324cc118d0cf37974c23566725c7c6ce14ba48572330075d1b2dff66
4
- data.tar.gz: 48f562d20d1217e91a9727d1fa348312c51c49b9f82607f3bd2803247e76976c
3
+ metadata.gz: 214a3eb41745b4ed7c93662fa1cc1e1ecd11bc4678d5633eeab9a2075f5b4965
4
+ data.tar.gz: 87d473be7421ac6ff8d2c5ef7d2e5039ab75987660c313a37916d3f81def2477
5
5
  SHA512:
6
- metadata.gz: faad4323b59c0eca2d69f66bb9698a41ab5837c0f417710b0f125ca39c0fad22e3d399f8fe730bd2c526a1e9cfe34ad9b8c8be5fdbc47b5c8a6e860eb23308f7
7
- data.tar.gz: 304a7388700465fcfc86a8e7ffeba87c9bd95455075bce889277abe44193f8f407326a136cfe0be39738d53061603d249f153f031866c9570bddec424ac5b807
6
+ metadata.gz: 8bd2a0dbfd742b01ceb21447a7028401362d3f9cf55ed449e1eae66aae88fa020c9a80edcdd49e606395880d9c54e4a3d3e8af4b20449387ab6246a234fcd6f6
7
+ data.tar.gz: 0147def7b482ce78b420bfff341f5575a045ba806c1ea853ad3d24bbe2557735a2e672ed9a1510da8727ce9ae0f01ce7409460d8362b6819f3cc2cc30bffbbd8
data/CHANGELOG.md ADDED
@@ -0,0 +1,75 @@
1
+ # Changelog
2
+
3
+ ## 0.2.7
4
+
5
+ ### Changed
6
+
7
+ - An operation on a Ruby Fiber looks its stack's bounds up once per stack
8
+ rather than once per operation that a 16-entry cache had dropped. Capybara
9
+ filters each result set in an Enumerator left suspended until GC frees it, so
10
+ a page of finds runs on hundreds of distinct fiber stacks, and each lookup read
11
+ all of `/proc/self/maps` — a fifth of a find loop, and slower the larger
12
+ Ruby's fiber pool grew. Every stack an operation has run on is now kept (Ruby
13
+ hands its pooled stacks out again and never unmaps them), and on Linux 6.11+ a
14
+ lookup asks the kernel for the one mapping (`PROCMAP_QUERY`) instead of
15
+ reading the whole map. A call into V8 from a fresh fiber, steady state: 160 µs
16
+ → 1.0 µs beside a 2,200-line map, 521 µs → 1.0 µs beside 8,300 lines.
17
+
18
+ ## 0.2.6
19
+
20
+ ### Added
21
+
22
+ - `Context#function(path)` returns a `RustyRacer::Function`: the function a
23
+ dotted path names, resolved once, with the object it was read off kept as its
24
+ `this`. `#call` / `#call_void` invoke it as `Context#call` / `#call_void`
25
+ would — same marshalling, timeout, errors and re-entrancy — without walking
26
+ the path or making a property name per call. It raises `DisposedError` once
27
+ its realm is reset or disposed and never keeps that realm alive; `#dispose`
28
+ releases it early, and garbage collection releases it too.
29
+
30
+ ### Changed
31
+
32
+ - Ruby → JS marshalling no longer runs the page's JavaScript. Arrays and
33
+ objects built for JS have their elements and entries defined
34
+ (`CreateDataProperty`) rather than assigned, so an accessor on
35
+ `Array.prototype` or `Object.prototype` can no longer intercept or swallow
36
+ call arguments, host function results or the host namespace's members. A
37
+ `"__proto__"` key in a Hash now becomes an own property of the object, as
38
+ `JSON.parse` makes it, instead of setting its prototype.
39
+ - Ruby → JS marshalling dispatches on the value's built-in type first: a String,
40
+ Symbol, Array or Hash argument no longer pays for a raised-and-rescued
41
+ conversion probe or a constant lookup (a String argument cost ~1.2 µs more
42
+ than none; now ~0.07 µs). Subclasses, `to_f` (an `ActiveSupport::Duration`),
43
+ `to_str`, `to_ary` and `to_hash` conversions marshal as before — except that
44
+ an Array or Hash subclass is now always an array or an object, where one that
45
+ also defined `to_str` (or a Hash subclass `to_ary`) used to cross as that.
46
+ - A getter or `Proxy` trap that throws while `Context#call` resolves its path
47
+ now raises a `RuntimeError` carrying the JS stack, and the thrown value
48
+ propagates through a host function like any other JS throw.
49
+ - A `Module` or `Script` whose wrapper is garbage-collected without `#dispose`
50
+ is released too (at the end of the isolate's next operation), where 0.2.5 kept
51
+ it until its realm was reset or disposed. `#dispose` itself now only queues
52
+ the release, as `Function#dispose` does, so it works from any thread — in
53
+ 0.2.5 a dispose from another thread marked the wrapper disposed but silently
54
+ kept the module or script for good.
55
+ - A module's url (its `import.meta.url`, its url as a referrer) is kept for as
56
+ long as V8 keeps the module, not as long as its wrapper or its realm: a
57
+ dependency the resolver compiled and Ruby dropped or disposed, or a function
58
+ of a disposed realm's module that another realm still holds, still reads it.
59
+ In 0.2.5 either read `undefined`.
60
+
61
+ ### Documented
62
+
63
+ - What reading a JS value into Ruby runs (getters, `Proxy` traps, holes read
64
+ through the prototype chain, `toString`) and the binary-String ↔ `Uint8Array`
65
+ rule — README, "Marshalling and the page's JavaScript".
66
+
67
+ ### Deliberately unchanged
68
+
69
+ - JS → Ruby still renders a value with no structural form — a function, a
70
+ RegExp, a depth-truncated object — through its ToString, which a page can
71
+ override (`Function.prototype.toString`) to run code or throw during the read.
72
+ V8's side-effect-free renderer (`Value::ToDetailString`, already used for
73
+ Symbols) would run nothing, but it truncates a function's source past 128
74
+ characters and renders objects as `#<Object>`, so switching is a behaviour
75
+ 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"
@@ -96,6 +101,63 @@ make a plain `#eval` fail after every one of its writes has landed.
96
101
  `#eval_primitive` sits between the two: it returns the completion value only
97
102
  where it is a primitive, and nil for an object or a symbol, which it never reads.
98
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.
160
+
99
161
  ES modules (the embedder owns the URL→module registry):
100
162
 
101
163
  ```ruby
@@ -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.5"
8
+ version = "0.2.7"
9
9
  edition = "2024"
10
10
  publish = false
11
11
 
@@ -29,5 +29,11 @@ rb-sys = "0.9"
29
29
  # (which is built with rusty_v8_enable_simdutf=true) on every platform.
30
30
  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
- # current native thread (in-thread V8 runs on the Ruby thread's stack).
33
- libc = "0.2"
32
+ # current native thread (in-thread V8 runs on the Ruby thread's stack). 0.2.172
33
+ # for `_IOWR`, which builds the PROCMAP_QUERY ioctl number per architecture.
34
+ libc = "0.2.172"
35
+
36
+ # `cargo test` links libruby: the crate calls into it, though the marshalling tests
37
+ # that run without a VM never do.
38
+ [dev-dependencies]
39
+ rb-sys = { version = "0.9", features = ["link-ruby"] }