mini_racer_rs 1.0.0
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 +7 -0
- data/AI-DISCLOSURE.md +13 -0
- data/CHANGELOG.md +45 -0
- data/COMPATIBILITY.md +235 -0
- data/Cargo.lock +1263 -0
- data/Cargo.toml +62 -0
- data/LICENSE +21 -0
- data/README.md +254 -0
- data/THIRD_PARTY_NOTICES.md +4501 -0
- data/crates/mini_racer_engine/Cargo.toml +25 -0
- data/crates/mini_racer_engine/build.rs +13 -0
- data/crates/mini_racer_engine/src/context.rs +802 -0
- data/crates/mini_racer_engine/src/control.rs +239 -0
- data/crates/mini_racer_engine/src/error.rs +88 -0
- data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
- data/crates/mini_racer_engine/src/lib.rs +20 -0
- data/crates/mini_racer_engine/src/notify.rs +107 -0
- data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
- data/crates/mini_racer_engine/src/process/fork.rs +261 -0
- data/crates/mini_racer_engine/src/process/platform.rs +194 -0
- data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
- data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
- data/crates/mini_racer_engine/src/process/workers.rs +165 -0
- data/crates/mini_racer_engine/src/process.rs +69 -0
- data/crates/mini_racer_engine/src/runner.rs +1560 -0
- data/crates/mini_racer_engine/src/serialize.rs +253 -0
- data/crates/mini_racer_engine/src/snapshot.rs +221 -0
- data/crates/mini_racer_engine/src/spin.rs +23 -0
- data/crates/mini_racer_engine/src/sys.rs +566 -0
- data/crates/mini_racer_sync/Cargo.toml +18 -0
- data/crates/mini_racer_sync/src/channel.rs +621 -0
- data/crates/mini_racer_sync/src/fork.rs +116 -0
- data/crates/mini_racer_sync/src/lib.rs +20 -0
- data/crates/mini_racer_sync/src/lock.rs +49 -0
- data/crates/mini_racer_sync/src/parker.rs +44 -0
- data/crates/mini_racer_sync/src/primitives.rs +10 -0
- data/crates/mini_racer_sync/src/tasks.rs +345 -0
- data/crates/mini_racer_sync/src/termination.rs +1234 -0
- data/crates/mini_racer_sync/src/workers.rs +283 -0
- data/crates/mini_racer_wire/Cargo.toml +18 -0
- data/crates/mini_racer_wire/src/decode.rs +926 -0
- data/crates/mini_racer_wire/src/lib.rs +67 -0
- data/crates/mini_racer_wire/src/marker.rs +43 -0
- data/crates/mini_racer_wire/src/text.rs +106 -0
- data/crates/mini_racer_wire/src/write.rs +187 -0
- data/ext/mini_racer_rs/Cargo.toml +26 -0
- data/ext/mini_racer_rs/build.rs +16 -0
- data/ext/mini_racer_rs/extconf.rb +78 -0
- data/ext/mini_racer_rs/src/context.rs +485 -0
- data/ext/mini_racer_rs/src/decode.rs +450 -0
- data/ext/mini_racer_rs/src/encode.rs +333 -0
- data/ext/mini_racer_rs/src/errors.rs +156 -0
- data/ext/mini_racer_rs/src/fork.rs +31 -0
- data/ext/mini_racer_rs/src/gc_root.rs +117 -0
- data/ext/mini_racer_rs/src/guard.rs +7 -0
- data/ext/mini_racer_rs/src/host.rs +339 -0
- data/ext/mini_racer_rs/src/lib.rs +44 -0
- data/ext/mini_racer_rs/src/options.rs +269 -0
- data/ext/mini_racer_rs/src/platform.rs +42 -0
- data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
- data/ext/mini_racer_rs/src/snapshot.rs +149 -0
- data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
- data/lib/mini_racer/shared.rb +420 -0
- data/lib/mini_racer/truffleruby.rb +439 -0
- data/lib/mini_racer/version.rb +6 -0
- data/lib/mini_racer.rb +3 -0
- data/lib/mini_racer_rs/internal_error.rb +7 -0
- data/lib/mini_racer_rs/version.rb +5 -0
- data/lib/mini_racer_rs/vocabulary.rb +105 -0
- data/lib/mini_racer_rs.rb +33 -0
- data/licenses/mini_racer.txt +21 -0
- data/licenses/rust/SOURCES.tsv +6 -0
- data/licenses/rust/std/COPYRIGHT +34 -0
- data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
- data/licenses/rust/std/LICENSE-APACHE +176 -0
- data/licenses/rust/std/LICENSE-MIT +25 -0
- data/licenses/v8/SOURCES.tsv +20 -0
- data/licenses/v8/abseil-cpp/LICENSE +203 -0
- data/licenses/v8/chromium-build/LICENSE +27 -0
- data/licenses/v8/components.tsv +29 -0
- data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
- data/licenses/v8/fast_float/LICENSE-MIT +27 -0
- data/licenses/v8/highway/LICENSE +371 -0
- data/licenses/v8/icu/LICENSE +568 -0
- data/licenses/v8/inspector_protocol/LICENSE +27 -0
- data/licenses/v8/libc++/LICENSE.TXT +311 -0
- data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
- data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
- data/licenses/v8/rapidhash/LICENSE +34 -0
- data/licenses/v8/simdutf/LICENSE +18 -0
- data/licenses/v8/siphash/LICENSE +116 -0
- data/licenses/v8/utf8-decoder/LICENSE +19 -0
- data/licenses/v8/v8/LICENSE +61 -0
- data/licenses/v8/v8/LICENSE.fdlibm +6 -0
- data/licenses/v8/v8/LICENSE.strongtalk +29 -0
- data/licenses/v8/v8/LICENSE.v8 +26 -0
- metadata +156 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
use std::time::Duration;
|
|
2
|
+
|
|
3
|
+
use crate::lock::lock;
|
|
4
|
+
use crate::parker::Parker;
|
|
5
|
+
use crate::primitives::{AtomicU64, Mutex, Ordering};
|
|
6
|
+
|
|
7
|
+
// One word holds whose turn it is, whether the channel is closed, which sides
|
|
8
|
+
// are parked, and the request and callback depths, so the update that hands
|
|
9
|
+
// over the turn also tells the sender whether the receiver is asleep. Parking
|
|
10
|
+
// sets its flag with an update to the same word, so a handover and a park are
|
|
11
|
+
// totally ordered and no wakeup is lost.
|
|
12
|
+
const V8_TURN: u64 = 1;
|
|
13
|
+
const CLOSED: u64 = 1 << 1;
|
|
14
|
+
const RUBY_PARKED: u64 = 1 << 2;
|
|
15
|
+
const V8_PARKED: u64 = 1 << 3;
|
|
16
|
+
const RUBY_NOTIFIED: u64 = 1 << 4;
|
|
17
|
+
const V8_NOTIFIED: u64 = 1 << 5;
|
|
18
|
+
const REQUESTS_SHIFT: u32 = 16;
|
|
19
|
+
const CALLBACKS_SHIFT: u32 = 32;
|
|
20
|
+
const COUNT: u64 = 0xFFFF;
|
|
21
|
+
|
|
22
|
+
/// The payloads a channel moves. Ruby sends requests and replies; V8 sends
|
|
23
|
+
/// responses and callbacks.
|
|
24
|
+
pub trait Protocol {
|
|
25
|
+
type Request;
|
|
26
|
+
type Response;
|
|
27
|
+
type Callback;
|
|
28
|
+
type Reply;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub enum ToV8<P: Protocol> {
|
|
32
|
+
Request(P::Request),
|
|
33
|
+
Reply(P::Reply),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub enum ToRuby<P: Protocol> {
|
|
37
|
+
Response(P::Response),
|
|
38
|
+
Callback(P::Callback),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
42
|
+
pub enum Side {
|
|
43
|
+
Ruby,
|
|
44
|
+
V8,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl Side {
|
|
48
|
+
const fn turn(self) -> u64 {
|
|
49
|
+
match self {
|
|
50
|
+
Self::Ruby => 0,
|
|
51
|
+
Self::V8 => V8_TURN,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fn parked(self) -> u64 {
|
|
56
|
+
match self {
|
|
57
|
+
Self::Ruby => RUBY_PARKED,
|
|
58
|
+
Self::V8 => V8_PARKED,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const fn notified(self) -> u64 {
|
|
63
|
+
match self {
|
|
64
|
+
Self::Ruby => RUBY_NOTIFIED,
|
|
65
|
+
Self::V8 => V8_NOTIFIED,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fn other(self) -> Self {
|
|
70
|
+
match self {
|
|
71
|
+
Self::Ruby => Self::V8,
|
|
72
|
+
Self::V8 => Self::Ruby,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
|
78
|
+
pub enum ChannelError {
|
|
79
|
+
#[error("channel closed")]
|
|
80
|
+
Closed,
|
|
81
|
+
#[error("not this side's turn")]
|
|
82
|
+
OutOfTurn,
|
|
83
|
+
#[error("nothing to receive")]
|
|
84
|
+
Empty,
|
|
85
|
+
#[error("the previous message was never received")]
|
|
86
|
+
Unreceived,
|
|
87
|
+
#[error("nothing open to finish")]
|
|
88
|
+
Unbalanced,
|
|
89
|
+
#[error("nesting too deep")]
|
|
90
|
+
TooDeep,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Open requests and callbacks. Ruby's turn always has as many of each; V8's
|
|
94
|
+
/// turn has one more request than callbacks.
|
|
95
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
96
|
+
pub struct Nesting {
|
|
97
|
+
pub requests: u64,
|
|
98
|
+
pub callbacks: u64,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[derive(Clone, Copy)]
|
|
102
|
+
enum Step {
|
|
103
|
+
Request,
|
|
104
|
+
Reply,
|
|
105
|
+
Response,
|
|
106
|
+
Callback,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
impl Step {
|
|
110
|
+
fn apply(self, open: Nesting) -> Result<Nesting, ChannelError> {
|
|
111
|
+
let Nesting {
|
|
112
|
+
requests,
|
|
113
|
+
callbacks,
|
|
114
|
+
} = open;
|
|
115
|
+
let deeper = |n: u64| {
|
|
116
|
+
n.checked_add(1)
|
|
117
|
+
.filter(|n| *n <= COUNT)
|
|
118
|
+
.ok_or(ChannelError::TooDeep)
|
|
119
|
+
};
|
|
120
|
+
let shallower = |n: u64| n.checked_sub(1).ok_or(ChannelError::Unbalanced);
|
|
121
|
+
Ok(match self {
|
|
122
|
+
Self::Request => Nesting {
|
|
123
|
+
requests: deeper(requests)?,
|
|
124
|
+
callbacks,
|
|
125
|
+
},
|
|
126
|
+
Self::Reply => Nesting {
|
|
127
|
+
requests,
|
|
128
|
+
callbacks: shallower(callbacks)?,
|
|
129
|
+
},
|
|
130
|
+
Self::Response => Nesting {
|
|
131
|
+
requests: shallower(requests)?,
|
|
132
|
+
callbacks,
|
|
133
|
+
},
|
|
134
|
+
Self::Callback => Nesting {
|
|
135
|
+
requests,
|
|
136
|
+
callbacks: deeper(callbacks)?,
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const fn nesting(state: u64) -> Nesting {
|
|
143
|
+
Nesting {
|
|
144
|
+
requests: (state >> REQUESTS_SHIFT) & COUNT,
|
|
145
|
+
callbacks: (state >> CALLBACKS_SHIFT) & COUNT,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const fn with_nesting(state: u64, open: Nesting) -> u64 {
|
|
150
|
+
let counts = (COUNT << REQUESTS_SHIFT) | (COUNT << CALLBACKS_SHIFT);
|
|
151
|
+
(state & !counts) | (open.requests << REQUESTS_SHIFT) | (open.callbacks << CALLBACKS_SHIFT)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const fn closed(state: u64) -> bool {
|
|
155
|
+
state & CLOSED != 0
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const fn holds_turn(state: u64, side: Side) -> bool {
|
|
159
|
+
state & V8_TURN == side.turn()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const fn ready(state: u64, side: Side) -> bool {
|
|
163
|
+
holds_turn(state, side) || closed(state)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// The turn-taking channel between a Ruby thread and a context's runner.
|
|
167
|
+
///
|
|
168
|
+
/// Each side is driven by one thread at a time. A sender holds its outgoing
|
|
169
|
+
/// slot's lock for the whole handover, so a second sender on the same side
|
|
170
|
+
/// gets `OutOfTurn` instead of overwriting, and a receiver only locks a slot
|
|
171
|
+
/// once the turn says it is full, so the locks are effectively uncontended.
|
|
172
|
+
pub struct Channel<P: Protocol> {
|
|
173
|
+
state: AtomicU64,
|
|
174
|
+
to_v8: Mutex<Option<ToV8<P>>>,
|
|
175
|
+
to_ruby: Mutex<Option<ToRuby<P>>>,
|
|
176
|
+
ruby: Parker,
|
|
177
|
+
v8: Parker,
|
|
178
|
+
ruby_notifier: Mutex<Option<Box<dyn Notifier>>>,
|
|
179
|
+
v8_notifier: Mutex<Option<Box<dyn Notifier>>>,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Rung for a side that waits with `park_notified`, such as a fiber waiting
|
|
183
|
+
/// for a file descriptor to become readable.
|
|
184
|
+
pub trait Notifier: Send + Sync {
|
|
185
|
+
fn notify(&self);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
impl<P: Protocol> Default for Channel<P> {
|
|
189
|
+
fn default() -> Self {
|
|
190
|
+
Self {
|
|
191
|
+
state: AtomicU64::new(0),
|
|
192
|
+
to_v8: Mutex::new(None),
|
|
193
|
+
to_ruby: Mutex::new(None),
|
|
194
|
+
ruby: Parker::default(),
|
|
195
|
+
v8: Parker::default(),
|
|
196
|
+
ruby_notifier: Mutex::new(None),
|
|
197
|
+
v8_notifier: Mutex::new(None),
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
impl<P: Protocol> Channel<P> {
|
|
203
|
+
/// Ruby starts an operation: outermost when nothing is open, nested when a
|
|
204
|
+
/// callback is.
|
|
205
|
+
pub fn request(&self, request: P::Request) -> Result<(), ChannelError> {
|
|
206
|
+
self.publish(
|
|
207
|
+
Side::Ruby,
|
|
208
|
+
&self.to_v8,
|
|
209
|
+
ToV8::Request(request),
|
|
210
|
+
Step::Request,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Ruby finishes the innermost callback.
|
|
215
|
+
pub fn reply(&self, reply: P::Reply) -> Result<(), ChannelError> {
|
|
216
|
+
self.publish(Side::Ruby, &self.to_v8, ToV8::Reply(reply), Step::Reply)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// V8 finishes the innermost request.
|
|
220
|
+
pub fn respond(&self, response: P::Response) -> Result<(), ChannelError> {
|
|
221
|
+
self.publish(
|
|
222
|
+
Side::V8,
|
|
223
|
+
&self.to_ruby,
|
|
224
|
+
ToRuby::Response(response),
|
|
225
|
+
Step::Response,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// V8 asks Ruby to run a callback inside the innermost request.
|
|
230
|
+
pub fn callback(&self, callback: P::Callback) -> Result<(), ChannelError> {
|
|
231
|
+
self.publish(
|
|
232
|
+
Side::V8,
|
|
233
|
+
&self.to_ruby,
|
|
234
|
+
ToRuby::Callback(callback),
|
|
235
|
+
Step::Callback,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// The message for Ruby, or `None` while it is V8's turn.
|
|
240
|
+
pub fn recv_ruby(&self) -> Result<Option<ToRuby<P>>, ChannelError> {
|
|
241
|
+
self.recv(Side::Ruby, &self.to_ruby)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// The message for V8, or `None` while it is Ruby's turn.
|
|
245
|
+
pub fn recv_v8(&self) -> Result<Option<ToV8<P>>, ChannelError> {
|
|
246
|
+
self.recv(Side::V8, &self.to_v8)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// Whether `side` has the turn or the channel is closed.
|
|
250
|
+
#[must_use]
|
|
251
|
+
pub fn ready(&self, side: Side) -> bool {
|
|
252
|
+
ready(self.state.load(Ordering::Acquire), side)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// Open, Ruby's turn, and no request or callback open. A thread that has
|
|
256
|
+
/// claimed the context but not sent yet still counts as idle.
|
|
257
|
+
#[must_use]
|
|
258
|
+
pub fn idle(&self) -> bool {
|
|
259
|
+
let state = self.state.load(Ordering::Acquire);
|
|
260
|
+
holds_turn(state, Side::Ruby)
|
|
261
|
+
&& !closed(state)
|
|
262
|
+
&& nesting(state)
|
|
263
|
+
== Nesting {
|
|
264
|
+
requests: 0,
|
|
265
|
+
callbacks: 0,
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[must_use]
|
|
270
|
+
pub fn is_closed(&self) -> bool {
|
|
271
|
+
closed(self.state.load(Ordering::Acquire))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[must_use]
|
|
275
|
+
pub fn nesting(&self) -> Nesting {
|
|
276
|
+
nesting(self.state.load(Ordering::Acquire))
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Waits for the turn: returns once `side` has it, the channel closes,
|
|
280
|
+
/// `wake(side)` is called, or the timeout passes. May return early;
|
|
281
|
+
/// callers recheck `ready`.
|
|
282
|
+
pub fn park(&self, side: Side, timeout: Option<Duration>) {
|
|
283
|
+
if self.mark(side, side.parked()) {
|
|
284
|
+
self.parker(side).park(timeout);
|
|
285
|
+
self.state.fetch_and(!side.parked(), Ordering::AcqRel);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// `park` for a waiter that sleeps elsewhere: unless `side` has the turn,
|
|
290
|
+
/// runs `wait`, which should return once the side's notifier rings, the
|
|
291
|
+
/// channel closes or `wake(side)` is called, and may return earlier.
|
|
292
|
+
pub fn park_notified(&self, side: Side, wait: impl FnOnce()) {
|
|
293
|
+
if self.mark(side, side.notified()) {
|
|
294
|
+
wait();
|
|
295
|
+
self.state.fetch_and(!side.notified(), Ordering::AcqRel);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// What `park_notified` on `side` waits for; `None` removes it.
|
|
300
|
+
pub fn set_notifier(&self, side: Side, notifier: Option<Box<dyn Notifier>>) {
|
|
301
|
+
*lock(self.notifier(side)) = notifier;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Sets `flag` unless `side` has the turn; returns whether it did.
|
|
305
|
+
fn mark(&self, side: Side, flag: u64) -> bool {
|
|
306
|
+
let mut state = self.state.load(Ordering::Acquire);
|
|
307
|
+
loop {
|
|
308
|
+
if ready(state, side) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
match self.state.compare_exchange_weak(
|
|
312
|
+
state,
|
|
313
|
+
state | flag,
|
|
314
|
+
Ordering::AcqRel,
|
|
315
|
+
Ordering::Acquire,
|
|
316
|
+
) {
|
|
317
|
+
Ok(_) => return true,
|
|
318
|
+
Err(actual) => state = actual,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
fn ring(&self, side: Side) {
|
|
324
|
+
if let Some(notifier) = lock(self.notifier(side)).as_ref() {
|
|
325
|
+
notifier.notify();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const fn notifier(&self, side: Side) -> &Mutex<Option<Box<dyn Notifier>>> {
|
|
330
|
+
match side {
|
|
331
|
+
Side::Ruby => &self.ruby_notifier,
|
|
332
|
+
Side::V8 => &self.v8_notifier,
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/// Wakes `side` from `park` without handing it the turn.
|
|
337
|
+
pub fn wake(&self, side: Side) {
|
|
338
|
+
self.parker(side).unpark();
|
|
339
|
+
self.ring(side);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/// Closes the channel and wakes both sides. A message already sent is
|
|
343
|
+
/// still received; every later send fails with `Closed`.
|
|
344
|
+
pub fn close(&self) {
|
|
345
|
+
self.state.fetch_or(CLOSED, Ordering::AcqRel);
|
|
346
|
+
self.ruby.unpark();
|
|
347
|
+
self.v8.unpark();
|
|
348
|
+
self.ring(Side::Ruby);
|
|
349
|
+
self.ring(Side::V8);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
fn publish<M>(
|
|
353
|
+
&self,
|
|
354
|
+
from: Side,
|
|
355
|
+
outgoing: &Mutex<Option<M>>,
|
|
356
|
+
message: M,
|
|
357
|
+
step: Step,
|
|
358
|
+
) -> Result<(), ChannelError> {
|
|
359
|
+
let mut slot = lock(outgoing);
|
|
360
|
+
let mut state = self.state.load(Ordering::Acquire);
|
|
361
|
+
if closed(state) {
|
|
362
|
+
return Err(ChannelError::Closed);
|
|
363
|
+
}
|
|
364
|
+
if !holds_turn(state, from) {
|
|
365
|
+
return Err(ChannelError::OutOfTurn);
|
|
366
|
+
}
|
|
367
|
+
if slot.is_some() {
|
|
368
|
+
return Err(ChannelError::Unreceived);
|
|
369
|
+
}
|
|
370
|
+
// Only the turn holder changes the counts, and the slot lock keeps
|
|
371
|
+
// this the only sender, so a concurrent update can only set a parked
|
|
372
|
+
// flag or close.
|
|
373
|
+
let open = step.apply(nesting(state))?;
|
|
374
|
+
*slot = Some(message);
|
|
375
|
+
let to = from.other();
|
|
376
|
+
loop {
|
|
377
|
+
if closed(state) {
|
|
378
|
+
slot.take();
|
|
379
|
+
return Err(ChannelError::Closed);
|
|
380
|
+
}
|
|
381
|
+
let next = with_nesting(
|
|
382
|
+
(state & !V8_TURN & !to.parked() & !to.notified()) | to.turn(),
|
|
383
|
+
open,
|
|
384
|
+
);
|
|
385
|
+
match self
|
|
386
|
+
.state
|
|
387
|
+
.compare_exchange_weak(state, next, Ordering::AcqRel, Ordering::Acquire)
|
|
388
|
+
{
|
|
389
|
+
Ok(_) => break,
|
|
390
|
+
Err(actual) => state = actual,
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
drop(slot);
|
|
394
|
+
if state & to.parked() != 0 {
|
|
395
|
+
self.parker(to).unpark();
|
|
396
|
+
}
|
|
397
|
+
if state & to.notified() != 0 {
|
|
398
|
+
self.ring(to);
|
|
399
|
+
}
|
|
400
|
+
Ok(())
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn recv<M>(&self, side: Side, incoming: &Mutex<Option<M>>) -> Result<Option<M>, ChannelError> {
|
|
404
|
+
let state = self.state.load(Ordering::Acquire);
|
|
405
|
+
if !holds_turn(state, side) {
|
|
406
|
+
return if closed(state) {
|
|
407
|
+
Err(ChannelError::Closed)
|
|
408
|
+
} else {
|
|
409
|
+
Ok(None)
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
let message = lock(incoming).take();
|
|
413
|
+
match message {
|
|
414
|
+
Some(message) => Ok(Some(message)),
|
|
415
|
+
None if closed(state) => Err(ChannelError::Closed),
|
|
416
|
+
None => Err(ChannelError::Empty),
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const fn parker(&self, side: Side) -> &Parker {
|
|
421
|
+
match side {
|
|
422
|
+
Side::Ruby => &self.ruby,
|
|
423
|
+
Side::V8 => &self.v8,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
#[cfg(test)]
|
|
429
|
+
#[cfg(not(loom))]
|
|
430
|
+
mod tests {
|
|
431
|
+
use std::sync::Arc;
|
|
432
|
+
use std::thread;
|
|
433
|
+
|
|
434
|
+
use super::*;
|
|
435
|
+
|
|
436
|
+
struct Text;
|
|
437
|
+
|
|
438
|
+
impl Protocol for Text {
|
|
439
|
+
type Request = &'static str;
|
|
440
|
+
type Response = &'static str;
|
|
441
|
+
type Callback = &'static str;
|
|
442
|
+
type Reply = &'static str;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
fn request(channel: &Channel<Text>) -> Option<&'static str> {
|
|
446
|
+
match channel.recv_v8() {
|
|
447
|
+
Ok(Some(ToV8::Request(body))) => Some(body),
|
|
448
|
+
_ => None,
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
fn reply(channel: &Channel<Text>) -> Option<&'static str> {
|
|
453
|
+
match channel.recv_v8() {
|
|
454
|
+
Ok(Some(ToV8::Reply(body))) => Some(body),
|
|
455
|
+
_ => None,
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fn response(channel: &Channel<Text>) -> Option<&'static str> {
|
|
460
|
+
match channel.recv_ruby() {
|
|
461
|
+
Ok(Some(ToRuby::Response(body))) => Some(body),
|
|
462
|
+
_ => None,
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
fn callback(channel: &Channel<Text>) -> Option<&'static str> {
|
|
467
|
+
match channel.recv_ruby() {
|
|
468
|
+
Ok(Some(ToRuby::Callback(body))) => Some(body),
|
|
469
|
+
_ => None,
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const fn open(requests: u64, callbacks: u64) -> Nesting {
|
|
474
|
+
Nesting {
|
|
475
|
+
requests,
|
|
476
|
+
callbacks,
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#[test]
|
|
481
|
+
fn a_request_and_its_response_change_turns() {
|
|
482
|
+
let channel = Channel::<Text>::default();
|
|
483
|
+
assert!(channel.ready(Side::Ruby));
|
|
484
|
+
assert!(matches!(channel.recv_v8(), Ok(None)));
|
|
485
|
+
|
|
486
|
+
channel.request("eval").unwrap();
|
|
487
|
+
assert!(channel.ready(Side::V8));
|
|
488
|
+
assert!(!channel.ready(Side::Ruby));
|
|
489
|
+
assert!(matches!(channel.recv_ruby(), Ok(None)));
|
|
490
|
+
assert_eq!(request(&channel), Some("eval"));
|
|
491
|
+
|
|
492
|
+
channel.respond("2").unwrap();
|
|
493
|
+
assert_eq!(response(&channel), Some("2"));
|
|
494
|
+
assert_eq!(channel.nesting(), open(0, 0));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
#[test]
|
|
498
|
+
fn nested_requests_and_callbacks_unwind_in_order() {
|
|
499
|
+
let channel = Channel::<Text>::default();
|
|
500
|
+
channel.request("outer").unwrap();
|
|
501
|
+
assert_eq!(request(&channel), Some("outer"));
|
|
502
|
+
channel.callback("ruby fn").unwrap();
|
|
503
|
+
assert_eq!(channel.nesting(), open(1, 1));
|
|
504
|
+
assert_eq!(callback(&channel), Some("ruby fn"));
|
|
505
|
+
|
|
506
|
+
channel.request("inner").unwrap();
|
|
507
|
+
assert_eq!(channel.nesting(), open(2, 1));
|
|
508
|
+
assert_eq!(request(&channel), Some("inner"));
|
|
509
|
+
channel.respond("inner done").unwrap();
|
|
510
|
+
assert_eq!(response(&channel), Some("inner done"));
|
|
511
|
+
|
|
512
|
+
channel.reply("ruby fn done").unwrap();
|
|
513
|
+
assert_eq!(reply(&channel), Some("ruby fn done"));
|
|
514
|
+
channel.respond("outer done").unwrap();
|
|
515
|
+
assert_eq!(response(&channel), Some("outer done"));
|
|
516
|
+
assert_eq!(channel.nesting(), open(0, 0));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#[test]
|
|
520
|
+
fn misuse_is_an_error() {
|
|
521
|
+
let channel = Channel::<Text>::default();
|
|
522
|
+
assert_eq!(channel.recv_ruby().err(), Some(ChannelError::Empty));
|
|
523
|
+
assert_eq!(channel.reply("stray"), Err(ChannelError::Unbalanced));
|
|
524
|
+
assert_eq!(channel.respond("early"), Err(ChannelError::OutOfTurn));
|
|
525
|
+
|
|
526
|
+
channel.request("eval").unwrap();
|
|
527
|
+
assert_eq!(channel.request("again"), Err(ChannelError::OutOfTurn));
|
|
528
|
+
assert_eq!(request(&channel), Some("eval"));
|
|
529
|
+
assert_eq!(channel.recv_v8().err(), Some(ChannelError::Empty));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
#[test]
|
|
533
|
+
fn a_message_left_unreceived_is_never_overwritten() {
|
|
534
|
+
let channel = Channel::<Text>::default();
|
|
535
|
+
channel.request("outer").unwrap();
|
|
536
|
+
request(&channel);
|
|
537
|
+
channel.callback("ruby fn").unwrap();
|
|
538
|
+
channel.reply("skipped the callback").unwrap();
|
|
539
|
+
reply(&channel);
|
|
540
|
+
|
|
541
|
+
assert_eq!(channel.respond("outer done"), Err(ChannelError::Unreceived));
|
|
542
|
+
assert_eq!(channel.nesting(), open(1, 0));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
#[test]
|
|
546
|
+
fn a_message_sent_before_close_is_still_received() {
|
|
547
|
+
let channel = Channel::<Text>::default();
|
|
548
|
+
channel.request("eval").unwrap();
|
|
549
|
+
request(&channel);
|
|
550
|
+
channel.respond("2").unwrap();
|
|
551
|
+
channel.close();
|
|
552
|
+
|
|
553
|
+
assert!(channel.is_closed());
|
|
554
|
+
assert_eq!(response(&channel), Some("2"));
|
|
555
|
+
assert_eq!(channel.recv_ruby().err(), Some(ChannelError::Closed));
|
|
556
|
+
assert_eq!(channel.request("late"), Err(ChannelError::Closed));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
#[test]
|
|
560
|
+
fn close_fails_the_side_waiting_for_the_turn() {
|
|
561
|
+
let channel = Channel::<Text>::default();
|
|
562
|
+
channel.request("eval").unwrap();
|
|
563
|
+
channel.close();
|
|
564
|
+
|
|
565
|
+
assert!(channel.ready(Side::Ruby));
|
|
566
|
+
assert!(channel.ready(Side::V8));
|
|
567
|
+
assert_eq!(channel.recv_ruby().err(), Some(ChannelError::Closed));
|
|
568
|
+
assert_eq!(channel.respond("late"), Err(ChannelError::Closed));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
#[test]
|
|
572
|
+
fn park_returns_at_once_when_the_turn_is_already_here() {
|
|
573
|
+
let channel = Channel::<Text>::default();
|
|
574
|
+
channel.park(Side::Ruby, None);
|
|
575
|
+
channel.park(Side::Ruby, Some(Duration::from_secs(60)));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
#[test]
|
|
579
|
+
fn a_wake_ends_a_park_without_the_turn() {
|
|
580
|
+
let channel = Channel::<Text>::default();
|
|
581
|
+
channel.wake(Side::V8);
|
|
582
|
+
channel.park(Side::V8, None);
|
|
583
|
+
|
|
584
|
+
assert!(!channel.ready(Side::V8));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
#[test]
|
|
588
|
+
fn park_gives_up_at_the_timeout() {
|
|
589
|
+
let channel = Channel::<Text>::default();
|
|
590
|
+
channel.park(Side::V8, Some(Duration::from_millis(5)));
|
|
591
|
+
|
|
592
|
+
assert!(!channel.ready(Side::V8));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
#[test]
|
|
596
|
+
fn round_trips_between_threads() {
|
|
597
|
+
let channel = Arc::new(Channel::<Text>::default());
|
|
598
|
+
let runner = Arc::clone(&channel);
|
|
599
|
+
let v8 = thread::spawn(move || {
|
|
600
|
+
for _ in 0..1000 {
|
|
601
|
+
let message = loop {
|
|
602
|
+
if let Some(message) = runner.recv_v8().unwrap() {
|
|
603
|
+
break message;
|
|
604
|
+
}
|
|
605
|
+
runner.park(Side::V8, None);
|
|
606
|
+
};
|
|
607
|
+
assert!(matches!(message, ToV8::Request("ping")));
|
|
608
|
+
runner.respond("pong").unwrap();
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
for _ in 0..1000 {
|
|
613
|
+
channel.request("ping").unwrap();
|
|
614
|
+
while !channel.ready(Side::Ruby) {
|
|
615
|
+
channel.park(Side::Ruby, None);
|
|
616
|
+
}
|
|
617
|
+
assert_eq!(response(&channel), Some("pong"));
|
|
618
|
+
}
|
|
619
|
+
v8.join().unwrap();
|
|
620
|
+
}
|
|
621
|
+
}
|