mini_racer-csim 0.21.1.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/CHANGELOG +351 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/LICENSE.txt +21 -0
- data/README.md +687 -0
- data/ext/mini_racer_extension/extconf.rb +75 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +2899 -0
- data/ext/mini_racer_extension/mini_racer_v8.cc +2692 -0
- data/ext/mini_racer_extension/mini_racer_v8.h +70 -0
- data/ext/mini_racer_extension/serde.c +782 -0
- data/ext/mini_racer_loader/extconf.rb +13 -0
- data/ext/mini_racer_loader/mini_racer_loader.c +123 -0
- data/lib/mini_racer/shared.rb +395 -0
- data/lib/mini_racer/truffleruby.rb +479 -0
- data/lib/mini_racer/version.rb +9 -0
- data/lib/mini_racer-csim.rb +4 -0
- data/lib/mini_racer.rb +117 -0
- metadata +168 -0
|
@@ -0,0 +1,2899 @@
|
|
|
1
|
+
#include <stdatomic.h>
|
|
2
|
+
#include <stdint.h>
|
|
3
|
+
#include <stdio.h>
|
|
4
|
+
#include <stdlib.h>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
#include <pthread.h>
|
|
7
|
+
#include <unistd.h>
|
|
8
|
+
#include <math.h>
|
|
9
|
+
|
|
10
|
+
#if defined(__linux__) && !defined(__GLIBC__)
|
|
11
|
+
// musl compatibility for glibc-linked libraries (e.g. libv8-node)
|
|
12
|
+
// some versions of libv8-node are accidentally linked against glibc symbols
|
|
13
|
+
// or compiled with a toolchain that emits these C23 compatibility symbols
|
|
14
|
+
unsigned long long __isoc23_strtoull(const char *nptr, char **endptr, int base) {
|
|
15
|
+
return strtoull(nptr, endptr, base);
|
|
16
|
+
}
|
|
17
|
+
unsigned long __isoc23_strtoul(const char *nptr, char **endptr, int base) {
|
|
18
|
+
return strtoul(nptr, endptr, base);
|
|
19
|
+
}
|
|
20
|
+
long long __isoc23_strtoll(const char *nptr, char **endptr, int base) {
|
|
21
|
+
return strtoll(nptr, endptr, base);
|
|
22
|
+
}
|
|
23
|
+
long __isoc23_strtol(const char *nptr, char **endptr, int base) {
|
|
24
|
+
return strtol(nptr, endptr, base);
|
|
25
|
+
}
|
|
26
|
+
double __isoc23_strtod(const char *nptr, char **endptr) {
|
|
27
|
+
return strtod(nptr, endptr);
|
|
28
|
+
}
|
|
29
|
+
float __isoc23_strtof(const char *nptr, char **endptr) {
|
|
30
|
+
return strtof(nptr, endptr);
|
|
31
|
+
}
|
|
32
|
+
#endif
|
|
33
|
+
|
|
34
|
+
#include "ruby.h"
|
|
35
|
+
#include "ruby/encoding.h"
|
|
36
|
+
#include "ruby/version.h"
|
|
37
|
+
#include "ruby/thread.h"
|
|
38
|
+
#include "serde.c"
|
|
39
|
+
#include "mini_racer_v8.h"
|
|
40
|
+
|
|
41
|
+
// for debugging
|
|
42
|
+
#define RB_PUTS(v) \
|
|
43
|
+
do { \
|
|
44
|
+
fflush(stdout); \
|
|
45
|
+
rb_funcall(rb_mKernel, rb_intern("puts"), 1, v); \
|
|
46
|
+
} while (0)
|
|
47
|
+
|
|
48
|
+
#if RUBY_API_VERSION_CODE < 3*10000+4*100 // 3.4.0
|
|
49
|
+
static inline void rb_thread_lock_native_thread(void)
|
|
50
|
+
{
|
|
51
|
+
// Without rb_thread_lock_native_thread, V8 in single-threaded mode is
|
|
52
|
+
// prone to crash with debug checks like this...
|
|
53
|
+
//
|
|
54
|
+
// # Fatal error in ../deps/v8/src/base/platform/platform-posix.cc, line 1350
|
|
55
|
+
// # Debug check failed: MainThreadIsCurrentThread().
|
|
56
|
+
//
|
|
57
|
+
// ...because the Ruby runtime clobbers thread-local variables when it
|
|
58
|
+
// context-switches threads. You have been warned.
|
|
59
|
+
}
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
#define countof(x) (sizeof(x) / sizeof(*(x)))
|
|
63
|
+
#define endof(x) ((x) + countof(x))
|
|
64
|
+
|
|
65
|
+
// mostly RO: assigned once by platform_set_flag1 while holding |flags_mtx|,
|
|
66
|
+
// from then on read-only and accessible without holding locks
|
|
67
|
+
int single_threaded;
|
|
68
|
+
|
|
69
|
+
// work around missing pthread_barrier_t on macOS
|
|
70
|
+
typedef struct Barrier
|
|
71
|
+
{
|
|
72
|
+
pthread_mutex_t mtx;
|
|
73
|
+
pthread_cond_t cv;
|
|
74
|
+
int count, in, out;
|
|
75
|
+
} Barrier;
|
|
76
|
+
|
|
77
|
+
static inline int barrier_init(Barrier *b, int count)
|
|
78
|
+
{
|
|
79
|
+
int r;
|
|
80
|
+
|
|
81
|
+
if ((r = pthread_mutex_init(&b->mtx, NULL)))
|
|
82
|
+
return r;
|
|
83
|
+
if ((r = pthread_cond_init(&b->cv, NULL))) {
|
|
84
|
+
pthread_mutex_destroy(&b->mtx);
|
|
85
|
+
return r;
|
|
86
|
+
}
|
|
87
|
+
b->count = count;
|
|
88
|
+
b->out = 0;
|
|
89
|
+
b->in = 0;
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static inline void barrier_destroy(Barrier *b)
|
|
94
|
+
{
|
|
95
|
+
pthread_mutex_destroy(&b->mtx);
|
|
96
|
+
pthread_cond_destroy(&b->cv);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static inline int barrier_wait(Barrier *b)
|
|
100
|
+
{
|
|
101
|
+
int last;
|
|
102
|
+
|
|
103
|
+
pthread_mutex_lock(&b->mtx);
|
|
104
|
+
while (b->out)
|
|
105
|
+
pthread_cond_wait(&b->cv, &b->mtx);
|
|
106
|
+
if (++b->in == b->count) {
|
|
107
|
+
b->in = 0;
|
|
108
|
+
b->out = b->count;
|
|
109
|
+
pthread_cond_broadcast(&b->cv);
|
|
110
|
+
} else {
|
|
111
|
+
do
|
|
112
|
+
pthread_cond_wait(&b->cv, &b->mtx);
|
|
113
|
+
while (b->in);
|
|
114
|
+
}
|
|
115
|
+
last = (--b->out == 0);
|
|
116
|
+
if (last)
|
|
117
|
+
pthread_cond_broadcast(&b->cv);
|
|
118
|
+
pthread_mutex_unlock(&b->mtx);
|
|
119
|
+
return last;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
typedef struct Context
|
|
123
|
+
{
|
|
124
|
+
int depth; // call depth, protected by |rr_mtx|
|
|
125
|
+
// protected by |mtx|; RW for ruby threads, RO for v8 thread;
|
|
126
|
+
// atomic because context_stop (which can be called from other ruby
|
|
127
|
+
// threads) writes it without holding |mtx|, to avoid deadlocking
|
|
128
|
+
// 1=shut down v8, 2=free memory; note that only the v8 thread
|
|
129
|
+
// frees the memory and it intentionally stays around until
|
|
130
|
+
// the ruby object is gc'd, otherwise lifecycle management
|
|
131
|
+
// gets too complicated
|
|
132
|
+
atomic_int quit;
|
|
133
|
+
int verbose_exceptions;
|
|
134
|
+
int64_t idle_gc, max_memory, timeout;
|
|
135
|
+
struct State *pst; // used by v8 thread
|
|
136
|
+
VALUE procs; // array of js -> ruby callbacks
|
|
137
|
+
VALUE exception; // pending exception or Qnil
|
|
138
|
+
// Per-instantiate resolver Proc, Qnil when none active.
|
|
139
|
+
// module_instantiate saves/restores via rb_ensure to keep the slot
|
|
140
|
+
// consistent across exceptions and nested compile_module + instantiate
|
|
141
|
+
// from inside the resolver block. Concurrent Module#instantiate calls
|
|
142
|
+
// on the same Context from different Ruby threads race on this slot;
|
|
143
|
+
// callers must serialize externally — the rest of mini_racer's Context
|
|
144
|
+
// API has the same single-threaded-per-Context expectation.
|
|
145
|
+
VALUE resolve_block;
|
|
146
|
+
// Callable for `import(...)` in JS, or Qnil to reject dynamic imports
|
|
147
|
+
// with a clear error. Set via Context#dynamic_import_resolver=.
|
|
148
|
+
VALUE dynamic_import_resolver;
|
|
149
|
+
// Batched module-loader callbacks set by load_module_graph and persisted for
|
|
150
|
+
// the Context's lifetime (overwritten by the next load, freed on dispose), so
|
|
151
|
+
// registry-backed dynamic import() can reuse them after the call returns.
|
|
152
|
+
// Qnil until the first load_module_graph.
|
|
153
|
+
VALUE graph_resolve_block; // ->(edges) { [url|nil, ...] }
|
|
154
|
+
VALUE graph_fetch_block; // ->(urls) { [[source, cached_data]|nil, ...] }
|
|
155
|
+
Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
|
|
156
|
+
Buf snapshot;
|
|
157
|
+
Buf host_namespace; // NUL-terminated global name to install host helpers on, or empty
|
|
158
|
+
pthread_t single_threaded_thr;
|
|
159
|
+
pid_t single_threaded_pid;
|
|
160
|
+
int single_threaded_thr_started;
|
|
161
|
+
// |rr_mtx| stands for "recursive ruby mutex"; it's used to exclude
|
|
162
|
+
// other ruby threads but allow reentrancy from the same ruby thread
|
|
163
|
+
// (think ruby->js->ruby->js calls)
|
|
164
|
+
pthread_mutex_t rr_mtx;
|
|
165
|
+
pthread_mutex_t mtx;
|
|
166
|
+
pthread_cond_t cv;
|
|
167
|
+
struct {
|
|
168
|
+
pthread_mutex_t mtx;
|
|
169
|
+
pthread_cond_t cv;
|
|
170
|
+
int cancel;
|
|
171
|
+
} wd; // watchdog
|
|
172
|
+
Barrier early_init, late_init;
|
|
173
|
+
} Context;
|
|
174
|
+
|
|
175
|
+
typedef struct Snapshot {
|
|
176
|
+
VALUE blob;
|
|
177
|
+
} Snapshot;
|
|
178
|
+
|
|
179
|
+
// GC-finalizer caveat: module_free cannot send a dispose RPC (would need
|
|
180
|
+
// to take rr_mtx without a reliable GVL guarantee). Handles freed here
|
|
181
|
+
// rely on State::~State() walking st.modules at isolate teardown — so
|
|
182
|
+
// long-lived Contexts with many short-lived Modules accumulate Persistents
|
|
183
|
+
// until the Context is disposed. Call Module#dispose explicitly to free
|
|
184
|
+
// eagerly.
|
|
185
|
+
typedef struct Module {
|
|
186
|
+
VALUE context; // parent Context VALUE (kept alive via mark)
|
|
187
|
+
VALUE cached_data; // ASCII-8BIT String or Qnil
|
|
188
|
+
int32_t handle_id; // 0 if uninitialized or already freed
|
|
189
|
+
int cache_rejected;
|
|
190
|
+
int disposed;
|
|
191
|
+
} Module;
|
|
192
|
+
|
|
193
|
+
// GC-finalizer caveat: script_free cannot send a dispose RPC (would need
|
|
194
|
+
// to take rr_mtx without a reliable GVL guarantee). Handles freed here
|
|
195
|
+
// rely on State::~State() walking st.scripts at isolate teardown — so
|
|
196
|
+
// long-lived Contexts with many short-lived Scripts accumulate Persistents
|
|
197
|
+
// until the Context is disposed. Call Script#dispose explicitly to free
|
|
198
|
+
// eagerly.
|
|
199
|
+
typedef struct Script {
|
|
200
|
+
VALUE context; // parent Context VALUE (kept alive via mark)
|
|
201
|
+
VALUE cached_data; // ASCII-8BIT String or Qnil
|
|
202
|
+
int32_t handle_id; // 0 if uninitialized or already freed
|
|
203
|
+
int cache_rejected;
|
|
204
|
+
int disposed;
|
|
205
|
+
} Script;
|
|
206
|
+
|
|
207
|
+
static void context_destroy(Context *c);
|
|
208
|
+
static void context_free(void *arg);
|
|
209
|
+
static void context_mark(void *arg);
|
|
210
|
+
static size_t context_size(const void *arg);
|
|
211
|
+
|
|
212
|
+
static const rb_data_type_t context_type = {
|
|
213
|
+
.wrap_struct_name = "mini_racer/context",
|
|
214
|
+
.function = {
|
|
215
|
+
.dfree = context_free,
|
|
216
|
+
.dmark = context_mark,
|
|
217
|
+
.dsize = context_size,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
static void snapshot_free(void *arg);
|
|
222
|
+
static void snapshot_mark(void *arg);
|
|
223
|
+
static size_t snapshot_size(const void *arg);
|
|
224
|
+
|
|
225
|
+
static const rb_data_type_t snapshot_type = {
|
|
226
|
+
.wrap_struct_name = "mini_racer/snapshot",
|
|
227
|
+
.function = {
|
|
228
|
+
.dfree = snapshot_free,
|
|
229
|
+
.dmark = snapshot_mark,
|
|
230
|
+
.dsize = snapshot_size,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
static void script_free(void *arg);
|
|
235
|
+
static void script_mark(void *arg);
|
|
236
|
+
static size_t script_size(const void *arg);
|
|
237
|
+
|
|
238
|
+
static const rb_data_type_t script_type = {
|
|
239
|
+
.wrap_struct_name = "mini_racer/script",
|
|
240
|
+
.function = {
|
|
241
|
+
.dfree = script_free,
|
|
242
|
+
.dmark = script_mark,
|
|
243
|
+
.dsize = script_size,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
static void module_free(void *arg);
|
|
248
|
+
static void module_mark(void *arg);
|
|
249
|
+
static size_t module_size(const void *arg);
|
|
250
|
+
|
|
251
|
+
static const rb_data_type_t module_type = {
|
|
252
|
+
.wrap_struct_name = "mini_racer/module",
|
|
253
|
+
.function = {
|
|
254
|
+
.dfree = module_free,
|
|
255
|
+
.dmark = module_mark,
|
|
256
|
+
.dsize = module_size,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
static VALUE platform_init_error;
|
|
261
|
+
static VALUE context_disposed_error;
|
|
262
|
+
static VALUE parse_error;
|
|
263
|
+
static VALUE memory_error;
|
|
264
|
+
static VALUE script_error;
|
|
265
|
+
static VALUE runtime_error;
|
|
266
|
+
static VALUE internal_error;
|
|
267
|
+
static VALUE snapshot_error;
|
|
268
|
+
static VALUE terminated_error;
|
|
269
|
+
static VALUE context_class;
|
|
270
|
+
static VALUE snapshot_class;
|
|
271
|
+
static VALUE script_class;
|
|
272
|
+
static VALUE module_class;
|
|
273
|
+
static VALUE date_time_class;
|
|
274
|
+
static VALUE binary_class;
|
|
275
|
+
static VALUE js_function_class;
|
|
276
|
+
|
|
277
|
+
static ID id_filename;
|
|
278
|
+
static ID id_cached_data;
|
|
279
|
+
static ID id_produce_cache;
|
|
280
|
+
|
|
281
|
+
static pthread_mutex_t flags_mtx = PTHREAD_MUTEX_INITIALIZER;
|
|
282
|
+
static Buf flags; // protected by |flags_mtx|
|
|
283
|
+
|
|
284
|
+
// arg == &(struct rendezvous_nogvl){...}
|
|
285
|
+
static void *rendezvous_callback(void *arg);
|
|
286
|
+
|
|
287
|
+
// note: must be stack-allocated or VALUEs won't be visible to ruby's GC
|
|
288
|
+
typedef struct State
|
|
289
|
+
{
|
|
290
|
+
VALUE a, b;
|
|
291
|
+
uint8_t verbatim_keys:1;
|
|
292
|
+
} State;
|
|
293
|
+
|
|
294
|
+
// note: must be stack-allocated or VALUEs won't be visible to ruby's GC
|
|
295
|
+
typedef struct DesCtx
|
|
296
|
+
{
|
|
297
|
+
State *tos;
|
|
298
|
+
VALUE refs; // object refs array
|
|
299
|
+
uint8_t transcode_latin1:1;
|
|
300
|
+
char err[64];
|
|
301
|
+
State stack[512];
|
|
302
|
+
} DesCtx;
|
|
303
|
+
|
|
304
|
+
struct rendezvous_nogvl
|
|
305
|
+
{
|
|
306
|
+
Context *context;
|
|
307
|
+
Buf *req, *res;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
struct rendezvous_des
|
|
311
|
+
{
|
|
312
|
+
DesCtx *d;
|
|
313
|
+
Buf *res;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
static void DesCtx_init(DesCtx *c)
|
|
317
|
+
{
|
|
318
|
+
c->tos = c->stack;
|
|
319
|
+
c->refs = rb_ary_new();
|
|
320
|
+
*c->tos = (State){Qundef, Qundef, /*verbatim_keys*/0};
|
|
321
|
+
*c->err = '\0';
|
|
322
|
+
c->transcode_latin1 = 1; // convert to utf8
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
static void put(DesCtx *c, VALUE v)
|
|
326
|
+
{
|
|
327
|
+
VALUE *a, *b;
|
|
328
|
+
|
|
329
|
+
if (*c->err)
|
|
330
|
+
return;
|
|
331
|
+
a = &c->tos->a;
|
|
332
|
+
b = &c->tos->b;
|
|
333
|
+
switch (TYPE(*a)) {
|
|
334
|
+
case T_ARRAY:
|
|
335
|
+
rb_ary_push(*a, v);
|
|
336
|
+
break;
|
|
337
|
+
case T_HASH:
|
|
338
|
+
if (*b == Qundef) {
|
|
339
|
+
*b = v;
|
|
340
|
+
} else {
|
|
341
|
+
if (!c->tos->verbatim_keys)
|
|
342
|
+
*b = rb_funcall(*b, rb_intern("to_s"), 0);
|
|
343
|
+
rb_hash_aset(*a, *b, v);
|
|
344
|
+
*b = Qundef;
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
case T_UNDEF:
|
|
348
|
+
*a = v;
|
|
349
|
+
break;
|
|
350
|
+
default:
|
|
351
|
+
snprintf(c->err, sizeof(c->err), "bad state");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
static void push(DesCtx *c, VALUE v)
|
|
357
|
+
{
|
|
358
|
+
if (*c->err)
|
|
359
|
+
return;
|
|
360
|
+
if (c->tos == endof(c->stack)) {
|
|
361
|
+
snprintf(c->err, sizeof(c->err), "stack overflow");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
*++c->tos = (State){v, Qundef, /*verbatim_keys*/0};
|
|
365
|
+
rb_ary_push(c->refs, v);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// see also des_named_props_end
|
|
369
|
+
static void pop(DesCtx *c)
|
|
370
|
+
{
|
|
371
|
+
if (*c->err)
|
|
372
|
+
return;
|
|
373
|
+
if (c->tos == c->stack) {
|
|
374
|
+
snprintf(c->err, sizeof(c->err), "stack underflow");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
put(c, (*c->tos--).a);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static void des_null(void *arg)
|
|
381
|
+
{
|
|
382
|
+
put(arg, Qnil);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
static void des_undefined(void *arg)
|
|
386
|
+
{
|
|
387
|
+
put(arg, Qnil);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static void des_bool(void *arg, int v)
|
|
391
|
+
{
|
|
392
|
+
put(arg, v ? Qtrue : Qfalse);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
static void des_int(void *arg, int64_t v)
|
|
396
|
+
{
|
|
397
|
+
put(arg, LONG2FIX(v));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
static void des_num(void *arg, double v)
|
|
401
|
+
{
|
|
402
|
+
if (isfinite(v) && v == trunc(v) && v >= INT64_MIN && v <= INT64_MAX) {
|
|
403
|
+
put(arg, LONG2FIX(v));
|
|
404
|
+
} else {
|
|
405
|
+
put(arg, DBL2NUM(v));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
static void des_date(void *arg, double v)
|
|
410
|
+
{
|
|
411
|
+
double sec, usec;
|
|
412
|
+
|
|
413
|
+
if (!isfinite(v))
|
|
414
|
+
rb_raise(rb_eRangeError, "invalid Date");
|
|
415
|
+
sec = v/1e3;
|
|
416
|
+
usec = 1e3 * fmod(v, 1e3);
|
|
417
|
+
put(arg, rb_time_new(sec, usec));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// note: v8 stores bigints in 1's complement, ruby in 2's complement,
|
|
421
|
+
// so we have to take additional steps to ensure correct conversion
|
|
422
|
+
static void des_bigint(void *arg, const void *p, size_t n, int sign)
|
|
423
|
+
{
|
|
424
|
+
VALUE v;
|
|
425
|
+
size_t i;
|
|
426
|
+
DesCtx *c;
|
|
427
|
+
unsigned long *a, t, limbs[65]; // +1 to suppress sign extension
|
|
428
|
+
|
|
429
|
+
c = arg;
|
|
430
|
+
if (*c->err)
|
|
431
|
+
return;
|
|
432
|
+
if (n > sizeof(limbs) - sizeof(*limbs)) {
|
|
433
|
+
snprintf(c->err, sizeof(c->err), "bigint too big");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
a = limbs;
|
|
437
|
+
t = 0;
|
|
438
|
+
for (i = 0; i < n; a++, i += sizeof(*a)) {
|
|
439
|
+
memcpy(a, (char *)p + i, sizeof(*a));
|
|
440
|
+
t = *a;
|
|
441
|
+
}
|
|
442
|
+
if (t >> 63)
|
|
443
|
+
*a++ = 0; // suppress sign extension
|
|
444
|
+
v = rb_big_unpack(limbs, a-limbs);
|
|
445
|
+
if (sign < 0) {
|
|
446
|
+
// rb_big_unpack returns T_FIXNUM for smallish bignums
|
|
447
|
+
switch (TYPE(v)) {
|
|
448
|
+
case T_BIGNUM:
|
|
449
|
+
v = rb_big_mul(v, LONG2FIX(-1));
|
|
450
|
+
break;
|
|
451
|
+
case T_FIXNUM:
|
|
452
|
+
v = LONG2FIX(-1 * FIX2LONG(v));
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
abort();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
put(c, v);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static void des_string(void *arg, const char *s, size_t n)
|
|
462
|
+
{
|
|
463
|
+
put(arg, rb_utf8_str_new(s, n));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
static VALUE str_encode_bang(VALUE v)
|
|
467
|
+
{
|
|
468
|
+
// TODO cache these? this function can get called often
|
|
469
|
+
return rb_funcall(v, rb_intern("encode!"), 1, rb_str_new_cstr("UTF-8"));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
static void des_string8(void *arg, const uint8_t *s, size_t n)
|
|
473
|
+
{
|
|
474
|
+
rb_encoding *e;
|
|
475
|
+
DesCtx *c;
|
|
476
|
+
VALUE v;
|
|
477
|
+
|
|
478
|
+
c = arg;
|
|
479
|
+
if (*c->err)
|
|
480
|
+
return;
|
|
481
|
+
if (c->transcode_latin1) {
|
|
482
|
+
e = rb_enc_find("ISO-8859-1"); // TODO cache?
|
|
483
|
+
if (!e) {
|
|
484
|
+
snprintf(c->err, sizeof(c->err), "no ISO-8859-1 encoding");
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
v = rb_enc_str_new((char *)s, n, e);
|
|
488
|
+
v = str_encode_bang(v); // cannot fail
|
|
489
|
+
} else {
|
|
490
|
+
v = rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding());
|
|
491
|
+
}
|
|
492
|
+
put(c, v);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// des_string16: |s| is not word aligned
|
|
496
|
+
// des_string16: |n| is in bytes, not code points
|
|
497
|
+
static void des_string16(void *arg, const void *s, size_t n)
|
|
498
|
+
{
|
|
499
|
+
rb_encoding *e;
|
|
500
|
+
VALUE v, r;
|
|
501
|
+
DesCtx *c;
|
|
502
|
+
int exc;
|
|
503
|
+
|
|
504
|
+
c = arg;
|
|
505
|
+
if (*c->err)
|
|
506
|
+
return;
|
|
507
|
+
// TODO(bnoordhuis) replace this hack with something more principled
|
|
508
|
+
if (n == sizeof(js_function_marker) && !memcmp(js_function_marker, s, n))
|
|
509
|
+
return put(c, rb_funcall(js_function_class, rb_intern("new"), 0));
|
|
510
|
+
e = rb_enc_find("UTF-16LE"); // TODO cache?
|
|
511
|
+
if (!e) {
|
|
512
|
+
snprintf(c->err, sizeof(c->err), "no UTF16-LE encoding");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
v = rb_enc_str_new((char *)s, n, e);
|
|
516
|
+
// JS strings can contain unmatched or illegal surrogate pairs
|
|
517
|
+
// that Ruby won't decode; return the string as-is in that case
|
|
518
|
+
r = rb_protect(str_encode_bang, v, &exc);
|
|
519
|
+
if (exc) {
|
|
520
|
+
rb_set_errinfo(Qnil);
|
|
521
|
+
r = v;
|
|
522
|
+
}
|
|
523
|
+
put(c, r);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ruby doesn't really have a concept of a byte array so store it as
|
|
527
|
+
// an 8-bit string instead; it's either that or a regular array of
|
|
528
|
+
// numbers, but the latter is markedly less efficient, storage-wise
|
|
529
|
+
static void des_arraybuffer(void *arg, const void *s, size_t n)
|
|
530
|
+
{
|
|
531
|
+
put(arg, rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding()));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
static void des_array_begin(void *arg)
|
|
535
|
+
{
|
|
536
|
+
push(arg, rb_ary_new());
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
static void des_array_end(void *arg)
|
|
540
|
+
{
|
|
541
|
+
pop(arg);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
static void des_named_props_begin(void *arg)
|
|
545
|
+
{
|
|
546
|
+
push(arg, rb_hash_new());
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// see also pop
|
|
550
|
+
static void des_named_props_end(void *arg)
|
|
551
|
+
{
|
|
552
|
+
DesCtx *c;
|
|
553
|
+
|
|
554
|
+
c = arg;
|
|
555
|
+
if (*c->err)
|
|
556
|
+
return;
|
|
557
|
+
if (c->tos == c->stack) {
|
|
558
|
+
snprintf(c->err, sizeof(c->err), "stack underflow");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
c->tos--; // dropped, no way to represent in ruby
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
static void des_object_begin(void *arg)
|
|
565
|
+
{
|
|
566
|
+
push(arg, rb_hash_new());
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
static void des_object_end(void *arg)
|
|
570
|
+
{
|
|
571
|
+
pop(arg);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
static void des_map_begin(void *arg)
|
|
575
|
+
{
|
|
576
|
+
DesCtx *c;
|
|
577
|
+
|
|
578
|
+
c = arg;
|
|
579
|
+
push(c, rb_hash_new());
|
|
580
|
+
c->tos->verbatim_keys = 1; // don't stringify or intern keys
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
static void des_map_end(void *arg)
|
|
584
|
+
{
|
|
585
|
+
pop(arg);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
static void des_object_ref(void *arg, uint32_t id)
|
|
589
|
+
{
|
|
590
|
+
DesCtx *c;
|
|
591
|
+
VALUE v;
|
|
592
|
+
|
|
593
|
+
c = arg;
|
|
594
|
+
v = rb_ary_entry(c->refs, id);
|
|
595
|
+
put(c, v);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
static void des_error_begin(void *arg)
|
|
599
|
+
{
|
|
600
|
+
push(arg, rb_ary_new());
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
static void des_error_end(void *arg)
|
|
604
|
+
{
|
|
605
|
+
VALUE *a, h, message, stack, cause, newline;
|
|
606
|
+
DesCtx *c;
|
|
607
|
+
|
|
608
|
+
c = arg;
|
|
609
|
+
if (*c->err)
|
|
610
|
+
return;
|
|
611
|
+
if (c->tos == c->stack) {
|
|
612
|
+
snprintf(c->err, sizeof(c->err), "stack underflow");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
a = &c->tos->a;
|
|
616
|
+
h = rb_ary_pop(*a);
|
|
617
|
+
message = rb_hash_aref(h, rb_str_new_cstr("message"));
|
|
618
|
+
stack = rb_hash_aref(h, rb_str_new_cstr("stack"));
|
|
619
|
+
cause = rb_hash_aref(h, rb_str_new_cstr("cause"));
|
|
620
|
+
if (NIL_P(message))
|
|
621
|
+
message = rb_str_new_cstr("JS exception");
|
|
622
|
+
if (!NIL_P(stack)) {
|
|
623
|
+
newline = rb_str_new_cstr("\n");
|
|
624
|
+
message = rb_funcall(message, rb_intern("concat"), 2, newline, stack);
|
|
625
|
+
}
|
|
626
|
+
*a = rb_class_new_instance(1, &message, script_error);
|
|
627
|
+
if (!NIL_P(cause))
|
|
628
|
+
rb_iv_set(*a, "@cause", cause);
|
|
629
|
+
pop(c);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
static int collect(VALUE k, VALUE v, VALUE a)
|
|
633
|
+
{
|
|
634
|
+
rb_ary_push(a, k);
|
|
635
|
+
rb_ary_push(a, v);
|
|
636
|
+
return ST_CONTINUE;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
static void add_string(Ser *s, VALUE v)
|
|
640
|
+
{
|
|
641
|
+
rb_encoding *e;
|
|
642
|
+
const void *p;
|
|
643
|
+
size_t n;
|
|
644
|
+
|
|
645
|
+
Check_Type(v, T_STRING);
|
|
646
|
+
e = rb_enc_get(v);
|
|
647
|
+
p = RSTRING_PTR(v);
|
|
648
|
+
n = RSTRING_LEN(v);
|
|
649
|
+
if (e) {
|
|
650
|
+
if (!strcmp(e->name, "ISO-8859-1"))
|
|
651
|
+
return ser_string8(s, p, n);
|
|
652
|
+
if (!strcmp(e->name, "UTF-16LE"))
|
|
653
|
+
return ser_string16(s, p, n);
|
|
654
|
+
}
|
|
655
|
+
return ser_string(s, p, n);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
659
|
+
{
|
|
660
|
+
unsigned long limbs[64];
|
|
661
|
+
VALUE a, t, id;
|
|
662
|
+
size_t i, n;
|
|
663
|
+
int sign;
|
|
664
|
+
|
|
665
|
+
if (*s->err)
|
|
666
|
+
return -1;
|
|
667
|
+
switch (TYPE(v)) {
|
|
668
|
+
case T_ARRAY:
|
|
669
|
+
{
|
|
670
|
+
VALUE obj_id = rb_obj_id(v);
|
|
671
|
+
id = rb_hash_lookup(refs, obj_id);
|
|
672
|
+
if (NIL_P(id)) {
|
|
673
|
+
n = RARRAY_LENINT(v);
|
|
674
|
+
i = rb_hash_size_num(refs);
|
|
675
|
+
rb_hash_aset(refs, obj_id, LONG2FIX(i));
|
|
676
|
+
ser_array_begin(s, n);
|
|
677
|
+
for (i = 0; i < n; i++)
|
|
678
|
+
if (serialize1(s, refs, rb_ary_entry(v, i)))
|
|
679
|
+
return -1;
|
|
680
|
+
ser_array_end(s, n);
|
|
681
|
+
} else {
|
|
682
|
+
ser_object_ref(s, FIX2LONG(id));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
break;
|
|
686
|
+
case T_HASH:
|
|
687
|
+
{
|
|
688
|
+
VALUE obj_id = rb_obj_id(v);
|
|
689
|
+
id = rb_hash_lookup(refs, obj_id);
|
|
690
|
+
if (NIL_P(id)) {
|
|
691
|
+
a = rb_ary_new();
|
|
692
|
+
i = rb_hash_size_num(refs);
|
|
693
|
+
n = rb_hash_size_num(v);
|
|
694
|
+
rb_hash_aset(refs, obj_id, LONG2FIX(i));
|
|
695
|
+
rb_hash_foreach(v, collect, a);
|
|
696
|
+
for (i = 0; i < 2*n; i += 2) {
|
|
697
|
+
t = rb_ary_entry(a, i);
|
|
698
|
+
switch (TYPE(t)) {
|
|
699
|
+
case T_FIXNUM:
|
|
700
|
+
case T_STRING:
|
|
701
|
+
case T_SYMBOL:
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
if (i == 2*n) {
|
|
707
|
+
ser_object_begin(s);
|
|
708
|
+
for (i = 0; i < 2*n; i += 2) {
|
|
709
|
+
if (serialize1(s, refs, rb_ary_entry(a, i+0)))
|
|
710
|
+
return -1;
|
|
711
|
+
if (serialize1(s, refs, rb_ary_entry(a, i+1)))
|
|
712
|
+
return -1;
|
|
713
|
+
}
|
|
714
|
+
ser_object_end(s, n);
|
|
715
|
+
} else {
|
|
716
|
+
return bail(&s->err, "TODO serialize as Map");
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
ser_object_ref(s, FIX2LONG(id));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
case T_DATA:
|
|
724
|
+
if (date_time_class == CLASS_OF(v)) {
|
|
725
|
+
v = rb_funcall(v, rb_intern("to_time"), 0);
|
|
726
|
+
}
|
|
727
|
+
if (rb_cTime == CLASS_OF(v)) {
|
|
728
|
+
struct timeval tv = rb_time_timeval(v);
|
|
729
|
+
ser_date(s, tv.tv_sec*1e3 + tv.tv_usec/1e3);
|
|
730
|
+
} else {
|
|
731
|
+
static const char undefined_conversion[] = "Undefined Conversion";
|
|
732
|
+
ser_string(s, undefined_conversion, sizeof(undefined_conversion)-1);
|
|
733
|
+
}
|
|
734
|
+
break;
|
|
735
|
+
case T_NIL:
|
|
736
|
+
ser_null(s);
|
|
737
|
+
break;
|
|
738
|
+
case T_UNDEF:
|
|
739
|
+
ser_undefined(s);
|
|
740
|
+
break;
|
|
741
|
+
case T_TRUE:
|
|
742
|
+
ser_bool(s, 1);
|
|
743
|
+
break;
|
|
744
|
+
case T_FALSE:
|
|
745
|
+
ser_bool(s, 0);
|
|
746
|
+
break;
|
|
747
|
+
case T_BIGNUM:
|
|
748
|
+
// note: v8 stores bigints in 1's complement, ruby in 2's complement,
|
|
749
|
+
// so we have to take additional steps to ensure correct conversion
|
|
750
|
+
memset(limbs, 0, sizeof(limbs));
|
|
751
|
+
sign = rb_big_sign(v) ? 1 : -1;
|
|
752
|
+
if (sign < 0)
|
|
753
|
+
v = rb_big_mul(v, LONG2FIX(-1));
|
|
754
|
+
rb_big_pack(v, limbs, countof(limbs));
|
|
755
|
+
ser_bigint(s, limbs, countof(limbs), sign);
|
|
756
|
+
break;
|
|
757
|
+
case T_FIXNUM:
|
|
758
|
+
ser_int(s, FIX2LONG(v));
|
|
759
|
+
break;
|
|
760
|
+
case T_FLOAT:
|
|
761
|
+
ser_num(s, NUM2DBL(v));
|
|
762
|
+
break;
|
|
763
|
+
case T_SYMBOL:
|
|
764
|
+
v = rb_sym2str(v);
|
|
765
|
+
// fallthru
|
|
766
|
+
case T_STRING:
|
|
767
|
+
add_string(s, v);
|
|
768
|
+
break;
|
|
769
|
+
case T_OBJECT:
|
|
770
|
+
// this is somewhat wide, but we have active support which creates
|
|
771
|
+
// entirely new objects
|
|
772
|
+
if (rb_respond_to(v, rb_intern("to_time"))) {
|
|
773
|
+
v = rb_funcall(v, rb_intern("to_time"), 0);
|
|
774
|
+
if (rb_obj_is_kind_of(v, rb_cTime)) {
|
|
775
|
+
struct timeval tv = rb_time_timeval(v);
|
|
776
|
+
ser_date(s, tv.tv_sec*1e3 + tv.tv_usec/1e3);
|
|
777
|
+
} else {
|
|
778
|
+
snprintf(s->err, sizeof(s->err), "unsupported type %s", rb_class2name(CLASS_OF(v)));
|
|
779
|
+
return -1;
|
|
780
|
+
}
|
|
781
|
+
} else if (!NIL_P(binary_class) && rb_obj_is_kind_of(v, binary_class)) {
|
|
782
|
+
t = rb_ivar_get(v, rb_intern("@data"));
|
|
783
|
+
Check_Type(t, T_STRING);
|
|
784
|
+
ser_uint8array(s, RSTRING_PTR(t), RSTRING_LEN(t));
|
|
785
|
+
} else {
|
|
786
|
+
snprintf(s->err, sizeof(s->err), "unsupported type %s", rb_class2name(CLASS_OF(v)));
|
|
787
|
+
return -1;
|
|
788
|
+
}
|
|
789
|
+
break;
|
|
790
|
+
default:
|
|
791
|
+
snprintf(s->err, sizeof(s->err), "unsupported type %x", TYPE(v));
|
|
792
|
+
return -1;
|
|
793
|
+
}
|
|
794
|
+
if (*s->err)
|
|
795
|
+
return -1;
|
|
796
|
+
return 0;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// don't mix with ser_array_begin/ser_object_begin because
|
|
800
|
+
// that will throw off the object reference count
|
|
801
|
+
static int serialize(Ser *s, VALUE v)
|
|
802
|
+
{
|
|
803
|
+
return serialize1(s, rb_hash_new(), v);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
static struct timespec deadline_ms(int ms)
|
|
807
|
+
{
|
|
808
|
+
static const int64_t ns_per_sec = 1000*1000*1000;
|
|
809
|
+
struct timespec t;
|
|
810
|
+
|
|
811
|
+
#ifdef __APPLE__
|
|
812
|
+
clock_gettime(CLOCK_REALTIME, &t);
|
|
813
|
+
#else
|
|
814
|
+
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
815
|
+
#endif
|
|
816
|
+
t.tv_sec += ms/1000;
|
|
817
|
+
t.tv_nsec += ms%1000 * ns_per_sec/1000;
|
|
818
|
+
while (t.tv_nsec >= ns_per_sec) {
|
|
819
|
+
t.tv_nsec -= ns_per_sec;
|
|
820
|
+
t.tv_sec++;
|
|
821
|
+
}
|
|
822
|
+
return t;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
static int timespec_le(struct timespec a, struct timespec b)
|
|
826
|
+
{
|
|
827
|
+
if (a.tv_sec < b.tv_sec) return 1;
|
|
828
|
+
return a.tv_sec == b.tv_sec && a.tv_nsec <= b.tv_nsec;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
static int deadline_exceeded(struct timespec deadline)
|
|
832
|
+
{
|
|
833
|
+
return timespec_le(deadline, deadline_ms(0));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
static void *v8_watchdog(void *arg)
|
|
837
|
+
{
|
|
838
|
+
struct timespec deadline;
|
|
839
|
+
Context *c;
|
|
840
|
+
|
|
841
|
+
c = arg;
|
|
842
|
+
deadline = deadline_ms(c->timeout);
|
|
843
|
+
pthread_mutex_lock(&c->wd.mtx);
|
|
844
|
+
for (;;) {
|
|
845
|
+
if (c->wd.cancel)
|
|
846
|
+
break;
|
|
847
|
+
pthread_cond_timedwait(&c->wd.cv, &c->wd.mtx, &deadline);
|
|
848
|
+
if (c->wd.cancel)
|
|
849
|
+
break;
|
|
850
|
+
if (deadline_exceeded(deadline)) {
|
|
851
|
+
v8_terminate_execution(c->pst);
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
pthread_mutex_unlock(&c->wd.mtx);
|
|
856
|
+
return NULL;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
|
|
860
|
+
void (*func)(struct State *pst, const uint8_t *p, size_t n))
|
|
861
|
+
{
|
|
862
|
+
pthread_t thr;
|
|
863
|
+
int r;
|
|
864
|
+
|
|
865
|
+
r = -1;
|
|
866
|
+
if (c->timeout > 0 && (r = pthread_create(&thr, NULL, v8_watchdog, c))) {
|
|
867
|
+
fprintf(stderr, "mini_racer: watchdog: pthread_create: %s\n", strerror(r));
|
|
868
|
+
fflush(stderr);
|
|
869
|
+
}
|
|
870
|
+
func(c->pst, p, n);
|
|
871
|
+
if (r)
|
|
872
|
+
return;
|
|
873
|
+
pthread_mutex_lock(&c->wd.mtx);
|
|
874
|
+
c->wd.cancel = 1;
|
|
875
|
+
pthread_cond_signal(&c->wd.cv);
|
|
876
|
+
pthread_mutex_unlock(&c->wd.mtx);
|
|
877
|
+
pthread_join(thr, NULL);
|
|
878
|
+
c->wd.cancel = 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
static void dispatch1(Context *c, const uint8_t *p, size_t n)
|
|
882
|
+
{
|
|
883
|
+
uint8_t b;
|
|
884
|
+
|
|
885
|
+
assert(n > 0);
|
|
886
|
+
switch (*p) {
|
|
887
|
+
case 'A': return v8_attach(c->pst, p+1, n-1);
|
|
888
|
+
case 'C': return v8_timedwait(c, p+1, n-1, v8_call);
|
|
889
|
+
case 'D': return v8_dispose_script(c->pst, p+1, n-1);
|
|
890
|
+
case 'E': return v8_timedwait(c, p+1, n-1, v8_eval);
|
|
891
|
+
case 'F': return v8_reset_realm(c->pst); // (F)resh realm
|
|
892
|
+
case 'G': return v8_timedwait(c, p+1, n-1, v8_load_module_graph); // (G)raph load
|
|
893
|
+
case 'H': return v8_heap_snapshot(c->pst);
|
|
894
|
+
case 'I': return v8_timedwait(c, p+1, n-1, v8_instantiate_module); // (I)nstantiate module
|
|
895
|
+
case 'K': return v8_timedwait(c, p+1, n-1, v8_compile); // (K)ompile — 'C' is taken
|
|
896
|
+
case 'M': return v8_perform_microtask_checkpoint(c->pst);
|
|
897
|
+
case 'N': return v8_module_namespace(c->pst, p+1, n-1); // (N)amespace
|
|
898
|
+
case 'O': return v8_timedwait(c, p+1, n-1, v8_compile_module); // (O)bject-module compile
|
|
899
|
+
case 'P': return v8_pump_message_loop(c->pst);
|
|
900
|
+
case 'R': return v8_timedwait(c, p+1, n-1, v8_run);
|
|
901
|
+
case 'S': return v8_heap_stats(c->pst);
|
|
902
|
+
case 'T': return v8_snapshot(c->pst, p+1, n-1);
|
|
903
|
+
case 'U': return v8_module_status(c->pst, p+1, n-1); // (U) module status — non-blocking
|
|
904
|
+
case 'V': return v8_timedwait(c, p+1, n-1, v8_evaluate_module); // e(V)aluate
|
|
905
|
+
case 'W': return v8_warmup(c->pst, p+1, n-1);
|
|
906
|
+
case 'Z': return v8_dispose_module(c->pst, p+1, n-1); // (Z) dispose module handle
|
|
907
|
+
case 'L':
|
|
908
|
+
b = 0;
|
|
909
|
+
v8_reply(c, &b, 1); // doesn't matter what as long as it's not empty
|
|
910
|
+
return v8_low_memory_notification(c->pst);
|
|
911
|
+
}
|
|
912
|
+
fprintf(stderr, "mini_racer: bad request %02x\n", *p);
|
|
913
|
+
fflush(stderr);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
static void dispatch(Context *c)
|
|
917
|
+
{
|
|
918
|
+
buf_reset(&c->res);
|
|
919
|
+
dispatch1(c, c->req.buf, c->req.len);
|
|
920
|
+
buf_reset(&c->req);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// called by v8_isolate_and_context
|
|
924
|
+
void v8_thread_main(Context *c, struct State *pst)
|
|
925
|
+
{
|
|
926
|
+
struct timespec deadline;
|
|
927
|
+
bool issued_idle_gc = true;
|
|
928
|
+
|
|
929
|
+
c->pst = pst;
|
|
930
|
+
barrier_wait(&c->late_init);
|
|
931
|
+
pthread_mutex_lock(&c->mtx);
|
|
932
|
+
while (!c->quit) {
|
|
933
|
+
if (!c->req.len) {
|
|
934
|
+
if (c->idle_gc > 0) {
|
|
935
|
+
deadline = deadline_ms(c->idle_gc);
|
|
936
|
+
pthread_cond_timedwait(&c->cv, &c->mtx, &deadline);
|
|
937
|
+
if (deadline_exceeded(deadline) && !issued_idle_gc) {
|
|
938
|
+
v8_low_memory_notification(c->pst);
|
|
939
|
+
issued_idle_gc = true;
|
|
940
|
+
}
|
|
941
|
+
} else {
|
|
942
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (!c->req.len)
|
|
946
|
+
continue; // spurious wakeup or quit signal from other thread
|
|
947
|
+
// Restore the realm from its persistents for this request (so
|
|
948
|
+
// reset_realm can swap it between requests), then dispatch. Nested
|
|
949
|
+
// dispatches during a callback roundtrip reuse the same restored realm
|
|
950
|
+
// and so go through v8_dispatch directly, without re-entering.
|
|
951
|
+
v8_threaded_enter(c->pst, c, dispatch);
|
|
952
|
+
issued_idle_gc = false;
|
|
953
|
+
pthread_cond_signal(&c->cv);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// called by v8_thread_main and from mini_racer_v8.cc,
|
|
958
|
+
// in all cases with Context.mtx held
|
|
959
|
+
void v8_dispatch(Context *c)
|
|
960
|
+
{
|
|
961
|
+
dispatch1(c, c->req.buf, c->req.len);
|
|
962
|
+
buf_reset(&c->req);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// called from mini_racer_v8.cc with Context.mtx held
|
|
966
|
+
// only called when inside v8_call, v8_eval, or v8_pump_message_loop
|
|
967
|
+
void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
|
|
968
|
+
{
|
|
969
|
+
buf_reset(&c->req);
|
|
970
|
+
pthread_cond_signal(&c->cv);
|
|
971
|
+
while (!c->req.len)
|
|
972
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
973
|
+
buf_reset(&c->res);
|
|
974
|
+
*p = c->req.buf;
|
|
975
|
+
*n = c->req.len;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// called from mini_racer_v8.cc with Context.mtx held
|
|
979
|
+
void v8_reply(Context *c, const uint8_t *p, size_t n)
|
|
980
|
+
{
|
|
981
|
+
buf_put(&c->res, p, n);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
static void v8_once_init(void)
|
|
985
|
+
{
|
|
986
|
+
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
|
987
|
+
pthread_once(&once, v8_global_init);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
static void *v8_thread_start(void *arg)
|
|
991
|
+
{
|
|
992
|
+
Context *c;
|
|
993
|
+
|
|
994
|
+
c = arg;
|
|
995
|
+
barrier_wait(&c->early_init);
|
|
996
|
+
v8_once_init();
|
|
997
|
+
v8_thread_init(c, c->snapshot.buf, c->snapshot.len, c->max_memory, c->verbose_exceptions,
|
|
998
|
+
c->host_namespace.len ? (const char *)c->host_namespace.buf : NULL);
|
|
999
|
+
while (c->quit < 2)
|
|
1000
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1001
|
+
context_destroy(c);
|
|
1002
|
+
return NULL;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
static VALUE deserialize1(DesCtx *d, const uint8_t *p, size_t n)
|
|
1006
|
+
{
|
|
1007
|
+
char err[64];
|
|
1008
|
+
|
|
1009
|
+
if (des(&err, p, n, d))
|
|
1010
|
+
rb_raise(runtime_error, "%s", err);
|
|
1011
|
+
if (d->tos != d->stack) // should not happen
|
|
1012
|
+
rb_raise(runtime_error, "parse stack not empty");
|
|
1013
|
+
return d->tos->a;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
static VALUE deserialize(VALUE arg)
|
|
1017
|
+
{
|
|
1018
|
+
struct rendezvous_des *a;
|
|
1019
|
+
Buf *b;
|
|
1020
|
+
|
|
1021
|
+
a = (void *)arg;
|
|
1022
|
+
b = a->res;
|
|
1023
|
+
return deserialize1(a->d, b->buf, b->len);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// called with |rr_mtx| and GVL held; can raise exception
|
|
1027
|
+
static VALUE rendezvous_callback_do(VALUE arg)
|
|
1028
|
+
{
|
|
1029
|
+
struct rendezvous_nogvl *a;
|
|
1030
|
+
VALUE func, args;
|
|
1031
|
+
Context *c;
|
|
1032
|
+
DesCtx d;
|
|
1033
|
+
Buf *b;
|
|
1034
|
+
|
|
1035
|
+
a = (void *)arg;
|
|
1036
|
+
b = a->res;
|
|
1037
|
+
c = a->context;
|
|
1038
|
+
assert(b->len > 0);
|
|
1039
|
+
assert(*b->buf == 'c');
|
|
1040
|
+
DesCtx_init(&d);
|
|
1041
|
+
args = deserialize1(&d, b->buf+1, b->len-1); // skip 'c' marker
|
|
1042
|
+
func = rb_ary_pop(args); // callback id
|
|
1043
|
+
func = rb_ary_entry(c->procs, FIX2LONG(func));
|
|
1044
|
+
return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// called with |rr_mtx| and GVL held; |mtx| is unlocked
|
|
1048
|
+
// callback data is in |a->res|, serialized result goes in |a->req|
|
|
1049
|
+
static void *rendezvous_callback(void *arg)
|
|
1050
|
+
{
|
|
1051
|
+
struct rendezvous_nogvl *a;
|
|
1052
|
+
const char *err;
|
|
1053
|
+
Context *c;
|
|
1054
|
+
int exc;
|
|
1055
|
+
VALUE r;
|
|
1056
|
+
Ser s;
|
|
1057
|
+
|
|
1058
|
+
a = arg;
|
|
1059
|
+
c = a->context;
|
|
1060
|
+
r = rb_protect(rendezvous_callback_do, (VALUE)a, &exc);
|
|
1061
|
+
if (exc) {
|
|
1062
|
+
c->exception = rb_errinfo();
|
|
1063
|
+
rb_set_errinfo(Qnil);
|
|
1064
|
+
goto fail;
|
|
1065
|
+
}
|
|
1066
|
+
ser_init1(&s, 'c'); // callback reply
|
|
1067
|
+
if (serialize(&s, r)) { // should not happen
|
|
1068
|
+
c->exception = rb_exc_new_cstr(internal_error, s.err);
|
|
1069
|
+
ser_reset(&s);
|
|
1070
|
+
goto fail;
|
|
1071
|
+
}
|
|
1072
|
+
out:
|
|
1073
|
+
buf_move(&s.b, a->req);
|
|
1074
|
+
return NULL;
|
|
1075
|
+
fail:
|
|
1076
|
+
ser_init0(&s); // ruby exception pending
|
|
1077
|
+
w_byte(&s, 'e'); // send ruby error message to v8 thread
|
|
1078
|
+
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
|
|
1079
|
+
err = StringValueCStr(r);
|
|
1080
|
+
if (err)
|
|
1081
|
+
w(&s, err, strlen(err));
|
|
1082
|
+
goto out;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// called with |rr_mtx| and GVL held; can raise exception
|
|
1086
|
+
static VALUE rendezvous_resolve_do(VALUE arg)
|
|
1087
|
+
{
|
|
1088
|
+
struct rendezvous_nogvl *a;
|
|
1089
|
+
VALUE args, specifier, referrer_url, ret;
|
|
1090
|
+
Context *c;
|
|
1091
|
+
Module *m;
|
|
1092
|
+
DesCtx d;
|
|
1093
|
+
Buf *b;
|
|
1094
|
+
|
|
1095
|
+
a = (void *)arg;
|
|
1096
|
+
b = a->res;
|
|
1097
|
+
c = a->context;
|
|
1098
|
+
assert(b->len > 0);
|
|
1099
|
+
assert(*b->buf == 'm');
|
|
1100
|
+
DesCtx_init(&d);
|
|
1101
|
+
args = deserialize1(&d, b->buf+1, b->len-1); // skip 'm' marker
|
|
1102
|
+
// args: [specifier, referrer_url]
|
|
1103
|
+
specifier = rb_ary_entry(args, 0);
|
|
1104
|
+
referrer_url = rb_ary_entry(args, 1);
|
|
1105
|
+
if (NIL_P(c->resolve_block))
|
|
1106
|
+
rb_raise(runtime_error, "module resolver requested but no resolver block is active");
|
|
1107
|
+
ret = rb_funcall(c->resolve_block, rb_intern("call"), 2, specifier, referrer_url);
|
|
1108
|
+
if (!rb_obj_is_kind_of(ret, module_class))
|
|
1109
|
+
rb_raise(runtime_error, "module resolver must return a MiniRacer::Module, got %s",
|
|
1110
|
+
rb_obj_classname(ret));
|
|
1111
|
+
TypedData_Get_Struct(ret, Module, &module_type, m);
|
|
1112
|
+
if (m->disposed)
|
|
1113
|
+
rb_raise(runtime_error, "module resolver returned a disposed Module");
|
|
1114
|
+
// Reject cross-Context modules explicitly: handle ids restart at 1
|
|
1115
|
+
// per Context, so without this check a foreign Module silently maps
|
|
1116
|
+
// to whichever local module happens to share its id.
|
|
1117
|
+
{
|
|
1118
|
+
Context *mc;
|
|
1119
|
+
TypedData_Get_Struct(m->context, Context, &context_type, mc);
|
|
1120
|
+
if (mc != c)
|
|
1121
|
+
rb_raise(runtime_error,
|
|
1122
|
+
"module resolver returned a Module from a different Context");
|
|
1123
|
+
}
|
|
1124
|
+
return INT2FIX(m->handle_id);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// called with |rr_mtx| and GVL held; |mtx| is unlocked
|
|
1128
|
+
// resolver data is in |a->res|, serialized handle id goes in |a->req|
|
|
1129
|
+
static void *rendezvous_resolve(void *arg)
|
|
1130
|
+
{
|
|
1131
|
+
struct rendezvous_nogvl *a;
|
|
1132
|
+
const char *err;
|
|
1133
|
+
Context *c;
|
|
1134
|
+
int exc;
|
|
1135
|
+
VALUE r;
|
|
1136
|
+
Ser s;
|
|
1137
|
+
|
|
1138
|
+
a = arg;
|
|
1139
|
+
c = a->context;
|
|
1140
|
+
r = rb_protect(rendezvous_resolve_do, (VALUE)a, &exc);
|
|
1141
|
+
if (exc) {
|
|
1142
|
+
c->exception = rb_errinfo();
|
|
1143
|
+
rb_set_errinfo(Qnil);
|
|
1144
|
+
goto fail;
|
|
1145
|
+
}
|
|
1146
|
+
ser_init1(&s, 'm'); // resolve reply (matches request marker)
|
|
1147
|
+
if (serialize(&s, r)) { // should not happen
|
|
1148
|
+
c->exception = rb_exc_new_cstr(internal_error, s.err);
|
|
1149
|
+
ser_reset(&s);
|
|
1150
|
+
goto fail;
|
|
1151
|
+
}
|
|
1152
|
+
out:
|
|
1153
|
+
buf_move(&s.b, a->req);
|
|
1154
|
+
return NULL;
|
|
1155
|
+
fail:
|
|
1156
|
+
ser_init0(&s);
|
|
1157
|
+
w_byte(&s, 'e');
|
|
1158
|
+
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
|
|
1159
|
+
err = StringValueCStr(r);
|
|
1160
|
+
if (err)
|
|
1161
|
+
w(&s, err, strlen(err));
|
|
1162
|
+
goto out;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// called with |rr_mtx| and GVL held; can raise exception
|
|
1166
|
+
static VALUE rendezvous_dynamic_import_do(VALUE arg)
|
|
1167
|
+
{
|
|
1168
|
+
struct rendezvous_nogvl *a;
|
|
1169
|
+
VALUE args, specifier, referrer_url, ret;
|
|
1170
|
+
Context *c;
|
|
1171
|
+
Module *m;
|
|
1172
|
+
DesCtx d;
|
|
1173
|
+
Buf *b;
|
|
1174
|
+
|
|
1175
|
+
a = (void *)arg;
|
|
1176
|
+
b = a->res;
|
|
1177
|
+
c = a->context;
|
|
1178
|
+
assert(b->len > 0);
|
|
1179
|
+
assert(*b->buf == 'd');
|
|
1180
|
+
DesCtx_init(&d);
|
|
1181
|
+
args = deserialize1(&d, b->buf+1, b->len-1); // skip 'd' marker
|
|
1182
|
+
specifier = rb_ary_entry(args, 0);
|
|
1183
|
+
referrer_url = rb_ary_entry(args, 1);
|
|
1184
|
+
if (NIL_P(c->dynamic_import_resolver))
|
|
1185
|
+
rb_raise(runtime_error,
|
|
1186
|
+
"import() called but Context#dynamic_import_resolver is not set");
|
|
1187
|
+
ret = rb_funcall(c->dynamic_import_resolver, rb_intern("call"), 2,
|
|
1188
|
+
specifier, referrer_url);
|
|
1189
|
+
if (!rb_obj_is_kind_of(ret, module_class))
|
|
1190
|
+
rb_raise(runtime_error,
|
|
1191
|
+
"dynamic import resolver must return a MiniRacer::Module, got %s",
|
|
1192
|
+
rb_obj_classname(ret));
|
|
1193
|
+
TypedData_Get_Struct(ret, Module, &module_type, m);
|
|
1194
|
+
if (m->disposed)
|
|
1195
|
+
rb_raise(runtime_error, "dynamic import resolver returned a disposed Module");
|
|
1196
|
+
{
|
|
1197
|
+
Context *mc;
|
|
1198
|
+
TypedData_Get_Struct(m->context, Context, &context_type, mc);
|
|
1199
|
+
if (mc != c)
|
|
1200
|
+
rb_raise(runtime_error,
|
|
1201
|
+
"dynamic import resolver returned a Module from a different Context");
|
|
1202
|
+
}
|
|
1203
|
+
return INT2FIX(m->handle_id);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// called with |rr_mtx| and GVL held; |mtx| is unlocked
|
|
1207
|
+
// request data is in |a->res|, serialized handle id goes in |a->req|
|
|
1208
|
+
static void *rendezvous_dynamic_import(void *arg)
|
|
1209
|
+
{
|
|
1210
|
+
struct rendezvous_nogvl *a;
|
|
1211
|
+
const char *err;
|
|
1212
|
+
Context *c;
|
|
1213
|
+
int exc;
|
|
1214
|
+
VALUE r;
|
|
1215
|
+
Ser s;
|
|
1216
|
+
|
|
1217
|
+
a = arg;
|
|
1218
|
+
c = a->context;
|
|
1219
|
+
r = rb_protect(rendezvous_dynamic_import_do, (VALUE)a, &exc);
|
|
1220
|
+
if (exc) {
|
|
1221
|
+
c->exception = rb_errinfo();
|
|
1222
|
+
rb_set_errinfo(Qnil);
|
|
1223
|
+
goto fail;
|
|
1224
|
+
}
|
|
1225
|
+
ser_init1(&s, 'd'); // dynamic import reply (matches request marker)
|
|
1226
|
+
if (serialize(&s, r)) { // should not happen
|
|
1227
|
+
c->exception = rb_exc_new_cstr(internal_error, s.err);
|
|
1228
|
+
ser_reset(&s);
|
|
1229
|
+
goto fail;
|
|
1230
|
+
}
|
|
1231
|
+
out:
|
|
1232
|
+
buf_move(&s.b, a->req);
|
|
1233
|
+
return NULL;
|
|
1234
|
+
fail:
|
|
1235
|
+
ser_init0(&s);
|
|
1236
|
+
w_byte(&s, 'e');
|
|
1237
|
+
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
|
|
1238
|
+
err = StringValueCStr(r);
|
|
1239
|
+
if (err)
|
|
1240
|
+
w(&s, err, strlen(err));
|
|
1241
|
+
goto out;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// load_module_graph batched fetch ('f') and resolve ('r') callbacks. Each
|
|
1245
|
+
// deserializes the batch v8 sent, calls the per-load Ruby block, and serializes
|
|
1246
|
+
// the array reply back under the same marker. Mirrors rendezvous_resolve.
|
|
1247
|
+
|
|
1248
|
+
// called with |rr_mtx| and GVL held; can raise exception
|
|
1249
|
+
static VALUE rendezvous_graph_fetch_do(VALUE arg)
|
|
1250
|
+
{
|
|
1251
|
+
struct rendezvous_nogvl *a = (void *)arg;
|
|
1252
|
+
Context *c = a->context;
|
|
1253
|
+
Buf *b = a->res;
|
|
1254
|
+
DesCtx d;
|
|
1255
|
+
VALUE urls;
|
|
1256
|
+
|
|
1257
|
+
assert(b->len > 0 && *b->buf == 'f');
|
|
1258
|
+
DesCtx_init(&d);
|
|
1259
|
+
urls = deserialize1(&d, b->buf+1, b->len-1); // skip 'f' marker; [url, ...]
|
|
1260
|
+
if (NIL_P(c->graph_fetch_block))
|
|
1261
|
+
rb_raise(runtime_error, "module fetch requested but no fetch_batch block is active");
|
|
1262
|
+
// -> [[source, cached_data]|nil, ...]
|
|
1263
|
+
VALUE ret = rb_funcall(c->graph_fetch_block, rb_intern("call"), 1, urls);
|
|
1264
|
+
// The cached_data element is an ASCII-8BIT String (same shape as
|
|
1265
|
+
// Module#cached_data / compile_module(cached_data:)). Wrap it as
|
|
1266
|
+
// MiniRacer::Binary so it crosses to V8 as a Uint8Array — a bare String
|
|
1267
|
+
// would serialize as a JS string and the code cache would be silently
|
|
1268
|
+
// dropped (and binary bytes mangled by UTF-8). Build fresh rows rather than
|
|
1269
|
+
// mutating the array the caller's block returned.
|
|
1270
|
+
if (TYPE(ret) == T_ARRAY && !NIL_P(binary_class)) {
|
|
1271
|
+
long len = RARRAY_LEN(ret), i;
|
|
1272
|
+
VALUE wrapped = rb_ary_new_capa(len);
|
|
1273
|
+
for (i = 0; i < len; i++) {
|
|
1274
|
+
VALUE row = rb_ary_entry(ret, i);
|
|
1275
|
+
if (TYPE(row) == T_ARRAY && RARRAY_LEN(row) >= 2 &&
|
|
1276
|
+
TYPE(rb_ary_entry(row, 1)) == T_STRING) {
|
|
1277
|
+
VALUE r2 = rb_ary_dup(row);
|
|
1278
|
+
rb_ary_store(r2, 1, rb_funcall(binary_class, rb_intern("new"), 1,
|
|
1279
|
+
rb_ary_entry(row, 1)));
|
|
1280
|
+
rb_ary_push(wrapped, r2);
|
|
1281
|
+
} else {
|
|
1282
|
+
rb_ary_push(wrapped, row);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
ret = wrapped;
|
|
1286
|
+
}
|
|
1287
|
+
return ret;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// called with |rr_mtx| and GVL held; can raise exception
|
|
1291
|
+
static VALUE rendezvous_graph_resolve_do(VALUE arg)
|
|
1292
|
+
{
|
|
1293
|
+
struct rendezvous_nogvl *a = (void *)arg;
|
|
1294
|
+
Context *c = a->context;
|
|
1295
|
+
Buf *b = a->res;
|
|
1296
|
+
DesCtx d;
|
|
1297
|
+
VALUE edges;
|
|
1298
|
+
|
|
1299
|
+
assert(b->len > 0 && *b->buf == 'r');
|
|
1300
|
+
DesCtx_init(&d);
|
|
1301
|
+
edges = deserialize1(&d, b->buf+1, b->len-1); // skip 'r' marker; [[spec, ref], ...]
|
|
1302
|
+
if (NIL_P(c->graph_resolve_block))
|
|
1303
|
+
rb_raise(runtime_error, "module resolve requested but no resolve block is active");
|
|
1304
|
+
// -> [url|nil, ...]
|
|
1305
|
+
return rb_funcall(c->graph_resolve_block, rb_intern("call"), 1, edges);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// called with |rr_mtx| and GVL held; |mtx| is unlocked
|
|
1309
|
+
// batch data is in |a->res| (marker |marker|), serialized array reply in |a->req|
|
|
1310
|
+
static void rendezvous_graph_batch(struct rendezvous_nogvl *a, char marker,
|
|
1311
|
+
VALUE (*body)(VALUE))
|
|
1312
|
+
{
|
|
1313
|
+
const char *err;
|
|
1314
|
+
Context *c = a->context;
|
|
1315
|
+
int exc;
|
|
1316
|
+
VALUE r;
|
|
1317
|
+
Ser s;
|
|
1318
|
+
|
|
1319
|
+
r = rb_protect(body, (VALUE)a, &exc);
|
|
1320
|
+
if (exc) {
|
|
1321
|
+
c->exception = rb_errinfo();
|
|
1322
|
+
rb_set_errinfo(Qnil);
|
|
1323
|
+
goto fail;
|
|
1324
|
+
}
|
|
1325
|
+
ser_init1(&s, (uint8_t)marker); // reply matches request marker
|
|
1326
|
+
if (serialize(&s, r)) {
|
|
1327
|
+
c->exception = rb_exc_new_cstr(internal_error, s.err);
|
|
1328
|
+
ser_reset(&s);
|
|
1329
|
+
goto fail;
|
|
1330
|
+
}
|
|
1331
|
+
out:
|
|
1332
|
+
buf_move(&s.b, a->req);
|
|
1333
|
+
return;
|
|
1334
|
+
fail:
|
|
1335
|
+
ser_init0(&s);
|
|
1336
|
+
w_byte(&s, 'e');
|
|
1337
|
+
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
|
|
1338
|
+
err = StringValueCStr(r);
|
|
1339
|
+
if (err)
|
|
1340
|
+
w(&s, err, strlen(err));
|
|
1341
|
+
goto out;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
static void *rendezvous_graph_fetch(void *arg)
|
|
1345
|
+
{
|
|
1346
|
+
rendezvous_graph_batch(arg, 'f', rendezvous_graph_fetch_do);
|
|
1347
|
+
return NULL;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
static void *rendezvous_graph_resolve(void *arg)
|
|
1351
|
+
{
|
|
1352
|
+
rendezvous_graph_batch(arg, 'r', rendezvous_graph_resolve_do);
|
|
1353
|
+
return NULL;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
static void *single_threaded_runner(void *arg)
|
|
1357
|
+
{
|
|
1358
|
+
Context *c;
|
|
1359
|
+
|
|
1360
|
+
c = arg;
|
|
1361
|
+
pthread_mutex_lock(&c->mtx);
|
|
1362
|
+
for (;;) {
|
|
1363
|
+
while (!c->req.len && atomic_load(&c->quit) < 1)
|
|
1364
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1365
|
+
if (atomic_load(&c->quit) >= 1)
|
|
1366
|
+
break;
|
|
1367
|
+
v8_single_threaded_enter(c->pst, c, dispatch);
|
|
1368
|
+
pthread_cond_signal(&c->cv);
|
|
1369
|
+
}
|
|
1370
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1371
|
+
return NULL;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
static int single_threaded_runner_start(Context *c)
|
|
1375
|
+
{
|
|
1376
|
+
pid_t pid;
|
|
1377
|
+
int r;
|
|
1378
|
+
|
|
1379
|
+
pid = getpid();
|
|
1380
|
+
if (c->single_threaded_thr_started && c->single_threaded_pid == pid)
|
|
1381
|
+
return 0;
|
|
1382
|
+
c->single_threaded_thr_started = 0;
|
|
1383
|
+
c->single_threaded_pid = pid;
|
|
1384
|
+
r = pthread_create(&c->single_threaded_thr, NULL, single_threaded_runner, c);
|
|
1385
|
+
if (!r)
|
|
1386
|
+
c->single_threaded_thr_started = 1;
|
|
1387
|
+
return r;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
static inline void *rendezvous_nogvl(void *arg)
|
|
1391
|
+
{
|
|
1392
|
+
struct rendezvous_nogvl *a;
|
|
1393
|
+
Context *c;
|
|
1394
|
+
int r;
|
|
1395
|
+
|
|
1396
|
+
a = arg;
|
|
1397
|
+
c = a->context;
|
|
1398
|
+
pthread_mutex_lock(&c->rr_mtx);
|
|
1399
|
+
if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
|
|
1400
|
+
fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
|
|
1401
|
+
fflush(stderr);
|
|
1402
|
+
}
|
|
1403
|
+
c->depth++;
|
|
1404
|
+
next:
|
|
1405
|
+
pthread_mutex_lock(&c->mtx);
|
|
1406
|
+
assert(c->req.len == 0);
|
|
1407
|
+
assert(c->res.len == 0);
|
|
1408
|
+
buf_move(a->req, &c->req); // v8 thread takes ownership of req
|
|
1409
|
+
if (single_threaded) {
|
|
1410
|
+
r = single_threaded_runner_start(c);
|
|
1411
|
+
if (r) {
|
|
1412
|
+
buf_move(&c->req, a->req);
|
|
1413
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1414
|
+
c->depth--;
|
|
1415
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1416
|
+
return (void *)(intptr_t)r;
|
|
1417
|
+
}
|
|
1418
|
+
pthread_cond_signal(&c->cv);
|
|
1419
|
+
do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
|
|
1420
|
+
} else {
|
|
1421
|
+
pthread_cond_signal(&c->cv);
|
|
1422
|
+
do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
|
|
1423
|
+
}
|
|
1424
|
+
buf_move(&c->res, a->res);
|
|
1425
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1426
|
+
if (*a->res->buf == 'm') { // module resolver request?
|
|
1427
|
+
rb_thread_call_with_gvl(rendezvous_resolve, a);
|
|
1428
|
+
buf_reset(a->res);
|
|
1429
|
+
goto next;
|
|
1430
|
+
}
|
|
1431
|
+
if (*a->res->buf == 'd') { // dynamic import() request?
|
|
1432
|
+
rb_thread_call_with_gvl(rendezvous_dynamic_import, a);
|
|
1433
|
+
buf_reset(a->res);
|
|
1434
|
+
goto next;
|
|
1435
|
+
}
|
|
1436
|
+
if (*a->res->buf == 'f') { // load_module_graph fetch batch?
|
|
1437
|
+
rb_thread_call_with_gvl(rendezvous_graph_fetch, a);
|
|
1438
|
+
buf_reset(a->res);
|
|
1439
|
+
goto next;
|
|
1440
|
+
}
|
|
1441
|
+
if (*a->res->buf == 'r') { // load_module_graph resolve batch?
|
|
1442
|
+
rb_thread_call_with_gvl(rendezvous_graph_resolve, a);
|
|
1443
|
+
buf_reset(a->res);
|
|
1444
|
+
goto next;
|
|
1445
|
+
}
|
|
1446
|
+
if (*a->res->buf == 'c') { // js -> ruby callback?
|
|
1447
|
+
rb_thread_call_with_gvl(rendezvous_callback, a);
|
|
1448
|
+
buf_reset(a->res);
|
|
1449
|
+
goto next;
|
|
1450
|
+
}
|
|
1451
|
+
c->depth--;
|
|
1452
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1453
|
+
return NULL;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
|
|
1457
|
+
{
|
|
1458
|
+
void *r;
|
|
1459
|
+
|
|
1460
|
+
if (atomic_load(&c->quit)) {
|
|
1461
|
+
buf_reset(req);
|
|
1462
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
1463
|
+
}
|
|
1464
|
+
r = rb_nogvl(rendezvous_nogvl, &(struct rendezvous_nogvl){c, req, res},
|
|
1465
|
+
NULL, NULL, 0);
|
|
1466
|
+
if (r)
|
|
1467
|
+
rb_raise(runtime_error, "pthread_create: %s", strerror((int)(intptr_t)r));
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// send request to & receive reply from v8 thread; takes ownership of |req|
|
|
1471
|
+
// can raise exceptions and longjmp away but won't leak |req|
|
|
1472
|
+
static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
|
|
1473
|
+
{
|
|
1474
|
+
VALUE r;
|
|
1475
|
+
Buf res;
|
|
1476
|
+
int exc;
|
|
1477
|
+
|
|
1478
|
+
rendezvous_no_des(c, req, &res); // takes ownership of |req|
|
|
1479
|
+
r = c->exception;
|
|
1480
|
+
c->exception = Qnil;
|
|
1481
|
+
// if js land didn't handle exception from ruby callback, re-raise it now
|
|
1482
|
+
if (res.len == 1 && *res.buf == 'e') {
|
|
1483
|
+
assert(!NIL_P(r));
|
|
1484
|
+
rb_exc_raise(r);
|
|
1485
|
+
}
|
|
1486
|
+
r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
|
|
1487
|
+
buf_reset(&res);
|
|
1488
|
+
if (exc) {
|
|
1489
|
+
r = rb_errinfo();
|
|
1490
|
+
rb_set_errinfo(Qnil);
|
|
1491
|
+
rb_exc_raise(r);
|
|
1492
|
+
}
|
|
1493
|
+
return r;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
static VALUE rendezvous(Context *c, Buf *req)
|
|
1497
|
+
{
|
|
1498
|
+
DesCtx d;
|
|
1499
|
+
|
|
1500
|
+
DesCtx_init(&d);
|
|
1501
|
+
return rendezvous1(c, req, &d);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
static void handle_exception(VALUE e)
|
|
1505
|
+
{
|
|
1506
|
+
const char *s;
|
|
1507
|
+
VALUE klass;
|
|
1508
|
+
|
|
1509
|
+
if (NIL_P(e))
|
|
1510
|
+
return;
|
|
1511
|
+
e = StringValue(e);
|
|
1512
|
+
s = StringValueCStr(e);
|
|
1513
|
+
switch (*s) {
|
|
1514
|
+
case NO_ERROR:
|
|
1515
|
+
return;
|
|
1516
|
+
case INTERNAL_ERROR:
|
|
1517
|
+
klass = internal_error;
|
|
1518
|
+
break;
|
|
1519
|
+
case MEMORY_ERROR:
|
|
1520
|
+
klass = memory_error;
|
|
1521
|
+
break;
|
|
1522
|
+
case PARSE_ERROR:
|
|
1523
|
+
klass = parse_error;
|
|
1524
|
+
break;
|
|
1525
|
+
case RUNTIME_ERROR:
|
|
1526
|
+
klass = runtime_error;
|
|
1527
|
+
break;
|
|
1528
|
+
case TERMINATED_ERROR:
|
|
1529
|
+
klass = terminated_error;
|
|
1530
|
+
break;
|
|
1531
|
+
default:
|
|
1532
|
+
rb_raise(internal_error, "bad error class %02x", *s);
|
|
1533
|
+
}
|
|
1534
|
+
rb_enc_raise(rb_enc_get(e), klass, "%s", s+1);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
static VALUE context_alloc(VALUE klass)
|
|
1538
|
+
{
|
|
1539
|
+
pthread_mutexattr_t mattr;
|
|
1540
|
+
pthread_condattr_t cattr;
|
|
1541
|
+
const char *cause;
|
|
1542
|
+
Context *c;
|
|
1543
|
+
VALUE f, a;
|
|
1544
|
+
int r;
|
|
1545
|
+
|
|
1546
|
+
// Safe to lazy init because we hold the GVL
|
|
1547
|
+
if (NIL_P(date_time_class)) {
|
|
1548
|
+
f = rb_intern("const_defined?");
|
|
1549
|
+
a = rb_str_new_cstr("DateTime");
|
|
1550
|
+
if (Qtrue == rb_funcall(rb_cObject, f, 1, a))
|
|
1551
|
+
date_time_class = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
|
1552
|
+
}
|
|
1553
|
+
if (NIL_P(binary_class)) {
|
|
1554
|
+
VALUE m = rb_const_get(rb_cObject, rb_intern("MiniRacer"));
|
|
1555
|
+
if (Qtrue == rb_funcall(m, rb_intern("const_defined?"), 1, rb_str_new_cstr("Binary")))
|
|
1556
|
+
binary_class = rb_const_get(m, rb_intern("Binary"));
|
|
1557
|
+
}
|
|
1558
|
+
c = ruby_xmalloc(sizeof(*c));
|
|
1559
|
+
memset(c, 0, sizeof(*c));
|
|
1560
|
+
c->exception = Qnil;
|
|
1561
|
+
c->resolve_block = Qnil;
|
|
1562
|
+
c->dynamic_import_resolver = Qnil;
|
|
1563
|
+
c->graph_resolve_block = Qnil;
|
|
1564
|
+
c->graph_fetch_block = Qnil;
|
|
1565
|
+
c->procs = rb_ary_new();
|
|
1566
|
+
buf_init(&c->snapshot);
|
|
1567
|
+
buf_init(&c->host_namespace);
|
|
1568
|
+
buf_init(&c->req);
|
|
1569
|
+
buf_init(&c->res);
|
|
1570
|
+
cause = "pthread_condattr_init";
|
|
1571
|
+
if ((r = pthread_condattr_init(&cattr)))
|
|
1572
|
+
goto fail0;
|
|
1573
|
+
#ifndef __APPLE__
|
|
1574
|
+
pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
|
|
1575
|
+
#endif
|
|
1576
|
+
cause = "pthread_mutexattr_init";
|
|
1577
|
+
if ((r = pthread_mutexattr_init(&mattr)))
|
|
1578
|
+
goto fail1;
|
|
1579
|
+
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
|
|
1580
|
+
cause = "pthread_mutex_init";
|
|
1581
|
+
r = pthread_mutex_init(&c->rr_mtx, &mattr);
|
|
1582
|
+
pthread_mutexattr_destroy(&mattr);
|
|
1583
|
+
if (r)
|
|
1584
|
+
goto fail1;
|
|
1585
|
+
if (pthread_mutex_init(&c->mtx, NULL))
|
|
1586
|
+
goto fail2;
|
|
1587
|
+
cause = "pthread_cond_init";
|
|
1588
|
+
if ((r = pthread_cond_init(&c->cv, &cattr)))
|
|
1589
|
+
goto fail3;
|
|
1590
|
+
cause = "pthread_mutex_init";
|
|
1591
|
+
if ((r = pthread_mutex_init(&c->wd.mtx, NULL)))
|
|
1592
|
+
goto fail4;
|
|
1593
|
+
cause = "pthread_cond_init";
|
|
1594
|
+
if (pthread_cond_init(&c->wd.cv, &cattr))
|
|
1595
|
+
goto fail5;
|
|
1596
|
+
cause = "barrier_init";
|
|
1597
|
+
if ((r = barrier_init(&c->early_init, 2)))
|
|
1598
|
+
goto fail6;
|
|
1599
|
+
cause = "barrier_init";
|
|
1600
|
+
if ((r = barrier_init(&c->late_init, 2)))
|
|
1601
|
+
goto fail7;
|
|
1602
|
+
pthread_condattr_destroy(&cattr);
|
|
1603
|
+
return TypedData_Wrap_Struct(klass, &context_type, c);
|
|
1604
|
+
fail7:
|
|
1605
|
+
barrier_destroy(&c->early_init);
|
|
1606
|
+
fail6:
|
|
1607
|
+
pthread_cond_destroy(&c->wd.cv);
|
|
1608
|
+
fail5:
|
|
1609
|
+
pthread_mutex_destroy(&c->wd.mtx);
|
|
1610
|
+
fail4:
|
|
1611
|
+
pthread_cond_destroy(&c->cv);
|
|
1612
|
+
fail3:
|
|
1613
|
+
pthread_mutex_destroy(&c->mtx);
|
|
1614
|
+
fail2:
|
|
1615
|
+
pthread_mutex_destroy(&c->rr_mtx);
|
|
1616
|
+
fail1:
|
|
1617
|
+
pthread_condattr_destroy(&cattr);
|
|
1618
|
+
fail0:
|
|
1619
|
+
ruby_xfree(c);
|
|
1620
|
+
rb_raise(runtime_error, "%s: %s", cause, strerror(r));
|
|
1621
|
+
return Qnil; // pacify compiler
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
static void *context_free_thread_do(void *arg)
|
|
1625
|
+
{
|
|
1626
|
+
Context *c;
|
|
1627
|
+
|
|
1628
|
+
c = arg;
|
|
1629
|
+
if (single_threaded && c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1630
|
+
pthread_mutex_lock(&c->mtx);
|
|
1631
|
+
atomic_store(&c->quit, 2);
|
|
1632
|
+
pthread_cond_signal(&c->cv);
|
|
1633
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1634
|
+
pthread_join(c->single_threaded_thr, NULL);
|
|
1635
|
+
}
|
|
1636
|
+
if (c->pst)
|
|
1637
|
+
v8_single_threaded_dispose(c->pst);
|
|
1638
|
+
pthread_mutex_lock(&c->mtx);
|
|
1639
|
+
context_destroy(c);
|
|
1640
|
+
return NULL;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
static void context_free_thread(Context *c)
|
|
1644
|
+
{
|
|
1645
|
+
pthread_t thr;
|
|
1646
|
+
int r;
|
|
1647
|
+
|
|
1648
|
+
// dispose on another thread so we don't block when trying to
|
|
1649
|
+
// enter an isolate that's in a stuck state; that *should* be
|
|
1650
|
+
// impossible but apparently it happened regularly before the
|
|
1651
|
+
// rewrite and I'm carrying it over out of an abundance of caution
|
|
1652
|
+
if ((r = pthread_create(&thr, NULL, context_free_thread_do, c))) {
|
|
1653
|
+
fprintf(stderr, "mini_racer: pthread_create: %s", strerror(r));
|
|
1654
|
+
fflush(stderr);
|
|
1655
|
+
context_free_thread_do(c);
|
|
1656
|
+
} else {
|
|
1657
|
+
pthread_detach(thr);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
static void context_free(void *arg)
|
|
1662
|
+
{
|
|
1663
|
+
Context *c;
|
|
1664
|
+
|
|
1665
|
+
c = arg;
|
|
1666
|
+
if (single_threaded) {
|
|
1667
|
+
context_free_thread(c);
|
|
1668
|
+
} else {
|
|
1669
|
+
pthread_mutex_lock(&c->mtx);
|
|
1670
|
+
c->quit = 2; // 2 = v8 thread frees
|
|
1671
|
+
pthread_cond_signal(&c->cv);
|
|
1672
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
static void context_destroy(Context *c)
|
|
1677
|
+
{
|
|
1678
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1679
|
+
pthread_mutex_destroy(&c->mtx);
|
|
1680
|
+
pthread_cond_destroy(&c->cv);
|
|
1681
|
+
barrier_destroy(&c->early_init);
|
|
1682
|
+
barrier_destroy(&c->late_init);
|
|
1683
|
+
pthread_mutex_destroy(&c->wd.mtx);
|
|
1684
|
+
pthread_cond_destroy(&c->wd.cv);
|
|
1685
|
+
buf_reset(&c->snapshot);
|
|
1686
|
+
buf_reset(&c->host_namespace);
|
|
1687
|
+
buf_reset(&c->req);
|
|
1688
|
+
buf_reset(&c->res);
|
|
1689
|
+
ruby_xfree(c);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
static void context_mark(void *arg)
|
|
1693
|
+
{
|
|
1694
|
+
Context *c;
|
|
1695
|
+
|
|
1696
|
+
c = arg;
|
|
1697
|
+
rb_gc_mark(c->procs);
|
|
1698
|
+
rb_gc_mark(c->exception);
|
|
1699
|
+
rb_gc_mark(c->resolve_block);
|
|
1700
|
+
rb_gc_mark(c->dynamic_import_resolver);
|
|
1701
|
+
rb_gc_mark(c->graph_resolve_block);
|
|
1702
|
+
rb_gc_mark(c->graph_fetch_block);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
static size_t context_size(const void *arg)
|
|
1706
|
+
{
|
|
1707
|
+
const Context *c = arg;
|
|
1708
|
+
return sizeof(*c);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
|
|
1712
|
+
{
|
|
1713
|
+
Context *c;
|
|
1714
|
+
VALUE e;
|
|
1715
|
+
Ser s;
|
|
1716
|
+
|
|
1717
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1718
|
+
// request is (A)ttach, [name, id] array
|
|
1719
|
+
ser_init1(&s, 'A');
|
|
1720
|
+
ser_array_begin(&s, 2);
|
|
1721
|
+
add_string(&s, name);
|
|
1722
|
+
ser_int(&s, RARRAY_LENINT(c->procs));
|
|
1723
|
+
ser_array_end(&s, 2);
|
|
1724
|
+
rb_ary_push(c->procs, proc);
|
|
1725
|
+
// response is an exception or undefined
|
|
1726
|
+
e = rendezvous(c, &s.b);
|
|
1727
|
+
handle_exception(e);
|
|
1728
|
+
return Qnil;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
static void *context_dispose_do(void *arg)
|
|
1732
|
+
{
|
|
1733
|
+
Context *c;
|
|
1734
|
+
|
|
1735
|
+
c = arg;
|
|
1736
|
+
if (single_threaded) {
|
|
1737
|
+
pthread_mutex_lock(&c->mtx);
|
|
1738
|
+
while (c->req.len || c->res.len)
|
|
1739
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1740
|
+
atomic_store(&c->quit, 1); // disposed
|
|
1741
|
+
if (c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1742
|
+
pthread_cond_signal(&c->cv);
|
|
1743
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1744
|
+
pthread_join(c->single_threaded_thr, NULL);
|
|
1745
|
+
pthread_mutex_lock(&c->mtx);
|
|
1746
|
+
c->single_threaded_thr_started = 0;
|
|
1747
|
+
}
|
|
1748
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1749
|
+
} else {
|
|
1750
|
+
pthread_mutex_lock(&c->mtx);
|
|
1751
|
+
while (c->req.len || c->res.len)
|
|
1752
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1753
|
+
atomic_store(&c->quit, 1); // disposed
|
|
1754
|
+
pthread_cond_signal(&c->cv); // wake up v8 thread
|
|
1755
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1756
|
+
}
|
|
1757
|
+
return NULL;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
static VALUE context_dispose(VALUE self)
|
|
1761
|
+
{
|
|
1762
|
+
Context *c;
|
|
1763
|
+
|
|
1764
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1765
|
+
rb_thread_call_without_gvl(context_dispose_do, c, NULL, NULL);
|
|
1766
|
+
return Qnil;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
static VALUE context_stop(VALUE self)
|
|
1770
|
+
{
|
|
1771
|
+
Context *c;
|
|
1772
|
+
|
|
1773
|
+
// does not grab |mtx| because Context.stop can be called from another
|
|
1774
|
+
// thread and then we deadlock if e.g. the V8 thread busy-loops in JS
|
|
1775
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1776
|
+
if (atomic_load(&c->quit))
|
|
1777
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
1778
|
+
v8_terminate_execution(c->pst);
|
|
1779
|
+
return Qnil;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
static VALUE context_call(int argc, VALUE *argv, VALUE self)
|
|
1783
|
+
{
|
|
1784
|
+
VALUE name, args;
|
|
1785
|
+
VALUE a, e;
|
|
1786
|
+
Context *c;
|
|
1787
|
+
Ser s;
|
|
1788
|
+
|
|
1789
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1790
|
+
rb_scan_args(argc, argv, "1*", &name, &args);
|
|
1791
|
+
Check_Type(name, T_STRING);
|
|
1792
|
+
rb_ary_unshift(args, name);
|
|
1793
|
+
// request is (C)all, [name, args...] array
|
|
1794
|
+
ser_init1(&s, 'C');
|
|
1795
|
+
if (serialize(&s, args)) {
|
|
1796
|
+
ser_reset(&s);
|
|
1797
|
+
rb_raise(runtime_error, "Context.call: %s", s.err);
|
|
1798
|
+
}
|
|
1799
|
+
// response is [result, err] array
|
|
1800
|
+
a = rendezvous(c, &s.b); // takes ownership of |s.b|
|
|
1801
|
+
e = rb_ary_pop(a);
|
|
1802
|
+
handle_exception(e);
|
|
1803
|
+
return rb_ary_pop(a);
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
static VALUE context_eval(int argc, VALUE *argv, VALUE self)
|
|
1807
|
+
{
|
|
1808
|
+
VALUE a, e, source, filename, kwargs;
|
|
1809
|
+
Context *c;
|
|
1810
|
+
Ser s;
|
|
1811
|
+
|
|
1812
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1813
|
+
filename = Qnil;
|
|
1814
|
+
rb_scan_args(argc, argv, "1:", &source, &kwargs);
|
|
1815
|
+
Check_Type(source, T_STRING);
|
|
1816
|
+
if (!NIL_P(kwargs))
|
|
1817
|
+
filename = rb_hash_aref(kwargs, rb_id2sym(rb_intern("filename")));
|
|
1818
|
+
if (NIL_P(filename))
|
|
1819
|
+
filename = rb_str_new_cstr("<eval>");
|
|
1820
|
+
Check_Type(filename, T_STRING);
|
|
1821
|
+
// request is (E)val, [filename, source] array
|
|
1822
|
+
ser_init1(&s, 'E');
|
|
1823
|
+
ser_array_begin(&s, 2);
|
|
1824
|
+
add_string(&s, filename);
|
|
1825
|
+
add_string(&s, source);
|
|
1826
|
+
ser_array_end(&s, 2);
|
|
1827
|
+
// response is [result, errname] array
|
|
1828
|
+
a = rendezvous(c, &s.b); // takes ownership of |s.b|
|
|
1829
|
+
e = rb_ary_pop(a);
|
|
1830
|
+
handle_exception(e);
|
|
1831
|
+
return rb_ary_pop(a);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
static VALUE context_heap_stats(VALUE self)
|
|
1835
|
+
{
|
|
1836
|
+
VALUE a, h, k, v;
|
|
1837
|
+
Context *c;
|
|
1838
|
+
int i, n;
|
|
1839
|
+
Buf b;
|
|
1840
|
+
|
|
1841
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1842
|
+
buf_init(&b);
|
|
1843
|
+
buf_putc(&b, 'S'); // (S)tats, returns object
|
|
1844
|
+
h = rendezvous(c, &b); // takes ownership of |b|
|
|
1845
|
+
a = rb_ary_new();
|
|
1846
|
+
rb_hash_foreach(h, collect, a);
|
|
1847
|
+
for (i = 0, n = RARRAY_LENINT(a); i < n; i += 2) {
|
|
1848
|
+
k = rb_ary_entry(a, i+0);
|
|
1849
|
+
v = rb_ary_entry(a, i+1);
|
|
1850
|
+
rb_hash_delete(h, k);
|
|
1851
|
+
rb_hash_aset(h, rb_str_intern(k), v); // turn "key" into :key
|
|
1852
|
+
}
|
|
1853
|
+
return h;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
static VALUE context_heap_snapshot(VALUE self)
|
|
1857
|
+
{
|
|
1858
|
+
Buf req, res;
|
|
1859
|
+
Context *c;
|
|
1860
|
+
|
|
1861
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1862
|
+
buf_init(&req);
|
|
1863
|
+
buf_putc(&req, 'H'); // (H)eap snapshot, returns plain bytes
|
|
1864
|
+
rendezvous_no_des(c, &req, &res); // takes ownership of |req|
|
|
1865
|
+
return rb_utf8_str_new((char *)res.buf, res.len);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
static VALUE context_perform_microtask_checkpoint(VALUE self)
|
|
1869
|
+
{
|
|
1870
|
+
Context *c;
|
|
1871
|
+
Buf b;
|
|
1872
|
+
|
|
1873
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1874
|
+
buf_init(&b);
|
|
1875
|
+
buf_putc(&b, 'M'); // (M)icrotask checkpoint, returns nil
|
|
1876
|
+
return rendezvous(c, &b); // takes ownership of |b|
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// EXPERIMENTAL (spike): tears down the current JavaScript realm (globalThis,
|
|
1880
|
+
// every global it carried, registered custom elements, pending timers, ...)
|
|
1881
|
+
// and installs a pristine one from the startup snapshot, keeping the warm
|
|
1882
|
+
// isolate. The opt-in host namespace and any attach()ed host functions are
|
|
1883
|
+
// re-applied automatically. Previously compiled scripts/modules are realm-bound
|
|
1884
|
+
// and are invalidated by the reset. Refused from within a host callback.
|
|
1885
|
+
static VALUE context_reset_realm(VALUE self)
|
|
1886
|
+
{
|
|
1887
|
+
Context *c;
|
|
1888
|
+
VALUE e;
|
|
1889
|
+
Buf b;
|
|
1890
|
+
|
|
1891
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1892
|
+
buf_init(&b);
|
|
1893
|
+
buf_putc(&b, 'F'); // (F)resh realm, returns err or undefined
|
|
1894
|
+
e = rendezvous(c, &b); // takes ownership of |b|
|
|
1895
|
+
handle_exception(e);
|
|
1896
|
+
return Qnil;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
static VALUE context_pump_message_loop(VALUE self)
|
|
1900
|
+
{
|
|
1901
|
+
Context *c;
|
|
1902
|
+
Buf b;
|
|
1903
|
+
|
|
1904
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1905
|
+
buf_init(&b);
|
|
1906
|
+
buf_putc(&b, 'P'); // (P)ump, returns bool
|
|
1907
|
+
return rendezvous(c, &b); // takes ownership of |b|
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
static VALUE context_low_memory_notification(VALUE self)
|
|
1911
|
+
{
|
|
1912
|
+
Buf req, res;
|
|
1913
|
+
Context *c;
|
|
1914
|
+
|
|
1915
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1916
|
+
buf_init(&req);
|
|
1917
|
+
buf_putc(&req, 'L'); // (L)ow memory notification, returns nothing
|
|
1918
|
+
rendezvous_no_des(c, &req, &res); // takes ownership of |req|
|
|
1919
|
+
return Qnil;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
static int platform_set_flag1(VALUE k, VALUE v)
|
|
1923
|
+
{
|
|
1924
|
+
char *p, *q, buf[256];
|
|
1925
|
+
int ok;
|
|
1926
|
+
|
|
1927
|
+
k = rb_funcall(k, rb_intern("to_s"), 0);
|
|
1928
|
+
Check_Type(k, T_STRING);
|
|
1929
|
+
if (!NIL_P(v)) {
|
|
1930
|
+
v = rb_funcall(v, rb_intern("to_s"), 0);
|
|
1931
|
+
Check_Type(v, T_STRING);
|
|
1932
|
+
}
|
|
1933
|
+
p = RSTRING_PTR(k);
|
|
1934
|
+
if (!strncmp(p, "--", 2))
|
|
1935
|
+
p += 2;
|
|
1936
|
+
if (NIL_P(v)) {
|
|
1937
|
+
snprintf(buf, sizeof(buf), "--%s", p);
|
|
1938
|
+
} else {
|
|
1939
|
+
snprintf(buf, sizeof(buf), "--%s=%s", p, RSTRING_PTR(v));
|
|
1940
|
+
}
|
|
1941
|
+
p = buf;
|
|
1942
|
+
pthread_mutex_lock(&flags_mtx);
|
|
1943
|
+
if (!flags.buf)
|
|
1944
|
+
buf_init(&flags);
|
|
1945
|
+
ok = (*flags.buf != 1);
|
|
1946
|
+
if (ok) {
|
|
1947
|
+
buf_put(&flags, p, 1+strlen(p)); // include trailing \0
|
|
1948
|
+
// strip dashes and underscores to reduce the number of variant
|
|
1949
|
+
// spellings (--no-single-threaded, --nosingle-threaded,
|
|
1950
|
+
// --no_single_threaded, etc.)
|
|
1951
|
+
p = q = buf;
|
|
1952
|
+
for (;;) {
|
|
1953
|
+
if (*p != '-')
|
|
1954
|
+
if (*p != '_')
|
|
1955
|
+
*q++ = *p;
|
|
1956
|
+
if (!*p++)
|
|
1957
|
+
break;
|
|
1958
|
+
}
|
|
1959
|
+
if (!strcmp(buf, "singlethreaded")) {
|
|
1960
|
+
single_threaded = 1;
|
|
1961
|
+
} else if (!strcmp(buf, "nosinglethreaded")) {
|
|
1962
|
+
single_threaded = 0;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
pthread_mutex_unlock(&flags_mtx);
|
|
1966
|
+
return ok;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
static VALUE platform_set_flags(int argc, VALUE *argv, VALUE klass)
|
|
1970
|
+
{
|
|
1971
|
+
VALUE args, kwargs, k, v;
|
|
1972
|
+
int i, n;
|
|
1973
|
+
|
|
1974
|
+
(void)&klass;
|
|
1975
|
+
rb_scan_args(argc, argv, "*:", &args, &kwargs);
|
|
1976
|
+
Check_Type(args, T_ARRAY);
|
|
1977
|
+
for (i = 0, n = RARRAY_LENINT(args); i < n; i++) {
|
|
1978
|
+
k = rb_ary_entry(args, i);
|
|
1979
|
+
v = Qnil;
|
|
1980
|
+
if (!platform_set_flag1(k, v))
|
|
1981
|
+
goto fail;
|
|
1982
|
+
}
|
|
1983
|
+
if (NIL_P(kwargs))
|
|
1984
|
+
return Qnil;
|
|
1985
|
+
Check_Type(kwargs, T_HASH);
|
|
1986
|
+
args = rb_ary_new();
|
|
1987
|
+
rb_hash_foreach(kwargs, collect, args);
|
|
1988
|
+
for (i = 0, n = RARRAY_LENINT(args); i < n; i += 2) {
|
|
1989
|
+
k = rb_ary_entry(args, i+0);
|
|
1990
|
+
v = rb_ary_entry(args, i+1);
|
|
1991
|
+
if (!platform_set_flag1(k, v))
|
|
1992
|
+
goto fail;
|
|
1993
|
+
}
|
|
1994
|
+
return Qnil;
|
|
1995
|
+
fail:
|
|
1996
|
+
rb_raise(platform_init_error, "platform already initialized");
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// called by v8_global_init; caller must free |*p| with free()
|
|
2000
|
+
void v8_get_flags(char **p, size_t *n)
|
|
2001
|
+
{
|
|
2002
|
+
*p = NULL;
|
|
2003
|
+
*n = 0;
|
|
2004
|
+
pthread_mutex_lock(&flags_mtx);
|
|
2005
|
+
if (!flags.len)
|
|
2006
|
+
goto out;
|
|
2007
|
+
*p = malloc(flags.len);
|
|
2008
|
+
if (!*p)
|
|
2009
|
+
goto out;
|
|
2010
|
+
*n = flags.len;
|
|
2011
|
+
memcpy(*p, flags.buf, *n);
|
|
2012
|
+
buf_reset(&flags);
|
|
2013
|
+
out:
|
|
2014
|
+
buf_init(&flags);
|
|
2015
|
+
buf_putc(&flags, 1); // marker to indicate it's been cleared
|
|
2016
|
+
pthread_mutex_unlock(&flags_mtx);
|
|
2017
|
+
if (single_threaded)
|
|
2018
|
+
rb_thread_lock_native_thread();
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// Blocks until the V8 thread finishes booting the isolate, which includes
|
|
2022
|
+
// snapshot deserialization (v8::Isolate::New) and safe-context setup. None of
|
|
2023
|
+
// that touches Ruby objects -- the snapshot is a plain C buffer already copied
|
|
2024
|
+
// into |c->snapshot| -- so we release the GVL while waiting, letting other Ruby
|
|
2025
|
+
// threads (e.g. a background pool refilling more contexts) truly run meanwhile.
|
|
2026
|
+
static void *context_boot_wait(void *arg)
|
|
2027
|
+
{
|
|
2028
|
+
Context *c;
|
|
2029
|
+
|
|
2030
|
+
c = arg;
|
|
2031
|
+
barrier_wait(&c->early_init);
|
|
2032
|
+
barrier_wait(&c->late_init);
|
|
2033
|
+
return NULL;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
static VALUE context_initialize(int argc, VALUE *argv, VALUE self)
|
|
2037
|
+
{
|
|
2038
|
+
VALUE kwargs, a, k, v;
|
|
2039
|
+
pthread_attr_t attr;
|
|
2040
|
+
const char *cause;
|
|
2041
|
+
pthread_t thr;
|
|
2042
|
+
Snapshot *ss;
|
|
2043
|
+
Context *c;
|
|
2044
|
+
char *s;
|
|
2045
|
+
int r;
|
|
2046
|
+
|
|
2047
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
2048
|
+
rb_scan_args(argc, argv, ":", &kwargs);
|
|
2049
|
+
if (NIL_P(kwargs))
|
|
2050
|
+
goto init;
|
|
2051
|
+
a = rb_ary_new();
|
|
2052
|
+
rb_hash_foreach(kwargs, collect, a);
|
|
2053
|
+
while (RARRAY_LENINT(a)) {
|
|
2054
|
+
v = rb_ary_pop(a);
|
|
2055
|
+
k = rb_ary_pop(a);
|
|
2056
|
+
k = rb_sym2str(k);
|
|
2057
|
+
s = RSTRING_PTR(k);
|
|
2058
|
+
if (!strcmp(s, "ensure_gc_after_idle")) {
|
|
2059
|
+
Check_Type(v, T_FIXNUM);
|
|
2060
|
+
c->idle_gc = FIX2LONG(v);
|
|
2061
|
+
if (c->idle_gc < 0 || c->idle_gc > INT32_MAX)
|
|
2062
|
+
rb_raise(rb_eArgError, "bad ensure_gc_after_idle");
|
|
2063
|
+
} else if (!strcmp(s, "max_memory")) {
|
|
2064
|
+
Check_Type(v, T_FIXNUM);
|
|
2065
|
+
c->max_memory = FIX2LONG(v);
|
|
2066
|
+
if (c->max_memory < 0 || c->max_memory >= UINT32_MAX)
|
|
2067
|
+
rb_raise(rb_eArgError, "bad max_memory");
|
|
2068
|
+
} else if (!strcmp(s, "marshal_stack_depth")) { // backcompat, ignored
|
|
2069
|
+
Check_Type(v, T_FIXNUM);
|
|
2070
|
+
} else if (!strcmp(s, "timeout")) {
|
|
2071
|
+
Check_Type(v, T_FIXNUM);
|
|
2072
|
+
c->timeout = FIX2LONG(v);
|
|
2073
|
+
if (c->timeout < 0 || c->timeout > INT32_MAX)
|
|
2074
|
+
rb_raise(rb_eArgError, "bad timeout");
|
|
2075
|
+
} else if (!strcmp(s, "snapshot")) {
|
|
2076
|
+
if (NIL_P(v))
|
|
2077
|
+
continue;
|
|
2078
|
+
TypedData_Get_Struct(v, Snapshot, &snapshot_type, ss);
|
|
2079
|
+
if (buf_put(&c->snapshot, RSTRING_PTR(ss->blob), RSTRING_LENINT(ss->blob)))
|
|
2080
|
+
rb_raise(runtime_error, "out of memory");
|
|
2081
|
+
} else if (!strcmp(s, "verbose_exceptions")) {
|
|
2082
|
+
c->verbose_exceptions = !(v == Qfalse || v == Qnil);
|
|
2083
|
+
} else if (!strcmp(s, "host_namespace")) {
|
|
2084
|
+
const char *ns = NULL;
|
|
2085
|
+
if (v == Qtrue) {
|
|
2086
|
+
ns = "MiniRacer"; // default brand, like Deno's `Deno`
|
|
2087
|
+
} else if (v != Qnil && v != Qfalse) {
|
|
2088
|
+
Check_Type(v, T_STRING);
|
|
2089
|
+
ns = StringValueCStr(v); // raises on embedded NUL
|
|
2090
|
+
}
|
|
2091
|
+
if (ns && *ns) {
|
|
2092
|
+
// The name becomes a global, so require a valid (ASCII) JS
|
|
2093
|
+
// identifier; otherwise it would only be reachable through
|
|
2094
|
+
// globalThis["..."] rather than as `<name>.method()`.
|
|
2095
|
+
for (const char *q = ns; *q; q++) {
|
|
2096
|
+
int ch = (unsigned char)*q;
|
|
2097
|
+
int ident_start = ch == '_' || ch == '$' ||
|
|
2098
|
+
(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
|
|
2099
|
+
int ident_char = ident_start || (ch >= '0' && ch <= '9');
|
|
2100
|
+
if (!(q == ns ? ident_start : ident_char))
|
|
2101
|
+
rb_raise(rb_eArgError,
|
|
2102
|
+
"host_namespace must be a valid identifier: %s", ns);
|
|
2103
|
+
}
|
|
2104
|
+
// store the name plus its NUL terminator
|
|
2105
|
+
buf_reset(&c->host_namespace);
|
|
2106
|
+
if (buf_put(&c->host_namespace, ns, strlen(ns) + 1))
|
|
2107
|
+
rb_raise(runtime_error, "out of memory");
|
|
2108
|
+
}
|
|
2109
|
+
} else {
|
|
2110
|
+
rb_raise(runtime_error, "bad keyword: %s", s);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
init:
|
|
2114
|
+
if (single_threaded) {
|
|
2115
|
+
v8_once_init();
|
|
2116
|
+
c->pst = v8_thread_init(c, c->snapshot.buf, c->snapshot.len, c->max_memory, c->verbose_exceptions,
|
|
2117
|
+
c->host_namespace.len ? (const char *)c->host_namespace.buf : NULL);
|
|
2118
|
+
} else {
|
|
2119
|
+
cause = "pthread_attr_init";
|
|
2120
|
+
if ((r = pthread_attr_init(&attr)))
|
|
2121
|
+
goto fail;
|
|
2122
|
+
pthread_attr_setstacksize(&attr, 2<<20); // 2 MiB
|
|
2123
|
+
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
2124
|
+
// v8 thread takes ownership of |c|
|
|
2125
|
+
cause = "pthread_create";
|
|
2126
|
+
r = pthread_create(&thr, &attr, v8_thread_start, c);
|
|
2127
|
+
pthread_attr_destroy(&attr);
|
|
2128
|
+
if (r)
|
|
2129
|
+
goto fail;
|
|
2130
|
+
rb_thread_call_without_gvl(context_boot_wait, c, NULL, NULL);
|
|
2131
|
+
}
|
|
2132
|
+
// Deferred to first Context.new so Platform.set_flags! still has effect
|
|
2133
|
+
// on the tag (which depends on V8 flags applied during v8_global_init).
|
|
2134
|
+
{
|
|
2135
|
+
static int version_tag_defined;
|
|
2136
|
+
if (!version_tag_defined) {
|
|
2137
|
+
VALUE m = rb_const_get(rb_cObject, rb_intern("MiniRacer"));
|
|
2138
|
+
rb_define_const(m, "V8_CACHED_DATA_VERSION_TAG",
|
|
2139
|
+
UINT2NUM(v8_cached_data_version_tag()));
|
|
2140
|
+
version_tag_defined = 1;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
return Qnil;
|
|
2144
|
+
fail:
|
|
2145
|
+
rb_raise(runtime_error, "Context.initialize: %s: %s", cause, strerror(r));
|
|
2146
|
+
return Qnil; // pacify compiler
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
static VALUE snapshot_alloc(VALUE klass)
|
|
2150
|
+
{
|
|
2151
|
+
Snapshot *ss;
|
|
2152
|
+
|
|
2153
|
+
ss = ruby_xmalloc(sizeof(*ss));
|
|
2154
|
+
ss->blob = rb_enc_str_new("", 0, rb_ascii8bit_encoding());
|
|
2155
|
+
return TypedData_Wrap_Struct(klass, &snapshot_type, ss);
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
static void snapshot_free(void *arg)
|
|
2159
|
+
{
|
|
2160
|
+
ruby_xfree(arg);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
static void snapshot_mark(void *arg)
|
|
2164
|
+
{
|
|
2165
|
+
Snapshot *ss;
|
|
2166
|
+
|
|
2167
|
+
ss = arg;
|
|
2168
|
+
rb_gc_mark(ss->blob);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
static size_t snapshot_size(const void *arg)
|
|
2172
|
+
{
|
|
2173
|
+
const Snapshot *ss;
|
|
2174
|
+
|
|
2175
|
+
ss = arg;
|
|
2176
|
+
return sizeof(*ss) + RSTRING_LENINT(ss->blob);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
|
|
2180
|
+
{
|
|
2181
|
+
VALUE a, e, code, cv;
|
|
2182
|
+
Snapshot *ss;
|
|
2183
|
+
Context *c;
|
|
2184
|
+
DesCtx d;
|
|
2185
|
+
Ser s;
|
|
2186
|
+
|
|
2187
|
+
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2188
|
+
rb_scan_args(argc, argv, "01", &code);
|
|
2189
|
+
if (NIL_P(code))
|
|
2190
|
+
code = rb_str_new_cstr("");
|
|
2191
|
+
Check_Type(code, T_STRING);
|
|
2192
|
+
cv = context_alloc(context_class);
|
|
2193
|
+
context_initialize(0, NULL, cv);
|
|
2194
|
+
TypedData_Get_Struct(cv, Context, &context_type, c);
|
|
2195
|
+
// request is snapsho(T), "code"
|
|
2196
|
+
ser_init1(&s, 'T');
|
|
2197
|
+
add_string(&s, code);
|
|
2198
|
+
// response is [arraybuffer, error]
|
|
2199
|
+
DesCtx_init(&d);
|
|
2200
|
+
d.transcode_latin1 = 0; // don't mangle snapshot binary data
|
|
2201
|
+
a = rendezvous1(c, &s.b, &d);
|
|
2202
|
+
e = rb_ary_pop(a);
|
|
2203
|
+
context_dispose(cv);
|
|
2204
|
+
if (*RSTRING_PTR(e))
|
|
2205
|
+
rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
|
|
2206
|
+
ss->blob = rb_ary_pop(a);
|
|
2207
|
+
return Qnil;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
static VALUE snapshot_warmup(VALUE self, VALUE arg)
|
|
2211
|
+
{
|
|
2212
|
+
VALUE a, e, cv;
|
|
2213
|
+
Snapshot *ss;
|
|
2214
|
+
Context *c;
|
|
2215
|
+
DesCtx d;
|
|
2216
|
+
Ser s;
|
|
2217
|
+
|
|
2218
|
+
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2219
|
+
Check_Type(arg, T_STRING);
|
|
2220
|
+
cv = context_alloc(context_class);
|
|
2221
|
+
context_initialize(0, NULL, cv);
|
|
2222
|
+
TypedData_Get_Struct(cv, Context, &context_type, c);
|
|
2223
|
+
// request is (W)armup, [snapshot, "warmup code"]
|
|
2224
|
+
ser_init1(&s, 'W');
|
|
2225
|
+
ser_array_begin(&s, 2);
|
|
2226
|
+
ser_string8(&s, (const uint8_t *)RSTRING_PTR(ss->blob), RSTRING_LENINT(ss->blob));
|
|
2227
|
+
add_string(&s, arg);
|
|
2228
|
+
ser_array_end(&s, 2);
|
|
2229
|
+
// response is [arraybuffer, error]
|
|
2230
|
+
DesCtx_init(&d);
|
|
2231
|
+
d.transcode_latin1 = 0; // don't mangle snapshot binary data
|
|
2232
|
+
a = rendezvous1(c, &s.b, &d);
|
|
2233
|
+
e = rb_ary_pop(a);
|
|
2234
|
+
context_dispose(cv);
|
|
2235
|
+
if (*RSTRING_PTR(e))
|
|
2236
|
+
rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
|
|
2237
|
+
ss->blob = rb_ary_pop(a);
|
|
2238
|
+
return self;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
static VALUE snapshot_dump(VALUE self)
|
|
2242
|
+
{
|
|
2243
|
+
Snapshot *ss;
|
|
2244
|
+
|
|
2245
|
+
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2246
|
+
return ss->blob;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
static VALUE snapshot_load(VALUE klass, VALUE blob)
|
|
2250
|
+
{
|
|
2251
|
+
Snapshot *ss;
|
|
2252
|
+
VALUE self;
|
|
2253
|
+
|
|
2254
|
+
Check_Type(blob, T_STRING);
|
|
2255
|
+
self = snapshot_alloc(klass);
|
|
2256
|
+
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2257
|
+
ss->blob = rb_str_dup(blob);
|
|
2258
|
+
rb_enc_associate(ss->blob, rb_ascii8bit_encoding());
|
|
2259
|
+
ENC_CODERANGE_SET(ss->blob, ENC_CODERANGE_VALID);
|
|
2260
|
+
return self;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
static VALUE snapshot_size0(VALUE self)
|
|
2264
|
+
{
|
|
2265
|
+
Snapshot *ss;
|
|
2266
|
+
|
|
2267
|
+
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2268
|
+
return LONG2FIX(RSTRING_LENINT(ss->blob));
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
static VALUE script_error_cause(VALUE self)
|
|
2272
|
+
{
|
|
2273
|
+
return rb_iv_get(self, "@cause");
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
static VALUE context_compile(int argc, VALUE *argv, VALUE self)
|
|
2277
|
+
{
|
|
2278
|
+
VALUE a, e, source, filename, cached_data, produce_cache, kwargs;
|
|
2279
|
+
VALUE script_v, result;
|
|
2280
|
+
Script *script;
|
|
2281
|
+
Context *c;
|
|
2282
|
+
Ser s;
|
|
2283
|
+
|
|
2284
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
2285
|
+
rb_scan_args(argc, argv, "1:", &source, &kwargs);
|
|
2286
|
+
Check_Type(source, T_STRING);
|
|
2287
|
+
filename = Qnil;
|
|
2288
|
+
cached_data = Qnil;
|
|
2289
|
+
produce_cache = Qfalse;
|
|
2290
|
+
if (!NIL_P(kwargs)) {
|
|
2291
|
+
filename = rb_hash_aref(kwargs, ID2SYM(id_filename));
|
|
2292
|
+
cached_data = rb_hash_aref(kwargs, ID2SYM(id_cached_data));
|
|
2293
|
+
produce_cache = rb_hash_aref(kwargs, ID2SYM(id_produce_cache));
|
|
2294
|
+
}
|
|
2295
|
+
if (NIL_P(filename))
|
|
2296
|
+
filename = rb_str_new_cstr("<compile>");
|
|
2297
|
+
Check_Type(filename, T_STRING);
|
|
2298
|
+
if (!NIL_P(cached_data)) {
|
|
2299
|
+
Check_Type(cached_data, T_STRING);
|
|
2300
|
+
// Refuse non-binary encodings so a user reading a cache file without
|
|
2301
|
+
// 'rb' mode gets a clear error instead of mangled bytes flowing to V8.
|
|
2302
|
+
if (rb_enc_get(cached_data) != rb_ascii8bit_encoding())
|
|
2303
|
+
rb_raise(rb_eEncodingError,
|
|
2304
|
+
"cached_data must be ASCII-8BIT (binary), got %s",
|
|
2305
|
+
rb_enc_name(rb_enc_get(cached_data)));
|
|
2306
|
+
}
|
|
2307
|
+
ser_init1(&s, 'K');
|
|
2308
|
+
ser_array_begin(&s, 4);
|
|
2309
|
+
add_string(&s, filename);
|
|
2310
|
+
add_string(&s, source);
|
|
2311
|
+
if (NIL_P(cached_data)) {
|
|
2312
|
+
ser_null(&s);
|
|
2313
|
+
} else {
|
|
2314
|
+
ser_uint8array(&s, (const uint8_t *)RSTRING_PTR(cached_data),
|
|
2315
|
+
RSTRING_LENINT(cached_data));
|
|
2316
|
+
}
|
|
2317
|
+
ser_bool(&s, RTEST(produce_cache));
|
|
2318
|
+
ser_array_end(&s, 4);
|
|
2319
|
+
a = rendezvous(c, &s.b);
|
|
2320
|
+
e = rb_ary_pop(a);
|
|
2321
|
+
handle_exception(e);
|
|
2322
|
+
result = rb_ary_pop(a);
|
|
2323
|
+
Check_Type(result, T_ARRAY);
|
|
2324
|
+
|
|
2325
|
+
script_v = rb_obj_alloc(script_class); // skip the raising initialize
|
|
2326
|
+
TypedData_Get_Struct(script_v, Script, &script_type, script);
|
|
2327
|
+
script->context = self;
|
|
2328
|
+
script->handle_id = NUM2INT(rb_ary_entry(result, 0));
|
|
2329
|
+
script->cached_data = rb_ary_entry(result, 1);
|
|
2330
|
+
script->cache_rejected = RTEST(rb_ary_entry(result, 2));
|
|
2331
|
+
return script_v;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
static VALUE script_alloc(VALUE klass)
|
|
2335
|
+
{
|
|
2336
|
+
Script *s;
|
|
2337
|
+
|
|
2338
|
+
s = ruby_xmalloc(sizeof(*s));
|
|
2339
|
+
memset(s, 0, sizeof(*s));
|
|
2340
|
+
s->context = Qnil;
|
|
2341
|
+
s->cached_data = Qnil;
|
|
2342
|
+
return TypedData_Wrap_Struct(klass, &script_type, s);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
static void script_free(void *arg)
|
|
2346
|
+
{
|
|
2347
|
+
// Intentionally does not send a dispose RPC — finalizers can't safely
|
|
2348
|
+
// take rr_mtx. State::~State() walks st.scripts at isolate teardown so
|
|
2349
|
+
// we leak nothing across a Context's lifetime; use Script#dispose to
|
|
2350
|
+
// free eagerly mid-lifetime.
|
|
2351
|
+
ruby_xfree(arg);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
static void script_mark(void *arg)
|
|
2355
|
+
{
|
|
2356
|
+
Script *s = arg;
|
|
2357
|
+
rb_gc_mark(s->context);
|
|
2358
|
+
rb_gc_mark(s->cached_data);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
static size_t script_size(const void *arg)
|
|
2362
|
+
{
|
|
2363
|
+
const Script *s = arg;
|
|
2364
|
+
size_t base = sizeof(*s);
|
|
2365
|
+
if (!NIL_P(s->cached_data))
|
|
2366
|
+
base += RSTRING_LENINT(s->cached_data);
|
|
2367
|
+
return base;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
static VALUE script_initialize(int argc, VALUE *argv, VALUE self)
|
|
2371
|
+
{
|
|
2372
|
+
(void)argc; (void)argv; (void)self;
|
|
2373
|
+
rb_raise(runtime_error, "MiniRacer::Script must be created via Context#compile");
|
|
2374
|
+
return Qnil;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
static VALUE script_run(VALUE self)
|
|
2378
|
+
{
|
|
2379
|
+
VALUE a, e;
|
|
2380
|
+
Script *script;
|
|
2381
|
+
Context *c;
|
|
2382
|
+
Ser s;
|
|
2383
|
+
|
|
2384
|
+
TypedData_Get_Struct(self, Script, &script_type, script);
|
|
2385
|
+
if (script->disposed)
|
|
2386
|
+
rb_raise(runtime_error, "disposed script");
|
|
2387
|
+
TypedData_Get_Struct(script->context, Context, &context_type, c);
|
|
2388
|
+
if (atomic_load(&c->quit))
|
|
2389
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2390
|
+
ser_init1(&s, 'R');
|
|
2391
|
+
ser_int(&s, script->handle_id);
|
|
2392
|
+
a = rendezvous(c, &s.b);
|
|
2393
|
+
e = rb_ary_pop(a);
|
|
2394
|
+
handle_exception(e);
|
|
2395
|
+
return rb_ary_pop(a);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
static VALUE script_cached_data(VALUE self)
|
|
2399
|
+
{
|
|
2400
|
+
Script *script;
|
|
2401
|
+
TypedData_Get_Struct(self, Script, &script_type, script);
|
|
2402
|
+
return script->cached_data;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
static VALUE script_cache_rejected_p(VALUE self)
|
|
2406
|
+
{
|
|
2407
|
+
Script *script;
|
|
2408
|
+
TypedData_Get_Struct(self, Script, &script_type, script);
|
|
2409
|
+
return script->cache_rejected ? Qtrue : Qfalse;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
static VALUE script_dispose(VALUE self)
|
|
2413
|
+
{
|
|
2414
|
+
VALUE e;
|
|
2415
|
+
Script *script;
|
|
2416
|
+
Context *c;
|
|
2417
|
+
Ser s;
|
|
2418
|
+
|
|
2419
|
+
TypedData_Get_Struct(self, Script, &script_type, script);
|
|
2420
|
+
if (script->disposed) return Qnil;
|
|
2421
|
+
TypedData_Get_Struct(script->context, Context, &context_type, c);
|
|
2422
|
+
script->disposed = 1;
|
|
2423
|
+
// Context already gone? The handle was cleaned by State::~State().
|
|
2424
|
+
if (atomic_load(&c->quit))
|
|
2425
|
+
return Qnil;
|
|
2426
|
+
ser_init1(&s, 'D');
|
|
2427
|
+
ser_int(&s, script->handle_id);
|
|
2428
|
+
e = rendezvous(c, &s.b);
|
|
2429
|
+
handle_exception(e);
|
|
2430
|
+
return Qnil;
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
static VALUE script_disposed_p(VALUE self)
|
|
2434
|
+
{
|
|
2435
|
+
Script *script;
|
|
2436
|
+
TypedData_Get_Struct(self, Script, &script_type, script);
|
|
2437
|
+
return script->disposed ? Qtrue : Qfalse;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
static VALUE context_compile_module(int argc, VALUE *argv, VALUE self)
|
|
2441
|
+
{
|
|
2442
|
+
VALUE a, e, source, filename, cached_data, produce_cache, kwargs;
|
|
2443
|
+
VALUE module_v, result;
|
|
2444
|
+
Module *m;
|
|
2445
|
+
Context *c;
|
|
2446
|
+
Ser s;
|
|
2447
|
+
|
|
2448
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
2449
|
+
if (atomic_load(&c->quit))
|
|
2450
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2451
|
+
rb_scan_args(argc, argv, "1:", &source, &kwargs);
|
|
2452
|
+
Check_Type(source, T_STRING);
|
|
2453
|
+
filename = Qnil;
|
|
2454
|
+
cached_data = Qnil;
|
|
2455
|
+
produce_cache = Qfalse;
|
|
2456
|
+
if (!NIL_P(kwargs)) {
|
|
2457
|
+
filename = rb_hash_aref(kwargs, ID2SYM(id_filename));
|
|
2458
|
+
cached_data = rb_hash_aref(kwargs, ID2SYM(id_cached_data));
|
|
2459
|
+
produce_cache = rb_hash_aref(kwargs, ID2SYM(id_produce_cache));
|
|
2460
|
+
}
|
|
2461
|
+
if (NIL_P(filename))
|
|
2462
|
+
filename = rb_str_new_cstr("<compile_module>");
|
|
2463
|
+
Check_Type(filename, T_STRING);
|
|
2464
|
+
if (!NIL_P(cached_data)) {
|
|
2465
|
+
Check_Type(cached_data, T_STRING);
|
|
2466
|
+
// Refuse non-binary encodings so a user reading a cache file without
|
|
2467
|
+
// 'rb' mode gets a clear error instead of mangled bytes flowing to V8.
|
|
2468
|
+
if (rb_enc_get(cached_data) != rb_ascii8bit_encoding())
|
|
2469
|
+
rb_raise(rb_eEncodingError,
|
|
2470
|
+
"cached_data must be ASCII-8BIT (binary), got %s",
|
|
2471
|
+
rb_enc_name(rb_enc_get(cached_data)));
|
|
2472
|
+
}
|
|
2473
|
+
ser_init1(&s, 'O');
|
|
2474
|
+
ser_array_begin(&s, 4);
|
|
2475
|
+
add_string(&s, filename);
|
|
2476
|
+
add_string(&s, source);
|
|
2477
|
+
if (NIL_P(cached_data)) {
|
|
2478
|
+
ser_null(&s);
|
|
2479
|
+
} else {
|
|
2480
|
+
ser_uint8array(&s, (const uint8_t *)RSTRING_PTR(cached_data),
|
|
2481
|
+
RSTRING_LENINT(cached_data));
|
|
2482
|
+
}
|
|
2483
|
+
ser_bool(&s, RTEST(produce_cache));
|
|
2484
|
+
ser_array_end(&s, 4);
|
|
2485
|
+
a = rendezvous(c, &s.b);
|
|
2486
|
+
e = rb_ary_pop(a);
|
|
2487
|
+
handle_exception(e);
|
|
2488
|
+
result = rb_ary_pop(a);
|
|
2489
|
+
Check_Type(result, T_ARRAY);
|
|
2490
|
+
|
|
2491
|
+
module_v = rb_obj_alloc(module_class); // skip the raising initialize
|
|
2492
|
+
TypedData_Get_Struct(module_v, Module, &module_type, m);
|
|
2493
|
+
m->context = self;
|
|
2494
|
+
m->handle_id = NUM2INT(rb_ary_entry(result, 0));
|
|
2495
|
+
m->cached_data = rb_ary_entry(result, 1);
|
|
2496
|
+
m->cache_rejected = RTEST(rb_ary_entry(result, 2));
|
|
2497
|
+
return module_v;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
static VALUE module_cached_data(VALUE self)
|
|
2501
|
+
{
|
|
2502
|
+
Module *m;
|
|
2503
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2504
|
+
return m->cached_data;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
static VALUE module_cache_rejected_p(VALUE self)
|
|
2508
|
+
{
|
|
2509
|
+
Module *m;
|
|
2510
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2511
|
+
return m->cache_rejected ? Qtrue : Qfalse;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
static VALUE module_alloc(VALUE klass)
|
|
2515
|
+
{
|
|
2516
|
+
Module *m;
|
|
2517
|
+
|
|
2518
|
+
m = ruby_xmalloc(sizeof(*m));
|
|
2519
|
+
memset(m, 0, sizeof(*m));
|
|
2520
|
+
m->context = Qnil;
|
|
2521
|
+
m->cached_data = Qnil;
|
|
2522
|
+
return TypedData_Wrap_Struct(klass, &module_type, m);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
static void module_free(void *arg)
|
|
2526
|
+
{
|
|
2527
|
+
// Intentionally does not send a dispose RPC — finalizers can't safely
|
|
2528
|
+
// take rr_mtx. State::~State() walks st.modules at isolate teardown so
|
|
2529
|
+
// we leak nothing across a Context's lifetime; use Module#dispose to
|
|
2530
|
+
// free eagerly mid-lifetime.
|
|
2531
|
+
ruby_xfree(arg);
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
static void module_mark(void *arg)
|
|
2535
|
+
{
|
|
2536
|
+
Module *m = arg;
|
|
2537
|
+
rb_gc_mark(m->context);
|
|
2538
|
+
rb_gc_mark(m->cached_data);
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
static size_t module_size(const void *arg)
|
|
2542
|
+
{
|
|
2543
|
+
(void)arg;
|
|
2544
|
+
return sizeof(Module);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
static VALUE module_initialize(int argc, VALUE *argv, VALUE self)
|
|
2548
|
+
{
|
|
2549
|
+
(void)argc; (void)argv; (void)self;
|
|
2550
|
+
rb_raise(runtime_error, "MiniRacer::Module must be created via Context#compile_module");
|
|
2551
|
+
return Qnil;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
struct instantiate_args {
|
|
2555
|
+
Context *c;
|
|
2556
|
+
int32_t handle_id;
|
|
2557
|
+
VALUE prev_block;
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2560
|
+
static VALUE module_instantiate_body(VALUE arg)
|
|
2561
|
+
{
|
|
2562
|
+
struct instantiate_args *ia = (struct instantiate_args *)arg;
|
|
2563
|
+
VALUE a, e;
|
|
2564
|
+
Ser s;
|
|
2565
|
+
|
|
2566
|
+
ser_init1(&s, 'I');
|
|
2567
|
+
ser_int(&s, ia->handle_id);
|
|
2568
|
+
a = rendezvous(ia->c, &s.b);
|
|
2569
|
+
e = rb_ary_pop(a);
|
|
2570
|
+
handle_exception(e);
|
|
2571
|
+
return Qnil;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
static VALUE module_instantiate_restore(VALUE arg)
|
|
2575
|
+
{
|
|
2576
|
+
struct instantiate_args *ia = (struct instantiate_args *)arg;
|
|
2577
|
+
ia->c->resolve_block = ia->prev_block;
|
|
2578
|
+
return Qnil;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
static VALUE module_instantiate(VALUE self)
|
|
2582
|
+
{
|
|
2583
|
+
VALUE block;
|
|
2584
|
+
Module *m;
|
|
2585
|
+
Context *c;
|
|
2586
|
+
struct instantiate_args ia;
|
|
2587
|
+
|
|
2588
|
+
if (!rb_block_given_p())
|
|
2589
|
+
rb_raise(rb_eArgError, "Module#instantiate requires a resolver block");
|
|
2590
|
+
block = rb_block_proc();
|
|
2591
|
+
|
|
2592
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2593
|
+
if (m->disposed)
|
|
2594
|
+
rb_raise(runtime_error, "disposed module");
|
|
2595
|
+
TypedData_Get_Struct(m->context, Context, &context_type, c);
|
|
2596
|
+
if (atomic_load(&c->quit))
|
|
2597
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2598
|
+
|
|
2599
|
+
// Save the previous resolver slot so a re-entrant instantiate from
|
|
2600
|
+
// inside this block restores its caller's block on the way out.
|
|
2601
|
+
// rb_ensure guarantees restoration even when the resolver block
|
|
2602
|
+
// raises (without it, the slot would be left pointing at this call's
|
|
2603
|
+
// block and keep it GC-alive until something else overwrites it).
|
|
2604
|
+
ia.c = c;
|
|
2605
|
+
ia.handle_id = m->handle_id;
|
|
2606
|
+
ia.prev_block = c->resolve_block;
|
|
2607
|
+
c->resolve_block = block;
|
|
2608
|
+
rb_ensure(module_instantiate_body, (VALUE)&ia,
|
|
2609
|
+
module_instantiate_restore, (VALUE)&ia);
|
|
2610
|
+
return self;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
struct load_graph_args {
|
|
2614
|
+
Context *c;
|
|
2615
|
+
VALUE entry_url;
|
|
2616
|
+
};
|
|
2617
|
+
|
|
2618
|
+
static VALUE context_load_module_graph_body(VALUE arg)
|
|
2619
|
+
{
|
|
2620
|
+
struct load_graph_args *la = (struct load_graph_args *)arg;
|
|
2621
|
+
VALUE a, e, result, value, mods, out;
|
|
2622
|
+
long i, len;
|
|
2623
|
+
Ser s;
|
|
2624
|
+
|
|
2625
|
+
// request is (G)raph, [entry_url]
|
|
2626
|
+
ser_init1(&s, 'G');
|
|
2627
|
+
ser_array_begin(&s, 1);
|
|
2628
|
+
add_string(&s, la->entry_url);
|
|
2629
|
+
ser_array_end(&s, 1);
|
|
2630
|
+
a = rendezvous(la->c, &s.b); // takes ownership of |s.b|
|
|
2631
|
+
e = rb_ary_pop(a);
|
|
2632
|
+
handle_exception(e);
|
|
2633
|
+
result = rb_ary_pop(a); // [value, [[url, cache_rejected], ...]]
|
|
2634
|
+
Check_Type(result, T_ARRAY);
|
|
2635
|
+
value = rb_ary_entry(result, 0);
|
|
2636
|
+
mods = rb_ary_entry(result, 1);
|
|
2637
|
+
Check_Type(mods, T_ARRAY);
|
|
2638
|
+
|
|
2639
|
+
len = RARRAY_LEN(mods);
|
|
2640
|
+
out = rb_ary_new_capa(len);
|
|
2641
|
+
for (i = 0; i < len; i++) {
|
|
2642
|
+
VALUE row = rb_ary_entry(mods, i), h = rb_hash_new();
|
|
2643
|
+
rb_hash_aset(h, ID2SYM(rb_intern("url")), rb_ary_entry(row, 0));
|
|
2644
|
+
rb_hash_aset(h, ID2SYM(rb_intern("cache_rejected")), rb_ary_entry(row, 1));
|
|
2645
|
+
rb_ary_push(out, h);
|
|
2646
|
+
}
|
|
2647
|
+
{
|
|
2648
|
+
VALUE h = rb_hash_new();
|
|
2649
|
+
rb_hash_aset(h, ID2SYM(rb_intern("value")), value);
|
|
2650
|
+
rb_hash_aset(h, ID2SYM(rb_intern("modules")), out);
|
|
2651
|
+
return h;
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
// EXPERIMENTAL (spike): compile+instantiate+evaluate an entire ES module graph
|
|
2656
|
+
// reachable from |entry_url| in one call, driving the walk on the V8 thread.
|
|
2657
|
+
// resolve:/fetch_batch: are invoked in batches (once per graph level) instead of
|
|
2658
|
+
// once per module/import, collapsing the ~2*N Ruby<->V8 round-trips of the
|
|
2659
|
+
// compile_module + instantiate path to ~2 per level.
|
|
2660
|
+
// resolve: ->(edges) { edges.map { |specifier, referrer| url_or_nil } }
|
|
2661
|
+
// fetch_batch: ->(urls) { urls.map { |u| [source, cached_data] | nil } }
|
|
2662
|
+
// Returns { value:, modules: [{ url:, cache_rejected: }, ...] } for the modules
|
|
2663
|
+
// newly compiled by this call (already-registered URLs are reused, not relisted).
|
|
2664
|
+
//
|
|
2665
|
+
// The resolve/fetch_batch callbacks are persisted on the Context (overwritten by
|
|
2666
|
+
// the next load_module_graph, freed on dispose): a later dynamic import() reuses
|
|
2667
|
+
// them and the URL registry, so a URL the graph already loaded resolves to the
|
|
2668
|
+
// same Module instance instead of being recompiled.
|
|
2669
|
+
static VALUE context_load_module_graph(int argc, VALUE *argv, VALUE self)
|
|
2670
|
+
{
|
|
2671
|
+
VALUE entry_url, kwargs, resolve, fetch_batch;
|
|
2672
|
+
Context *c;
|
|
2673
|
+
struct load_graph_args la;
|
|
2674
|
+
|
|
2675
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
2676
|
+
if (atomic_load(&c->quit))
|
|
2677
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2678
|
+
rb_scan_args(argc, argv, "1:", &entry_url, &kwargs);
|
|
2679
|
+
Check_Type(entry_url, T_STRING);
|
|
2680
|
+
resolve = fetch_batch = Qnil;
|
|
2681
|
+
if (!NIL_P(kwargs)) {
|
|
2682
|
+
resolve = rb_hash_aref(kwargs, ID2SYM(rb_intern("resolve")));
|
|
2683
|
+
fetch_batch = rb_hash_aref(kwargs, ID2SYM(rb_intern("fetch_batch")));
|
|
2684
|
+
}
|
|
2685
|
+
if (!rb_respond_to(resolve, rb_intern("call")))
|
|
2686
|
+
rb_raise(rb_eArgError, "load_module_graph requires a resolve: callable");
|
|
2687
|
+
if (!rb_respond_to(fetch_batch, rb_intern("call")))
|
|
2688
|
+
rb_raise(rb_eArgError, "load_module_graph requires a fetch_batch: callable");
|
|
2689
|
+
|
|
2690
|
+
// Persist the callbacks for the Context's lifetime so dynamic import() can
|
|
2691
|
+
// reuse them after this call returns (case A). A later load_module_graph
|
|
2692
|
+
// overwrites them; dispose frees them.
|
|
2693
|
+
c->graph_resolve_block = resolve;
|
|
2694
|
+
c->graph_fetch_block = fetch_batch;
|
|
2695
|
+
la.c = c;
|
|
2696
|
+
la.entry_url = entry_url;
|
|
2697
|
+
return context_load_module_graph_body((VALUE)&la);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
static VALUE module_evaluate(VALUE self)
|
|
2701
|
+
{
|
|
2702
|
+
VALUE a, e;
|
|
2703
|
+
Module *m;
|
|
2704
|
+
Context *c;
|
|
2705
|
+
Ser s;
|
|
2706
|
+
|
|
2707
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2708
|
+
if (m->disposed)
|
|
2709
|
+
rb_raise(runtime_error, "disposed module");
|
|
2710
|
+
TypedData_Get_Struct(m->context, Context, &context_type, c);
|
|
2711
|
+
if (atomic_load(&c->quit))
|
|
2712
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2713
|
+
ser_init1(&s, 'V');
|
|
2714
|
+
ser_int(&s, m->handle_id);
|
|
2715
|
+
a = rendezvous(c, &s.b);
|
|
2716
|
+
e = rb_ary_pop(a);
|
|
2717
|
+
handle_exception(e);
|
|
2718
|
+
return rb_ary_pop(a);
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
static VALUE module_namespace(VALUE self)
|
|
2722
|
+
{
|
|
2723
|
+
VALUE a, e;
|
|
2724
|
+
Module *m;
|
|
2725
|
+
Context *c;
|
|
2726
|
+
Ser s;
|
|
2727
|
+
|
|
2728
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2729
|
+
if (m->disposed)
|
|
2730
|
+
rb_raise(runtime_error, "disposed module");
|
|
2731
|
+
TypedData_Get_Struct(m->context, Context, &context_type, c);
|
|
2732
|
+
if (atomic_load(&c->quit))
|
|
2733
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2734
|
+
ser_init1(&s, 'N');
|
|
2735
|
+
ser_int(&s, m->handle_id);
|
|
2736
|
+
a = rendezvous(c, &s.b);
|
|
2737
|
+
e = rb_ary_pop(a);
|
|
2738
|
+
handle_exception(e);
|
|
2739
|
+
return rb_ary_pop(a);
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
static VALUE module_status(VALUE self)
|
|
2743
|
+
{
|
|
2744
|
+
VALUE a, e, result;
|
|
2745
|
+
Module *m;
|
|
2746
|
+
Context *c;
|
|
2747
|
+
Ser s;
|
|
2748
|
+
|
|
2749
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2750
|
+
if (m->disposed)
|
|
2751
|
+
rb_raise(runtime_error, "disposed module");
|
|
2752
|
+
TypedData_Get_Struct(m->context, Context, &context_type, c);
|
|
2753
|
+
if (atomic_load(&c->quit))
|
|
2754
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
2755
|
+
ser_init1(&s, 'U');
|
|
2756
|
+
ser_int(&s, m->handle_id);
|
|
2757
|
+
a = rendezvous(c, &s.b);
|
|
2758
|
+
e = rb_ary_pop(a);
|
|
2759
|
+
handle_exception(e);
|
|
2760
|
+
result = rb_ary_pop(a);
|
|
2761
|
+
// v8_module_status always replies with a String on success; a non-string
|
|
2762
|
+
// would mean the v8 thread fell through to the Undefined fail path with
|
|
2763
|
+
// a missing error (shouldn't happen, but check defensively rather than
|
|
2764
|
+
// crash rb_str_intern on Qnil).
|
|
2765
|
+
Check_Type(result, T_STRING);
|
|
2766
|
+
return rb_str_intern(result);
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
static VALUE module_dispose(VALUE self)
|
|
2770
|
+
{
|
|
2771
|
+
VALUE e;
|
|
2772
|
+
Module *m;
|
|
2773
|
+
Context *c;
|
|
2774
|
+
Ser s;
|
|
2775
|
+
|
|
2776
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2777
|
+
if (m->disposed) return Qnil;
|
|
2778
|
+
TypedData_Get_Struct(m->context, Context, &context_type, c);
|
|
2779
|
+
// Context already gone? The handle was cleaned by State::~State().
|
|
2780
|
+
if (atomic_load(&c->quit)) {
|
|
2781
|
+
m->disposed = 1;
|
|
2782
|
+
return Qnil;
|
|
2783
|
+
}
|
|
2784
|
+
ser_init1(&s, 'Z');
|
|
2785
|
+
ser_int(&s, m->handle_id);
|
|
2786
|
+
e = rendezvous(c, &s.b);
|
|
2787
|
+
handle_exception(e);
|
|
2788
|
+
// Mark disposed only after the V8 handle is actually freed so a retry
|
|
2789
|
+
// after a transient rendezvous failure can still release it.
|
|
2790
|
+
m->disposed = 1;
|
|
2791
|
+
return Qnil;
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
static VALUE module_disposed_p(VALUE self)
|
|
2795
|
+
{
|
|
2796
|
+
Module *m;
|
|
2797
|
+
TypedData_Get_Struct(self, Module, &module_type, m);
|
|
2798
|
+
return m->disposed ? Qtrue : Qfalse;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
static VALUE context_set_dynamic_import_resolver(VALUE self, VALUE blk)
|
|
2802
|
+
{
|
|
2803
|
+
Context *c;
|
|
2804
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
2805
|
+
if (!NIL_P(blk) && !rb_respond_to(blk, rb_intern("call")))
|
|
2806
|
+
rb_raise(rb_eTypeError,
|
|
2807
|
+
"dynamic_import_resolver must respond to #call or be nil");
|
|
2808
|
+
c->dynamic_import_resolver = blk;
|
|
2809
|
+
return blk;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
static VALUE context_get_dynamic_import_resolver(VALUE self)
|
|
2813
|
+
{
|
|
2814
|
+
Context *c;
|
|
2815
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
2816
|
+
return c->dynamic_import_resolver;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
__attribute__((visibility("default")))
|
|
2820
|
+
void Init_mini_racer_extension(void)
|
|
2821
|
+
{
|
|
2822
|
+
VALUE c, m;
|
|
2823
|
+
|
|
2824
|
+
id_filename = rb_intern("filename");
|
|
2825
|
+
id_cached_data = rb_intern("cached_data");
|
|
2826
|
+
id_produce_cache = rb_intern("produce_cache");
|
|
2827
|
+
|
|
2828
|
+
m = rb_define_module("MiniRacer");
|
|
2829
|
+
c = rb_define_class_under(m, "Error", rb_eStandardError);
|
|
2830
|
+
snapshot_error = rb_define_class_under(m, "SnapshotError", c);
|
|
2831
|
+
platform_init_error = rb_define_class_under(m, "PlatformAlreadyInitialized", c);
|
|
2832
|
+
context_disposed_error = rb_define_class_under(m, "ContextDisposedError", c);
|
|
2833
|
+
|
|
2834
|
+
c = rb_define_class_under(m, "EvalError", c);
|
|
2835
|
+
parse_error = rb_define_class_under(m, "ParseError", c);
|
|
2836
|
+
memory_error = rb_define_class_under(m, "V8OutOfMemoryError", c);
|
|
2837
|
+
script_error = rb_define_class_under(m, "ScriptError", c);
|
|
2838
|
+
runtime_error = rb_define_class_under(m, "RuntimeError", c);
|
|
2839
|
+
internal_error = rb_define_class_under(m, "InternalError", c);
|
|
2840
|
+
terminated_error = rb_define_class_under(m, "ScriptTerminatedError", c);
|
|
2841
|
+
|
|
2842
|
+
rb_define_method(script_error, "cause", script_error_cause, 0);
|
|
2843
|
+
|
|
2844
|
+
c = context_class = rb_define_class_under(m, "Context", rb_cObject);
|
|
2845
|
+
rb_define_method(c, "initialize", context_initialize, -1);
|
|
2846
|
+
rb_define_method(c, "attach", context_attach, 2);
|
|
2847
|
+
rb_define_method(c, "compile", context_compile, -1);
|
|
2848
|
+
rb_define_method(c, "compile_module", context_compile_module, -1);
|
|
2849
|
+
rb_define_method(c, "load_module_graph", context_load_module_graph, -1);
|
|
2850
|
+
rb_define_method(c, "dispose", context_dispose, 0);
|
|
2851
|
+
rb_define_method(c, "stop", context_stop, 0);
|
|
2852
|
+
rb_define_method(c, "call", context_call, -1);
|
|
2853
|
+
rb_define_method(c, "eval", context_eval, -1);
|
|
2854
|
+
rb_define_method(c, "heap_stats", context_heap_stats, 0);
|
|
2855
|
+
rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
|
|
2856
|
+
rb_define_method(c, "perform_microtask_checkpoint", context_perform_microtask_checkpoint, 0);
|
|
2857
|
+
rb_define_method(c, "reset_realm", context_reset_realm, 0);
|
|
2858
|
+
rb_define_method(c, "pump_message_loop", context_pump_message_loop, 0);
|
|
2859
|
+
rb_define_method(c, "low_memory_notification", context_low_memory_notification, 0);
|
|
2860
|
+
rb_define_method(c, "dynamic_import_resolver", context_get_dynamic_import_resolver, 0);
|
|
2861
|
+
rb_define_method(c, "dynamic_import_resolver=", context_set_dynamic_import_resolver, 1);
|
|
2862
|
+
rb_define_alloc_func(c, context_alloc);
|
|
2863
|
+
|
|
2864
|
+
c = script_class = rb_define_class_under(m, "Script", rb_cObject);
|
|
2865
|
+
rb_define_method(c, "initialize", script_initialize, -1);
|
|
2866
|
+
rb_define_method(c, "run", script_run, 0);
|
|
2867
|
+
rb_define_method(c, "cached_data", script_cached_data, 0);
|
|
2868
|
+
rb_define_method(c, "cache_rejected?", script_cache_rejected_p, 0);
|
|
2869
|
+
rb_define_method(c, "dispose", script_dispose, 0);
|
|
2870
|
+
rb_define_method(c, "disposed?", script_disposed_p, 0);
|
|
2871
|
+
rb_define_alloc_func(c, script_alloc);
|
|
2872
|
+
|
|
2873
|
+
c = module_class = rb_define_class_under(m, "Module", rb_cObject);
|
|
2874
|
+
rb_define_method(c, "initialize", module_initialize, -1);
|
|
2875
|
+
rb_define_method(c, "instantiate", module_instantiate, 0);
|
|
2876
|
+
rb_define_method(c, "evaluate", module_evaluate, 0);
|
|
2877
|
+
rb_define_method(c, "namespace", module_namespace, 0);
|
|
2878
|
+
rb_define_method(c, "status", module_status, 0);
|
|
2879
|
+
rb_define_method(c, "cached_data", module_cached_data, 0);
|
|
2880
|
+
rb_define_method(c, "cache_rejected?", module_cache_rejected_p, 0);
|
|
2881
|
+
rb_define_method(c, "dispose", module_dispose, 0);
|
|
2882
|
+
rb_define_method(c, "disposed?", module_disposed_p, 0);
|
|
2883
|
+
rb_define_alloc_func(c, module_alloc);
|
|
2884
|
+
|
|
2885
|
+
c = snapshot_class = rb_define_class_under(m, "Snapshot", rb_cObject);
|
|
2886
|
+
rb_define_method(c, "initialize", snapshot_initialize, -1);
|
|
2887
|
+
rb_define_method(c, "warmup!", snapshot_warmup, 1);
|
|
2888
|
+
rb_define_method(c, "dump", snapshot_dump, 0);
|
|
2889
|
+
rb_define_method(c, "size", snapshot_size0, 0);
|
|
2890
|
+
rb_define_singleton_method(c, "load", snapshot_load, 1);
|
|
2891
|
+
rb_define_alloc_func(c, snapshot_alloc);
|
|
2892
|
+
|
|
2893
|
+
c = rb_define_class_under(m, "Platform", rb_cObject);
|
|
2894
|
+
rb_define_singleton_method(c, "set_flags!", platform_set_flags, -1);
|
|
2895
|
+
|
|
2896
|
+
date_time_class = Qnil; // lazy init
|
|
2897
|
+
binary_class = Qnil; // lazy init
|
|
2898
|
+
js_function_class = rb_define_class_under(m, "JavaScriptFunction", rb_cObject);
|
|
2899
|
+
}
|