mini_racer 0.17.0.pre5 → 0.17.0.pre6

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.
@@ -0,0 +1,1564 @@
1
+ #include <stdatomic.h>
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include <pthread.h>
6
+
7
+ #include "ruby.h"
8
+ #include "ruby/encoding.h"
9
+ #include "ruby/version.h"
10
+ #include "ruby/thread.h"
11
+ #include "serde.c"
12
+ #include "mini_racer_v8.h"
13
+
14
+ #if RUBY_API_VERSION_CODE < 3*10000+4*100 // 3.4.0
15
+ static inline void rb_thread_lock_native_thread(void)
16
+ {
17
+ // Without rb_thread_lock_native_thread, V8 in single-threaded mode is
18
+ // prone to crash with debug checks like this...
19
+ //
20
+ // # Fatal error in ../deps/v8/src/base/platform/platform-posix.cc, line 1350
21
+ // # Debug check failed: MainThreadIsCurrentThread().
22
+ //
23
+ // ...because the Ruby runtime clobbers thread-local variables when it
24
+ // context-switches threads. You have been warned.
25
+ }
26
+ #endif
27
+
28
+ #define countof(x) (sizeof(x) / sizeof(*(x)))
29
+ #define endof(x) ((x) + countof(x))
30
+
31
+ // mostly RO: assigned once by platform_set_flag1 while holding |flags_mtx|,
32
+ // from then on read-only and accessible without holding locks
33
+ int single_threaded;
34
+
35
+ // work around missing pthread_barrier_t on macOS
36
+ typedef struct Barrier
37
+ {
38
+ pthread_mutex_t mtx;
39
+ pthread_cond_t cv;
40
+ int count, in, out;
41
+ } Barrier;
42
+
43
+ static inline int barrier_init(Barrier *b, int count)
44
+ {
45
+ int r;
46
+
47
+ if ((r = pthread_mutex_init(&b->mtx, NULL)))
48
+ return r;
49
+ if ((r = pthread_cond_init(&b->cv, NULL))) {
50
+ pthread_mutex_destroy(&b->mtx);
51
+ return r;
52
+ }
53
+ b->count = count;
54
+ b->out = 0;
55
+ b->in = 0;
56
+ return 0;
57
+ }
58
+
59
+ static inline void barrier_destroy(Barrier *b)
60
+ {
61
+ pthread_mutex_destroy(&b->mtx);
62
+ pthread_cond_destroy(&b->cv);
63
+ }
64
+
65
+ static inline int barrier_wait(Barrier *b)
66
+ {
67
+ int last;
68
+
69
+ pthread_mutex_lock(&b->mtx);
70
+ while (b->out)
71
+ pthread_cond_wait(&b->cv, &b->mtx);
72
+ if (++b->in == b->count) {
73
+ b->in = 0;
74
+ b->out = b->count;
75
+ pthread_cond_broadcast(&b->cv);
76
+ } else {
77
+ do
78
+ pthread_cond_wait(&b->cv, &b->mtx);
79
+ while (b->in);
80
+ }
81
+ last = (--b->out == 0);
82
+ if (last)
83
+ pthread_cond_broadcast(&b->cv);
84
+ pthread_mutex_unlock(&b->mtx);
85
+ return last;
86
+ }
87
+
88
+ typedef struct Context
89
+ {
90
+ int depth; // call depth, protected by |rr_mtx|
91
+ // protected by |mtx|; RW for ruby threads, RO for v8 thread;
92
+ // atomic because context_stop (which can be called from other ruby
93
+ // threads) writes it without holding |mtx|, to avoid deadlocking
94
+ // 1=shut down v8, 2=free memory; note that only the v8 thread
95
+ // frees the memory and it intentionally stays around until
96
+ // the ruby object is gc'd, otherwise lifecycle management
97
+ // gets too complicated
98
+ atomic_int quit;
99
+ int verbose_exceptions;
100
+ int64_t idle_gc, max_memory, timeout;
101
+ struct State *pst; // used by v8 thread
102
+ VALUE procs; // array of js -> ruby callbacks
103
+ VALUE exception; // pending exception or Qnil
104
+ Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
105
+ Buf snapshot;
106
+ // |rr_mtx| stands for "recursive ruby mutex"; it's used to exclude
107
+ // other ruby threads but allow reentrancy from the same ruby thread
108
+ // (think ruby->js->ruby->js calls)
109
+ pthread_mutex_t rr_mtx;
110
+ pthread_mutex_t mtx;
111
+ pthread_cond_t cv;
112
+ struct {
113
+ pthread_mutex_t mtx;
114
+ pthread_cond_t cv;
115
+ int cancel;
116
+ } wd; // watchdog
117
+ Barrier early_init, late_init;
118
+ } Context;
119
+
120
+ typedef struct Snapshot {
121
+ VALUE blob;
122
+ } Snapshot;
123
+
124
+ static void context_destroy(Context *c);
125
+ static void context_free(void *arg);
126
+ static void context_mark(void *arg);
127
+ static size_t context_size(const void *arg);
128
+
129
+ static const rb_data_type_t context_type = {
130
+ .wrap_struct_name = "mini_racer/context",
131
+ .function = {
132
+ .dfree = context_free,
133
+ .dmark = context_mark,
134
+ .dsize = context_size,
135
+ },
136
+ };
137
+
138
+ static void snapshot_free(void *arg);
139
+ static void snapshot_mark(void *arg);
140
+ static size_t snapshot_size(const void *arg);
141
+
142
+ static const rb_data_type_t snapshot_type = {
143
+ .wrap_struct_name = "mini_racer/snapshot",
144
+ .function = {
145
+ .dfree = snapshot_free,
146
+ .dmark = snapshot_mark,
147
+ .dsize = snapshot_size,
148
+ },
149
+ };
150
+
151
+ static VALUE platform_init_error;
152
+ static VALUE context_disposed_error;
153
+ static VALUE parse_error;
154
+ static VALUE memory_error;
155
+ static VALUE runtime_error;
156
+ static VALUE internal_error;
157
+ static VALUE snapshot_error;
158
+ static VALUE terminated_error;
159
+ static VALUE context_class;
160
+ static VALUE snapshot_class;
161
+ static VALUE date_time_class;
162
+ static VALUE js_function_class;
163
+
164
+ static pthread_mutex_t flags_mtx = PTHREAD_MUTEX_INITIALIZER;
165
+ static Buf flags; // protected by |flags_mtx|
166
+
167
+ struct rendezvous_nogvl
168
+ {
169
+ Context *context;
170
+ Buf *req, *res;
171
+ };
172
+
173
+ // arg == &(struct rendezvous_nogvl){...}
174
+ static void *rendezvous_callback(void *arg);
175
+
176
+ // note: must be stack-allocated or VALUEs won't be visible to ruby's GC
177
+ typedef struct State
178
+ {
179
+ VALUE a, b;
180
+ } State;
181
+
182
+ // note: must be stack-allocated or VALUEs won't be visible to ruby's GC
183
+ typedef struct DesCtx
184
+ {
185
+ State *tos;
186
+ VALUE refs; // array
187
+ char err[64];
188
+ State stack[512];
189
+ } DesCtx;
190
+
191
+ static void DesCtx_init(DesCtx *c)
192
+ {
193
+ c->tos = c->stack;
194
+ c->refs = rb_ary_new();
195
+ *c->tos = (State){Qundef, Qundef};
196
+ *c->err = '\0';
197
+ }
198
+
199
+ static void put(DesCtx *c, VALUE v)
200
+ {
201
+ VALUE *a, *b;
202
+
203
+ if (*c->err)
204
+ return;
205
+ a = &c->tos->a;
206
+ b = &c->tos->b;
207
+ switch (TYPE(*a)) {
208
+ case T_ARRAY:
209
+ rb_ary_push(*a, v);
210
+ break;
211
+ case T_HASH:
212
+ if (*b == Qundef) {
213
+ *b = v;
214
+ } else {
215
+ *b = rb_funcall(*b, rb_intern("to_s"), 0);
216
+ rb_hash_aset(*a, *b, v);
217
+ *b = Qundef;
218
+ }
219
+ break;
220
+ case T_UNDEF:
221
+ *a = v;
222
+ break;
223
+ default:
224
+ snprintf(c->err, sizeof(c->err), "bad state");
225
+ return;
226
+ }
227
+ }
228
+
229
+ static void push(DesCtx *c, VALUE v)
230
+ {
231
+ if (*c->err)
232
+ return;
233
+ if (c->tos == endof(c->stack)) {
234
+ snprintf(c->err, sizeof(c->err), "stack overflow");
235
+ return;
236
+ }
237
+ *++c->tos = (State){v, Qundef};
238
+ rb_ary_push(c->refs, v);
239
+ }
240
+
241
+ // see also des_named_props_end
242
+ static void pop(DesCtx *c)
243
+ {
244
+ if (*c->err)
245
+ return;
246
+ if (c->tos == c->stack) {
247
+ snprintf(c->err, sizeof(c->err), "stack underflow");
248
+ return;
249
+ }
250
+ put(c, (*c->tos--).a);
251
+ }
252
+
253
+ static void des_null(void *arg)
254
+ {
255
+ put(arg, Qnil);
256
+ }
257
+
258
+ static void des_undefined(void *arg)
259
+ {
260
+ put(arg, Qnil);
261
+ }
262
+
263
+ static void des_bool(void *arg, int v)
264
+ {
265
+ put(arg, v ? Qtrue : Qfalse);
266
+ }
267
+
268
+ static void des_int(void *arg, int64_t v)
269
+ {
270
+ put(arg, LONG2FIX(v));
271
+ }
272
+
273
+ static void des_num(void *arg, double v)
274
+ {
275
+ put(arg, DBL2NUM(v));
276
+ }
277
+
278
+ static void des_date(void *arg, double v)
279
+ {
280
+ double sec, usec;
281
+
282
+ if (!isfinite(v))
283
+ rb_raise(rb_eRangeError, "invalid Date");
284
+ sec = v/1e3;
285
+ usec = 1e3 * fmod(v, 1e3);
286
+ put(arg, rb_time_new(sec, usec));
287
+ }
288
+
289
+ // note: v8 stores bigints in 1's complement, ruby in 2's complement,
290
+ // so we have to take additional steps to ensure correct conversion
291
+ static void des_bigint(void *arg, const void *p, size_t n, int sign)
292
+ {
293
+ VALUE v;
294
+ size_t i;
295
+ DesCtx *c;
296
+ unsigned long *a, t, limbs[65]; // +1 to suppress sign extension
297
+
298
+ c = arg;
299
+ if (*c->err)
300
+ return;
301
+ if (n > sizeof(limbs) - sizeof(*limbs)) {
302
+ snprintf(c->err, sizeof(c->err), "bigint too big");
303
+ return;
304
+ }
305
+ a = limbs;
306
+ t = 0;
307
+ for (i = 0; i < n; a++, i += sizeof(*a)) {
308
+ memcpy(a, (char *)p + i, sizeof(*a));
309
+ t = *a;
310
+ }
311
+ if (t >> 63)
312
+ *a++ = 0; // suppress sign extension
313
+ v = rb_big_unpack(limbs, a-limbs);
314
+ if (sign < 0)
315
+ v = rb_big_mul(v, LONG2FIX(-1));
316
+ put(c, v);
317
+ }
318
+
319
+ static void des_string(void *arg, const char *s, size_t n)
320
+ {
321
+ put(arg, rb_utf8_str_new(s, n));
322
+ }
323
+
324
+ static void des_string8(void *arg, const uint8_t *s, size_t n)
325
+ {
326
+ put(arg, rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding()));
327
+ }
328
+
329
+ // des_string16: |s| is not word aligned
330
+ // des_string16: |n| is in bytes, not code points
331
+ static void des_string16(void *arg, const void *s, size_t n)
332
+ {
333
+ rb_encoding *e;
334
+ DesCtx *c;
335
+
336
+ c = arg;
337
+ if (*c->err)
338
+ return;
339
+ // TODO(bnoordhuis) replace this hack with something more principled
340
+ if (n == sizeof(js_function_marker) && !memcmp(js_function_marker, s, n))
341
+ return put(c, rb_funcall(js_function_class, rb_intern("new"), 0));
342
+ e = rb_enc_find("UTF-16LE"); // TODO cache?
343
+ if (!e) {
344
+ snprintf(c->err, sizeof(c->err), "no UTF16-LE encoding");
345
+ return;
346
+ }
347
+ put(c, rb_enc_str_new((char *)s, n, e));
348
+ }
349
+
350
+ // ruby doesn't really have a concept of a byte array so store it as
351
+ // an 8-bit string instead; it's either that or a regular array of
352
+ // numbers, but the latter is markedly less efficient, storage-wise
353
+ static void des_arraybuffer(void *arg, const void *s, size_t n)
354
+ {
355
+ put(arg, rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding()));
356
+ }
357
+
358
+ static void des_array_begin(void *arg)
359
+ {
360
+ push(arg, rb_ary_new());
361
+ }
362
+
363
+ static void des_array_end(void *arg)
364
+ {
365
+ pop(arg);
366
+ }
367
+
368
+ static void des_named_props_begin(void *arg)
369
+ {
370
+ push(arg, rb_hash_new());
371
+ }
372
+
373
+ // see also pop
374
+ static void des_named_props_end(void *arg)
375
+ {
376
+ DesCtx *c;
377
+
378
+ c = arg;
379
+ if (*c->err)
380
+ return;
381
+ if (c->tos == c->stack) {
382
+ snprintf(c->err, sizeof(c->err), "stack underflow");
383
+ return;
384
+ }
385
+ c->tos--; // dropped, no way to represent in ruby
386
+ }
387
+
388
+ static void des_object_begin(void *arg)
389
+ {
390
+ push(arg, rb_hash_new());
391
+ }
392
+
393
+ static void des_object_end(void *arg)
394
+ {
395
+ pop(arg);
396
+ }
397
+
398
+ static void des_object_ref(void *arg, uint32_t id)
399
+ {
400
+ DesCtx *c;
401
+ VALUE v;
402
+
403
+ c = arg;
404
+ v = rb_ary_entry(c->refs, id);
405
+ put(c, v);
406
+ }
407
+
408
+ static void des_error_begin(void *arg)
409
+ {
410
+ push(arg, rb_class_new_instance(0, NULL, rb_eRuntimeError));
411
+ }
412
+
413
+ static void des_error_end(void *arg)
414
+ {
415
+ pop(arg);
416
+ }
417
+
418
+ static int collect(VALUE k, VALUE v, VALUE a)
419
+ {
420
+ rb_ary_push(a, k);
421
+ rb_ary_push(a, v);
422
+ return ST_CONTINUE;
423
+ }
424
+
425
+ static int serialize1(Ser *s, VALUE refs, VALUE v)
426
+ {
427
+ unsigned long limbs[64];
428
+ VALUE a, t, id;
429
+ size_t i, n;
430
+ int sign;
431
+
432
+ if (*s->err)
433
+ return -1;
434
+ switch (TYPE(v)) {
435
+ case T_ARRAY:
436
+ id = rb_hash_lookup(refs, v);
437
+ if (NIL_P(id)) {
438
+ n = RARRAY_LENINT(v);
439
+ i = rb_hash_size_num(refs);
440
+ rb_hash_aset(refs, v, LONG2FIX(i));
441
+ ser_array_begin(s, n);
442
+ for (i = 0; i < n; i++)
443
+ if (serialize1(s, refs, rb_ary_entry(v, i)))
444
+ return -1;
445
+ ser_array_end(s, n);
446
+ } else {
447
+ ser_object_ref(s, FIX2LONG(id));
448
+ }
449
+ break;
450
+ case T_HASH:
451
+ id = rb_hash_lookup(refs, v);
452
+ if (NIL_P(id)) {
453
+ a = rb_ary_new();
454
+ i = rb_hash_size_num(refs);
455
+ n = rb_hash_size_num(v);
456
+ rb_hash_aset(refs, v, LONG2FIX(i));
457
+ rb_hash_foreach(v, collect, a);
458
+ for (i = 0; i < 2*n; i += 2) {
459
+ t = rb_ary_entry(a, i);
460
+ switch (TYPE(t)) {
461
+ case T_FIXNUM:
462
+ case T_STRING:
463
+ case T_SYMBOL:
464
+ continue;
465
+ }
466
+ break;
467
+ }
468
+ if (i == 2*n) {
469
+ ser_object_begin(s);
470
+ for (i = 0; i < 2*n; i += 2) {
471
+ if (serialize1(s, refs, rb_ary_entry(a, i+0)))
472
+ return -1;
473
+ if (serialize1(s, refs, rb_ary_entry(a, i+1)))
474
+ return -1;
475
+ }
476
+ ser_object_end(s, n);
477
+ } else {
478
+ return bail(&s->err, "TODO serialize as Map");
479
+ }
480
+ } else {
481
+ ser_object_ref(s, FIX2LONG(id));
482
+ }
483
+ break;
484
+ case T_DATA:
485
+ if (date_time_class == CLASS_OF(v)) {
486
+ v = rb_funcall(v, rb_intern("to_time"), 0);
487
+ }
488
+ if (rb_cTime == CLASS_OF(v)) {
489
+ struct timeval tv = rb_time_timeval(v);
490
+ ser_date(s, tv.tv_sec*1e3 + tv.tv_usec/1e3);
491
+ } else {
492
+ static const char undefined_conversion[] = "Undefined Conversion";
493
+ ser_string(s, undefined_conversion, sizeof(undefined_conversion)-1);
494
+ }
495
+ break;
496
+ case T_NIL:
497
+ ser_null(s);
498
+ break;
499
+ case T_UNDEF:
500
+ ser_undefined(s);
501
+ break;
502
+ case T_TRUE:
503
+ ser_bool(s, 1);
504
+ break;
505
+ case T_FALSE:
506
+ ser_bool(s, 0);
507
+ break;
508
+ case T_BIGNUM:
509
+ // note: v8 stores bigints in 1's complement, ruby in 2's complement,
510
+ // so we have to take additional steps to ensure correct conversion
511
+ memset(limbs, 0, sizeof(limbs));
512
+ sign = rb_big_sign(v) ? 1 : -1;
513
+ if (sign < 0)
514
+ v = rb_big_mul(v, LONG2FIX(-1));
515
+ rb_big_pack(v, limbs, countof(limbs));
516
+ ser_bigint(s, limbs, countof(limbs), sign);
517
+ break;
518
+ case T_FIXNUM:
519
+ ser_int(s, FIX2LONG(v));
520
+ break;
521
+ case T_FLOAT:
522
+ ser_num(s, NUM2DBL(v));
523
+ break;
524
+ case T_SYMBOL:
525
+ v = rb_sym2str(v);
526
+ // fallthru
527
+ case T_STRING:
528
+ ser_string(s, RSTRING_PTR(v), RSTRING_LENINT(v));
529
+ break;
530
+ default:
531
+ snprintf(s->err, sizeof(s->err), "unsupported type %x", TYPE(v));
532
+ return -1;
533
+ }
534
+ return 0;
535
+ }
536
+
537
+ static struct timespec deadline_ms(int ms)
538
+ {
539
+ static const int64_t ns_per_sec = 1000*1000*1000;
540
+ struct timespec t;
541
+
542
+ #ifdef __APPLE__
543
+ clock_gettime(CLOCK_REALTIME, &t);
544
+ #else
545
+ clock_gettime(CLOCK_MONOTONIC, &t);
546
+ #endif
547
+ t.tv_sec += ms/1000;
548
+ t.tv_nsec += ms%1000 * ns_per_sec/1000;
549
+ while (t.tv_nsec >= ns_per_sec) {
550
+ t.tv_nsec -= ns_per_sec;
551
+ t.tv_sec++;
552
+ }
553
+ return t;
554
+ }
555
+
556
+ static int timespec_le(struct timespec a, struct timespec b)
557
+ {
558
+ if (a.tv_sec < b.tv_sec) return 1;
559
+ return a.tv_sec == b.tv_sec && a.tv_nsec <= b.tv_nsec;
560
+ }
561
+
562
+ static int deadline_exceeded(struct timespec deadline)
563
+ {
564
+ return timespec_le(deadline, deadline_ms(0));
565
+ }
566
+
567
+ static void *v8_watchdog(void *arg)
568
+ {
569
+ struct timespec deadline;
570
+ Context *c;
571
+
572
+ c = arg;
573
+ deadline = deadline_ms(c->timeout);
574
+ pthread_mutex_lock(&c->wd.mtx);
575
+ for (;;) {
576
+ if (c->wd.cancel)
577
+ break;
578
+ pthread_cond_timedwait(&c->wd.cv, &c->wd.mtx, &deadline);
579
+ if (c->wd.cancel)
580
+ break;
581
+ if (deadline_exceeded(deadline)) {
582
+ v8_terminate_execution(c->pst);
583
+ break;
584
+ }
585
+ }
586
+ pthread_mutex_unlock(&c->wd.mtx);
587
+ return NULL;
588
+ }
589
+
590
+ static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
591
+ void (*func)(struct State *pst, const uint8_t *p, size_t n))
592
+ {
593
+ pthread_t thr;
594
+ int r;
595
+
596
+ r = -1;
597
+ if (c->timeout > 0 && (r = pthread_create(&thr, NULL, v8_watchdog, c))) {
598
+ fprintf(stderr, "mini_racer: watchdog: pthread_create: %s\n", strerror(r));
599
+ fflush(stderr);
600
+ }
601
+ func(c->pst, p, n);
602
+ if (r)
603
+ return;
604
+ pthread_mutex_lock(&c->wd.mtx);
605
+ c->wd.cancel = 1;
606
+ pthread_cond_signal(&c->wd.cv);
607
+ pthread_mutex_unlock(&c->wd.mtx);
608
+ pthread_join(thr, NULL);
609
+ c->wd.cancel = 0;
610
+ }
611
+
612
+ static void dispatch1(Context *c, const uint8_t *p, size_t n)
613
+ {
614
+ uint8_t b;
615
+
616
+ assert(n > 0);
617
+ switch (*p) {
618
+ case 'A': return v8_attach(c->pst, p+1, n-1);
619
+ case 'C': return v8_timedwait(c, p+1, n-1, v8_call);
620
+ case 'E': return v8_timedwait(c, p+1, n-1, v8_eval);
621
+ case 'H': return v8_heap_snapshot(c->pst);
622
+ case 'I': return v8_idle_notification(c->pst, p+1, n-1);
623
+ case 'P': return v8_pump_message_loop(c->pst);
624
+ case 'S': return v8_heap_stats(c->pst);
625
+ case 'T': return v8_snapshot(c->pst, p+1, n-1);
626
+ case 'W': return v8_warmup(c->pst, p+1, n-1);
627
+ case 'L':
628
+ b = 0;
629
+ v8_reply(c, &b, 1); // doesn't matter what as long as it's not empty
630
+ return v8_low_memory_notification(c->pst);
631
+ }
632
+ fprintf(stderr, "mini_racer: bad request %02x\n", *p);
633
+ fflush(stderr);
634
+ }
635
+
636
+ static void dispatch(Context *c)
637
+ {
638
+ buf_reset(&c->res);
639
+ dispatch1(c, c->req.buf, c->req.len);
640
+ buf_reset(&c->req);
641
+ }
642
+
643
+ // called by v8_isolate_and_context
644
+ void v8_thread_main(Context *c, struct State *pst)
645
+ {
646
+ struct timespec deadline;
647
+
648
+ c->pst = pst;
649
+ barrier_wait(&c->late_init);
650
+ pthread_mutex_lock(&c->mtx);
651
+ while (!c->quit) {
652
+ if (!c->req.len) {
653
+ if (c->idle_gc > 0) {
654
+ deadline = deadline_ms(c->idle_gc);
655
+ pthread_cond_timedwait(&c->cv, &c->mtx, &deadline);
656
+ if (deadline_exceeded(deadline))
657
+ v8_low_memory_notification(c->pst);
658
+ } else {
659
+ pthread_cond_wait(&c->cv, &c->mtx);
660
+ }
661
+ }
662
+ if (!c->req.len)
663
+ continue; // spurious wakeup or quit signal from other thread
664
+ dispatch(c);
665
+ pthread_cond_signal(&c->cv);
666
+ }
667
+ }
668
+
669
+ // called by v8_thread_main and from mini_racer_v8.cc,
670
+ // in all cases with Context.mtx held
671
+ void v8_dispatch(Context *c)
672
+ {
673
+ dispatch1(c, c->req.buf, c->req.len);
674
+ buf_reset(&c->req);
675
+ }
676
+
677
+ // called from mini_racer_v8.cc with Context.mtx held
678
+ // only called when inside v8_call, v8_eval, or v8_pump_message_loop
679
+ void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
680
+ {
681
+ struct rendezvous_nogvl *args;
682
+
683
+ buf_reset(&c->req);
684
+ if (single_threaded) {
685
+ assert(*c->res.buf == 'c'); // js -> ruby callback
686
+ args = &(struct rendezvous_nogvl){c, &c->req, &c->res};
687
+ rb_thread_call_with_gvl(rendezvous_callback, args);
688
+ } else {
689
+ pthread_cond_signal(&c->cv);
690
+ while (!c->req.len)
691
+ pthread_cond_wait(&c->cv, &c->mtx);
692
+ }
693
+ buf_reset(&c->res);
694
+ *p = c->req.buf;
695
+ *n = c->req.len;
696
+ }
697
+
698
+ // called from mini_racer_v8.cc with Context.mtx held
699
+ void v8_reply(Context *c, const uint8_t *p, size_t n)
700
+ {
701
+ buf_put(&c->res, p, n);
702
+ }
703
+
704
+ static void v8_once_init(void)
705
+ {
706
+ static pthread_once_t once = PTHREAD_ONCE_INIT;
707
+ pthread_once(&once, v8_global_init);
708
+ }
709
+
710
+ static void *v8_thread_start(void *arg)
711
+ {
712
+ Context *c;
713
+
714
+ c = arg;
715
+ barrier_wait(&c->early_init);
716
+ v8_once_init();
717
+ v8_thread_init(c, c->snapshot.buf, c->snapshot.len, c->max_memory, c->verbose_exceptions);
718
+ while (c->quit < 2)
719
+ pthread_cond_wait(&c->cv, &c->mtx);
720
+ context_destroy(c);
721
+ return NULL;
722
+ }
723
+
724
+ static VALUE deserialize1(const uint8_t *p, size_t n)
725
+ {
726
+ char err[64];
727
+ DesCtx d;
728
+
729
+ DesCtx_init(&d);
730
+ if (des(&err, p, n, &d))
731
+ rb_raise(runtime_error, "%s", err);
732
+ if (d.tos != d.stack) // should not happen
733
+ rb_raise(runtime_error, "parse stack not empty");
734
+ return d.tos->a;
735
+ }
736
+
737
+ static VALUE deserialize(VALUE arg)
738
+ {
739
+ Buf *b;
740
+
741
+ b = (void *)arg;
742
+ return deserialize1(b->buf, b->len);
743
+ }
744
+
745
+ // called with |rr_mtx| and GVL held; can raise exception
746
+ static VALUE rendezvous_callback_do(VALUE arg)
747
+ {
748
+ struct rendezvous_nogvl *a;
749
+ VALUE func, args;
750
+ Context *c;
751
+ Buf *b;
752
+
753
+ a = (void *)arg;
754
+ b = a->res;
755
+ c = a->context;
756
+ assert(b->len > 0);
757
+ assert(*b->buf == 'c');
758
+ args = deserialize1(b->buf+1, b->len-1); // skip 'c' marker
759
+ func = rb_ary_pop(args); // callback id
760
+ func = rb_ary_entry(c->procs, FIX2LONG(func));
761
+ return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
762
+ }
763
+
764
+ // called with |rr_mtx| and GVL held; |mtx| is unlocked
765
+ // callback data is in |a->res|, serialized result goes in |a->req|
766
+ static void *rendezvous_callback(void *arg)
767
+ {
768
+ struct rendezvous_nogvl *a;
769
+ Context *c;
770
+ int exc;
771
+ VALUE r;
772
+ Ser s;
773
+
774
+ a = arg;
775
+ c = a->context;
776
+ r = rb_protect(rendezvous_callback_do, (VALUE)a, &exc);
777
+ if (exc) {
778
+ c->exception = rb_errinfo();
779
+ rb_set_errinfo(Qnil);
780
+ goto fail;
781
+ }
782
+ ser_init1(&s, 'c'); // callback reply
783
+ ser_array_begin(&s, 2);
784
+ // either [result, undefined] or [undefined, err]
785
+ if (exc)
786
+ ser_undefined(&s);
787
+ if (serialize1(&s, rb_hash_new(), r)) { // should not happen
788
+ c->exception = rb_exc_new_cstr(internal_error, s.err);
789
+ ser_reset(&s);
790
+ goto fail;
791
+ }
792
+ if (!exc)
793
+ ser_undefined(&s);
794
+ ser_array_end(&s, 2);
795
+ out:
796
+ buf_move(&s.b, a->req);
797
+ return NULL;
798
+ fail:
799
+ ser_init1(&s, 'e'); // exception pending
800
+ goto out;
801
+ }
802
+
803
+ static inline void *rendezvous_nogvl(void *arg)
804
+ {
805
+ struct rendezvous_nogvl *a;
806
+ Context *c;
807
+
808
+ a = arg;
809
+ c = a->context;
810
+ pthread_mutex_lock(&c->rr_mtx);
811
+ if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
812
+ fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
813
+ fflush(stderr);
814
+ }
815
+ c->depth++;
816
+ next:
817
+ pthread_mutex_lock(&c->mtx);
818
+ assert(c->req.len == 0);
819
+ assert(c->res.len == 0);
820
+ buf_move(a->req, &c->req); // v8 thread takes ownership of req
821
+ if (single_threaded) {
822
+ v8_single_threaded_enter(c->pst, c, dispatch);
823
+ } else {
824
+ pthread_cond_signal(&c->cv);
825
+ do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
826
+ }
827
+ buf_move(&c->res, a->res);
828
+ pthread_mutex_unlock(&c->mtx);
829
+ if (*a->res->buf == 'c') { // js -> ruby callback?
830
+ rb_thread_call_with_gvl(rendezvous_callback, a);
831
+ goto next;
832
+ }
833
+ c->depth--;
834
+ pthread_mutex_unlock(&c->rr_mtx);
835
+ return NULL;
836
+ }
837
+
838
+ static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
839
+ {
840
+ if (atomic_load(&c->quit)) {
841
+ buf_reset(req);
842
+ rb_raise(context_disposed_error, "disposed context");
843
+ }
844
+ rb_nogvl(rendezvous_nogvl, &(struct rendezvous_nogvl){c, req, res},
845
+ NULL, NULL, 0);
846
+ }
847
+
848
+ // send request to & receive reply from v8 thread; takes ownership of |req|
849
+ // can raise exceptions and longjmp away but won't leak |req|
850
+ static VALUE rendezvous(Context *c, Buf *req)
851
+ {
852
+ VALUE r;
853
+ Buf res;
854
+ int exc;
855
+
856
+ rendezvous_no_des(c, req, &res); // takes ownership of |req|
857
+ r = rb_protect(deserialize, (VALUE)&res, &exc);
858
+ buf_reset(&res);
859
+ if (exc) {
860
+ r = rb_errinfo();
861
+ rb_set_errinfo(Qnil);
862
+ rb_exc_raise(r);
863
+ }
864
+ if (!NIL_P(c->exception)) {
865
+ r = c->exception;
866
+ c->exception = Qnil;
867
+ rb_exc_raise(r);
868
+ }
869
+ return r;
870
+ }
871
+
872
+ static void handle_exception(VALUE e)
873
+ {
874
+ const char *s;
875
+ VALUE klass;
876
+
877
+ if (NIL_P(e))
878
+ return;
879
+ StringValue(e);
880
+ s = RSTRING_PTR(e);
881
+ switch (*s) {
882
+ case NO_ERROR:
883
+ return;
884
+ case INTERNAL_ERROR:
885
+ klass = internal_error;
886
+ break;
887
+ case MEMORY_ERROR:
888
+ klass = memory_error;
889
+ break;
890
+ case PARSE_ERROR:
891
+ klass = parse_error;
892
+ break;
893
+ case RUNTIME_ERROR:
894
+ klass = runtime_error;
895
+ break;
896
+ case TERMINATED_ERROR:
897
+ klass = terminated_error;
898
+ break;
899
+ default:
900
+ rb_raise(internal_error, "bad error class %02x", *s);
901
+ }
902
+ rb_raise(klass, "%s", s+1);
903
+ }
904
+
905
+ static VALUE context_alloc(VALUE klass)
906
+ {
907
+ pthread_mutexattr_t mattr;
908
+ pthread_condattr_t cattr;
909
+ const char *cause;
910
+ Context *c;
911
+ VALUE f, a;
912
+ int r;
913
+
914
+ // Safe to lazy init because we hold the GVL
915
+ if (NIL_P(date_time_class)) {
916
+ f = rb_intern("const_defined?");
917
+ a = rb_str_new_cstr("DateTime");
918
+ if (Qtrue == rb_funcall(rb_cObject, f, 1, a))
919
+ date_time_class = rb_const_get(rb_cObject, rb_intern("DateTime"));
920
+ }
921
+ c = ruby_xmalloc(sizeof(*c));
922
+ memset(c, 0, sizeof(*c));
923
+ c->exception = Qnil;
924
+ c->procs = rb_ary_new();
925
+ buf_init(&c->snapshot);
926
+ buf_init(&c->req);
927
+ buf_init(&c->res);
928
+ cause = "pthread_condattr_init";
929
+ if ((r = pthread_condattr_init(&cattr)))
930
+ goto fail0;
931
+ #ifndef __APPLE__
932
+ pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
933
+ #endif
934
+ cause = "pthread_mutexattr_init";
935
+ if ((r = pthread_mutexattr_init(&mattr)))
936
+ goto fail1;
937
+ pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
938
+ cause = "pthread_mutex_init";
939
+ r = pthread_mutex_init(&c->rr_mtx, &mattr);
940
+ pthread_mutexattr_destroy(&mattr);
941
+ if (r)
942
+ goto fail1;
943
+ if (pthread_mutex_init(&c->mtx, NULL))
944
+ goto fail2;
945
+ cause = "pthread_cond_init";
946
+ if ((r = pthread_cond_init(&c->cv, &cattr)))
947
+ goto fail3;
948
+ cause = "pthread_mutex_init";
949
+ if ((r = pthread_mutex_init(&c->wd.mtx, NULL)))
950
+ goto fail4;
951
+ cause = "pthread_cond_init";
952
+ if (pthread_cond_init(&c->wd.cv, &cattr))
953
+ goto fail5;
954
+ cause = "barrier_init";
955
+ if ((r = barrier_init(&c->early_init, 2)))
956
+ goto fail6;
957
+ cause = "barrier_init";
958
+ if ((r = barrier_init(&c->late_init, 2)))
959
+ goto fail7;
960
+ pthread_condattr_destroy(&cattr);
961
+ return TypedData_Wrap_Struct(klass, &context_type, c);
962
+ fail7:
963
+ barrier_destroy(&c->early_init);
964
+ fail6:
965
+ pthread_cond_destroy(&c->wd.cv);
966
+ fail5:
967
+ pthread_mutex_destroy(&c->wd.mtx);
968
+ fail4:
969
+ pthread_cond_destroy(&c->cv);
970
+ fail3:
971
+ pthread_mutex_destroy(&c->mtx);
972
+ fail2:
973
+ pthread_mutex_destroy(&c->rr_mtx);
974
+ fail1:
975
+ pthread_condattr_destroy(&cattr);
976
+ fail0:
977
+ ruby_xfree(c);
978
+ rb_raise(runtime_error, "%s: %s", cause, strerror(r));
979
+ return Qnil; // pacify compiler
980
+ }
981
+
982
+ static void *context_free_thread_do(void *arg)
983
+ {
984
+ Context *c;
985
+
986
+ c = arg;
987
+ v8_single_threaded_dispose(c->pst);
988
+ context_destroy(c);
989
+ return NULL;
990
+ }
991
+
992
+ static void context_free_thread(Context *c)
993
+ {
994
+ pthread_t thr;
995
+ int r;
996
+
997
+ // dispose on another thread so we don't block when trying to
998
+ // enter an isolate that's in a stuck state; that *should* be
999
+ // impossible but apparently it happened regularly before the
1000
+ // rewrite and I'm carrying it over out of an abundance of caution
1001
+ if ((r = pthread_create(&thr, NULL, context_free_thread_do, c))) {
1002
+ fprintf(stderr, "mini_racer: pthread_create: %s", strerror(r));
1003
+ fflush(stderr);
1004
+ context_free_thread_do(c);
1005
+ } else {
1006
+ pthread_detach(thr);
1007
+ }
1008
+ }
1009
+
1010
+ static void context_free(void *arg)
1011
+ {
1012
+ Context *c;
1013
+
1014
+ c = arg;
1015
+ if (single_threaded) {
1016
+ context_free_thread(c);
1017
+ } else {
1018
+ pthread_mutex_lock(&c->mtx);
1019
+ c->quit = 2; // 2 = v8 thread frees
1020
+ pthread_cond_signal(&c->cv);
1021
+ pthread_mutex_unlock(&c->mtx);
1022
+ }
1023
+ }
1024
+
1025
+ static void context_destroy(Context *c)
1026
+ {
1027
+ pthread_mutex_unlock(&c->mtx);
1028
+ pthread_mutex_destroy(&c->mtx);
1029
+ pthread_cond_destroy(&c->cv);
1030
+ barrier_destroy(&c->early_init);
1031
+ barrier_destroy(&c->late_init);
1032
+ pthread_mutex_destroy(&c->wd.mtx);
1033
+ pthread_cond_destroy(&c->wd.cv);
1034
+ buf_reset(&c->snapshot);
1035
+ buf_reset(&c->req);
1036
+ buf_reset(&c->res);
1037
+ ruby_xfree(c);
1038
+ }
1039
+
1040
+ static void context_mark(void *arg)
1041
+ {
1042
+ Context *c;
1043
+
1044
+ c = arg;
1045
+ rb_gc_mark(c->procs);
1046
+ rb_gc_mark(c->exception);
1047
+ }
1048
+
1049
+ static size_t context_size(const void *arg)
1050
+ {
1051
+ const Context *c = arg;
1052
+ return sizeof(*c);
1053
+ }
1054
+
1055
+ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
1056
+ {
1057
+ Context *c;
1058
+ VALUE e;
1059
+ Ser s;
1060
+
1061
+ TypedData_Get_Struct(self, Context, &context_type, c);
1062
+ // request is (A)ttach, [name, id] array
1063
+ ser_init1(&s, 'A');
1064
+ ser_array_begin(&s, 2);
1065
+ ser_string(&s, RSTRING_PTR(name), RSTRING_LENINT(name));
1066
+ ser_int(&s, RARRAY_LENINT(c->procs));
1067
+ ser_array_end(&s, 2);
1068
+ rb_ary_push(c->procs, proc);
1069
+ // response is an exception or undefined
1070
+ e = rendezvous(c, &s.b);
1071
+ handle_exception(e);
1072
+ return Qnil;
1073
+ }
1074
+
1075
+ static void *context_dispose_do(void *arg)
1076
+ {
1077
+ Context *c;
1078
+
1079
+ c = arg;
1080
+ if (single_threaded) {
1081
+ atomic_store(&c->quit, 1); // disposed
1082
+ // intentionally a no-op for now
1083
+ } else {
1084
+ pthread_mutex_lock(&c->mtx);
1085
+ while (c->req.len || c->res.len)
1086
+ pthread_cond_wait(&c->cv, &c->mtx);
1087
+ atomic_store(&c->quit, 1); // disposed
1088
+ pthread_cond_signal(&c->cv); // wake up v8 thread
1089
+ pthread_mutex_unlock(&c->mtx);
1090
+ }
1091
+ return NULL;
1092
+ }
1093
+
1094
+ static VALUE context_dispose(VALUE self)
1095
+ {
1096
+ Context *c;
1097
+
1098
+ TypedData_Get_Struct(self, Context, &context_type, c);
1099
+ rb_thread_call_without_gvl(context_dispose_do, c, NULL, NULL);
1100
+ return Qnil;
1101
+ }
1102
+
1103
+ static VALUE context_stop(VALUE self)
1104
+ {
1105
+ Context *c;
1106
+
1107
+ // does not grab |mtx| because Context.stop can be called from another
1108
+ // thread and then we deadlock if e.g. the V8 thread busy-loops in JS
1109
+ TypedData_Get_Struct(self, Context, &context_type, c);
1110
+ if (atomic_load(&c->quit))
1111
+ rb_raise(context_disposed_error, "disposed context");
1112
+ v8_terminate_execution(c->pst);
1113
+ return Qnil;
1114
+ }
1115
+
1116
+ static VALUE context_call(int argc, VALUE *argv, VALUE self)
1117
+ {
1118
+ VALUE a, e, h;
1119
+ Context *c;
1120
+ int i;
1121
+ Ser s;
1122
+
1123
+ TypedData_Get_Struct(self, Context, &context_type, c);
1124
+ rb_scan_args(argc, argv, "1*", &a, &e);
1125
+ Check_Type(a, T_STRING);
1126
+ // request is (C)all, [name, args...] array
1127
+ ser_init1(&s, 'C');
1128
+ ser_array_begin(&s, argc);
1129
+ h = rb_hash_new();
1130
+ for (i = 0; i < argc; i++) {
1131
+ if (serialize1(&s, h, argv[i])) {
1132
+ ser_reset(&s);
1133
+ rb_raise(runtime_error, "Context.call: %s", s.err);
1134
+ }
1135
+ }
1136
+ ser_array_end(&s, argc);
1137
+ // response is [result, err] array
1138
+ a = rendezvous(c, &s.b); // takes ownership of |s.b|
1139
+ e = rb_ary_pop(a);
1140
+ handle_exception(e);
1141
+ return rb_ary_pop(a);
1142
+ }
1143
+
1144
+ static VALUE context_eval(int argc, VALUE *argv, VALUE self)
1145
+ {
1146
+ VALUE a, e, source, filename, kwargs;
1147
+ Context *c;
1148
+ Ser s;
1149
+
1150
+ TypedData_Get_Struct(self, Context, &context_type, c);
1151
+ filename = Qnil;
1152
+ rb_scan_args(argc, argv, "1:", &source, &kwargs);
1153
+ Check_Type(source, T_STRING);
1154
+ if (!NIL_P(kwargs))
1155
+ filename = rb_hash_aref(kwargs, rb_id2sym(rb_intern("filename")));
1156
+ if (NIL_P(filename))
1157
+ filename = rb_str_new_cstr("<eval>");
1158
+ Check_Type(filename, T_STRING);
1159
+ // request is (E)val, [filename, source] array
1160
+ ser_init1(&s, 'E');
1161
+ ser_array_begin(&s, 2);
1162
+ ser_string(&s, RSTRING_PTR(filename), RSTRING_LENINT(filename));
1163
+ ser_string(&s, RSTRING_PTR(source), RSTRING_LENINT(source));
1164
+ ser_array_end(&s, 2);
1165
+ // response is [result, errname] array
1166
+ a = rendezvous(c, &s.b); // takes ownership of |s.b|
1167
+ e = rb_ary_pop(a);
1168
+ handle_exception(e);
1169
+ return rb_ary_pop(a);
1170
+ }
1171
+
1172
+ static VALUE context_heap_stats(VALUE self)
1173
+ {
1174
+ VALUE a, h, k, v;
1175
+ Context *c;
1176
+ int i, n;
1177
+ Buf b;
1178
+
1179
+ TypedData_Get_Struct(self, Context, &context_type, c);
1180
+ buf_init(&b);
1181
+ buf_putc(&b, 'S'); // (S)tats, returns object
1182
+ h = rendezvous(c, &b); // takes ownership of |b|
1183
+ a = rb_ary_new();
1184
+ rb_hash_foreach(h, collect, a);
1185
+ for (i = 0, n = RARRAY_LENINT(a); i < n; i += 2) {
1186
+ k = rb_ary_entry(a, i+0);
1187
+ v = rb_ary_entry(a, i+1);
1188
+ rb_hash_delete(h, k);
1189
+ rb_hash_aset(h, rb_str_intern(k), v); // turn "key" into :key
1190
+ }
1191
+ return h;
1192
+ }
1193
+
1194
+ static VALUE context_heap_snapshot(VALUE self)
1195
+ {
1196
+ Buf req, res;
1197
+ Context *c;
1198
+
1199
+ TypedData_Get_Struct(self, Context, &context_type, c);
1200
+ buf_init(&req);
1201
+ buf_putc(&req, 'H'); // (H)eap snapshot, returns plain bytes
1202
+ rendezvous_no_des(c, &req, &res); // takes ownership of |req|
1203
+ return rb_utf8_str_new((char *)res.buf, res.len);
1204
+ }
1205
+
1206
+ static VALUE context_pump_message_loop(VALUE self)
1207
+ {
1208
+ Context *c;
1209
+ Buf b;
1210
+
1211
+ TypedData_Get_Struct(self, Context, &context_type, c);
1212
+ buf_init(&b);
1213
+ buf_putc(&b, 'P'); // (P)ump, returns bool
1214
+ return rendezvous(c, &b); // takes ownership of |b|
1215
+ }
1216
+
1217
+ static VALUE context_idle_notification(VALUE self, VALUE arg)
1218
+ {
1219
+ Context *c;
1220
+ Ser s;
1221
+
1222
+ Check_Type(arg, T_FIXNUM);
1223
+ TypedData_Get_Struct(self, Context, &context_type, c);
1224
+ // request is (I)dle notification, idle_time_in_seconds
1225
+ ser_init1(&s, 'I');
1226
+ ser_num(&s, LONG2FIX(arg) / 1e3);
1227
+ // response is |undefined|
1228
+ return rendezvous(c, &s.b); // takes ownership of |s.b|
1229
+ }
1230
+
1231
+ static VALUE context_low_memory_notification(VALUE self)
1232
+ {
1233
+ Buf req, res;
1234
+ Context *c;
1235
+
1236
+ TypedData_Get_Struct(self, Context, &context_type, c);
1237
+ buf_init(&req);
1238
+ buf_putc(&req, 'L'); // (L)ow memory notification, returns nothing
1239
+ rendezvous_no_des(c, &req, &res); // takes ownership of |req|
1240
+ return Qnil;
1241
+ }
1242
+
1243
+ static int platform_set_flag1(VALUE k, VALUE v)
1244
+ {
1245
+ char *p, *q, buf[256];
1246
+ int ok;
1247
+
1248
+ k = rb_funcall(k, rb_intern("to_s"), 0);
1249
+ Check_Type(k, T_STRING);
1250
+ if (!NIL_P(v)) {
1251
+ v = rb_funcall(v, rb_intern("to_s"), 0);
1252
+ Check_Type(v, T_STRING);
1253
+ }
1254
+ p = RSTRING_PTR(k);
1255
+ if (!strncmp(p, "--", 2))
1256
+ p += 2;
1257
+ if (NIL_P(v)) {
1258
+ snprintf(buf, sizeof(buf), "--%s", p);
1259
+ } else {
1260
+ snprintf(buf, sizeof(buf), "--%s=%s", p, RSTRING_PTR(v));
1261
+ }
1262
+ p = buf;
1263
+ pthread_mutex_lock(&flags_mtx);
1264
+ if (!flags.buf)
1265
+ buf_init(&flags);
1266
+ ok = (*flags.buf != 1);
1267
+ if (ok) {
1268
+ buf_put(&flags, p, 1+strlen(p)); // include trailing \0
1269
+ // strip dashes and underscores to reduce the number of variant
1270
+ // spellings (--no-single-threaded, --nosingle-threaded,
1271
+ // --no_single_threaded, etc.)
1272
+ p = q = buf;
1273
+ for (;;) {
1274
+ if (*p != '-')
1275
+ if (*p != '_')
1276
+ *q++ = *p;
1277
+ if (!*p++)
1278
+ break;
1279
+ }
1280
+ if (!strcmp(buf, "singlethreaded")) {
1281
+ single_threaded = 1;
1282
+ } else if (!strcmp(buf, "nosinglethreaded")) {
1283
+ single_threaded = 0;
1284
+ }
1285
+ }
1286
+ pthread_mutex_unlock(&flags_mtx);
1287
+ return ok;
1288
+ }
1289
+
1290
+ static VALUE platform_set_flags(int argc, VALUE *argv, VALUE klass)
1291
+ {
1292
+ VALUE args, kwargs, k, v;
1293
+ int i, n;
1294
+
1295
+ (void)&klass;
1296
+ rb_scan_args(argc, argv, "*:", &args, &kwargs);
1297
+ Check_Type(args, T_ARRAY);
1298
+ for (i = 0, n = RARRAY_LENINT(args); i < n; i++) {
1299
+ k = rb_ary_entry(args, i);
1300
+ v = Qnil;
1301
+ if (!platform_set_flag1(k, v))
1302
+ goto fail;
1303
+ }
1304
+ if (NIL_P(kwargs))
1305
+ return Qnil;
1306
+ Check_Type(kwargs, T_HASH);
1307
+ args = rb_ary_new();
1308
+ rb_hash_foreach(kwargs, collect, args);
1309
+ for (i = 0, n = RARRAY_LENINT(args); i < n; i += 2) {
1310
+ k = rb_ary_entry(args, i+0);
1311
+ v = rb_ary_entry(args, i+1);
1312
+ if (!platform_set_flag1(k, v))
1313
+ goto fail;
1314
+ }
1315
+ return Qnil;
1316
+ fail:
1317
+ rb_raise(platform_init_error, "platform already initialized");
1318
+ }
1319
+
1320
+ // called by v8_global_init; caller must free |*p| with free()
1321
+ void v8_get_flags(char **p, size_t *n)
1322
+ {
1323
+ *p = NULL;
1324
+ *n = 0;
1325
+ pthread_mutex_lock(&flags_mtx);
1326
+ if (!flags.len)
1327
+ goto out;
1328
+ *p = malloc(flags.len);
1329
+ if (!*p)
1330
+ goto out;
1331
+ *n = flags.len;
1332
+ memcpy(*p, flags.buf, *n);
1333
+ buf_reset(&flags);
1334
+ out:
1335
+ buf_init(&flags);
1336
+ buf_putc(&flags, 1); // marker to indicate it's been cleared
1337
+ pthread_mutex_unlock(&flags_mtx);
1338
+ if (single_threaded)
1339
+ rb_thread_lock_native_thread();
1340
+ }
1341
+
1342
+ static VALUE context_initialize(int argc, VALUE *argv, VALUE self)
1343
+ {
1344
+ VALUE kwargs, a, k, v;
1345
+ pthread_attr_t attr;
1346
+ const char *cause;
1347
+ pthread_t thr;
1348
+ Snapshot *ss;
1349
+ Context *c;
1350
+ char *s;
1351
+ int r;
1352
+
1353
+ TypedData_Get_Struct(self, Context, &context_type, c);
1354
+ rb_scan_args(argc, argv, ":", &kwargs);
1355
+ if (NIL_P(kwargs))
1356
+ goto init;
1357
+ a = rb_ary_new();
1358
+ rb_hash_foreach(kwargs, collect, a);
1359
+ while (RARRAY_LENINT(a)) {
1360
+ v = rb_ary_pop(a);
1361
+ k = rb_ary_pop(a);
1362
+ k = rb_sym2str(k);
1363
+ s = RSTRING_PTR(k);
1364
+ if (!strcmp(s, "ensure_gc_after_idle")) {
1365
+ Check_Type(v, T_FIXNUM);
1366
+ c->idle_gc = FIX2LONG(v);
1367
+ if (c->idle_gc < 0 || c->idle_gc > INT32_MAX)
1368
+ rb_raise(rb_eArgError, "bad ensure_gc_after_idle");
1369
+ } else if (!strcmp(s, "max_memory")) {
1370
+ Check_Type(v, T_FIXNUM);
1371
+ c->max_memory = FIX2LONG(v);
1372
+ if (c->max_memory < 0 || c->max_memory >= UINT32_MAX)
1373
+ rb_raise(rb_eArgError, "bad max_memory");
1374
+ } else if (!strcmp(s, "marshal_stack_depth")) { // backcompat, ignored
1375
+ Check_Type(v, T_FIXNUM);
1376
+ } else if (!strcmp(s, "timeout")) {
1377
+ Check_Type(v, T_FIXNUM);
1378
+ c->timeout = FIX2LONG(v);
1379
+ if (c->timeout < 0 || c->timeout > INT32_MAX)
1380
+ rb_raise(rb_eArgError, "bad timeout");
1381
+ } else if (!strcmp(s, "snapshot")) {
1382
+ if (NIL_P(v))
1383
+ continue;
1384
+ TypedData_Get_Struct(v, Snapshot, &snapshot_type, ss);
1385
+ if (buf_put(&c->snapshot, RSTRING_PTR(ss->blob), RSTRING_LENINT(ss->blob)))
1386
+ rb_raise(runtime_error, "out of memory");
1387
+ } else if (!strcmp(s, "verbose_exceptions")) {
1388
+ c->verbose_exceptions = !(v == Qfalse || v == Qnil);
1389
+ } else {
1390
+ rb_raise(runtime_error, "bad keyword: %s", s);
1391
+ }
1392
+ }
1393
+ init:
1394
+ if (single_threaded) {
1395
+ v8_once_init();
1396
+ c->pst = v8_thread_init(c, c->snapshot.buf, c->snapshot.len, c->max_memory, c->verbose_exceptions);
1397
+ } else {
1398
+ cause = "pthread_attr_init";
1399
+ if ((r = pthread_attr_init(&attr)))
1400
+ goto fail;
1401
+ pthread_attr_setstacksize(&attr, 2<<20); // 2 MiB
1402
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1403
+ // v8 thread takes ownership of |c|
1404
+ cause = "pthread_create";
1405
+ r = pthread_create(&thr, &attr, v8_thread_start, c);
1406
+ pthread_attr_destroy(&attr);
1407
+ if (r)
1408
+ goto fail;
1409
+ barrier_wait(&c->early_init);
1410
+ barrier_wait(&c->late_init);
1411
+ }
1412
+ return Qnil;
1413
+ fail:
1414
+ rb_raise(runtime_error, "Context.initialize: %s: %s", cause, strerror(r));
1415
+ return Qnil; // pacify compiler
1416
+ }
1417
+
1418
+ static VALUE snapshot_alloc(VALUE klass)
1419
+ {
1420
+ Snapshot *ss;
1421
+
1422
+ ss = ruby_xmalloc(sizeof(*ss));
1423
+ ss->blob = rb_enc_str_new("", 0, rb_ascii8bit_encoding());
1424
+ return TypedData_Wrap_Struct(klass, &snapshot_type, ss);
1425
+ }
1426
+
1427
+ static void snapshot_free(void *arg)
1428
+ {
1429
+ ruby_xfree(arg);
1430
+ }
1431
+
1432
+ static void snapshot_mark(void *arg)
1433
+ {
1434
+ Snapshot *ss;
1435
+
1436
+ ss = arg;
1437
+ rb_gc_mark(ss->blob);
1438
+ }
1439
+
1440
+ static size_t snapshot_size(const void *arg)
1441
+ {
1442
+ const Snapshot *ss;
1443
+
1444
+ ss = arg;
1445
+ return sizeof(*ss) + RSTRING_LENINT(ss->blob);
1446
+ }
1447
+
1448
+ static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
1449
+ {
1450
+ VALUE a, e, code, cv;
1451
+ Snapshot *ss;
1452
+ Context *c;
1453
+ Ser s;
1454
+
1455
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
1456
+ rb_scan_args(argc, argv, "01", &code);
1457
+ if (NIL_P(code))
1458
+ code = rb_str_new_cstr("");
1459
+ Check_Type(code, T_STRING);
1460
+ cv = context_alloc(context_class);
1461
+ context_initialize(0, NULL, cv);
1462
+ TypedData_Get_Struct(cv, Context, &context_type, c);
1463
+ // request is snapsho(T), "code"
1464
+ ser_init1(&s, 'T');
1465
+ ser_string(&s, RSTRING_PTR(code), RSTRING_LENINT(code));
1466
+ // response is [arraybuffer, error]
1467
+ a = rendezvous(c, &s.b);
1468
+ e = rb_ary_pop(a);
1469
+ context_dispose(cv);
1470
+ if (*RSTRING_PTR(e))
1471
+ rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
1472
+ ss->blob = rb_ary_pop(a);
1473
+ return Qnil;
1474
+ }
1475
+
1476
+ static VALUE snapshot_warmup(VALUE self, VALUE arg)
1477
+ {
1478
+ VALUE a, e, cv;
1479
+ Snapshot *ss;
1480
+ Context *c;
1481
+ Ser s;
1482
+
1483
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
1484
+ Check_Type(arg, T_STRING);
1485
+ cv = context_alloc(context_class);
1486
+ context_initialize(0, NULL, cv);
1487
+ TypedData_Get_Struct(cv, Context, &context_type, c);
1488
+ // request is (W)armup, [snapshot, "warmup code"]
1489
+ ser_init1(&s, 'W');
1490
+ ser_array_begin(&s, 2);
1491
+ ser_string8(&s, (const uint8_t *)RSTRING_PTR(ss->blob), RSTRING_LENINT(ss->blob));
1492
+ ser_string(&s, RSTRING_PTR(arg), RSTRING_LENINT(arg));
1493
+ ser_array_end(&s, 2);
1494
+ // response is [arraybuffer, error]
1495
+ a = rendezvous(c, &s.b);
1496
+ e = rb_ary_pop(a);
1497
+ context_dispose(cv);
1498
+ if (*RSTRING_PTR(e))
1499
+ rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
1500
+ ss->blob = rb_ary_pop(a);
1501
+ return self;
1502
+ }
1503
+
1504
+ static VALUE snapshot_dump(VALUE self)
1505
+ {
1506
+ Snapshot *ss;
1507
+
1508
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
1509
+ return ss->blob;
1510
+ }
1511
+
1512
+ static VALUE snapshot_size0(VALUE self)
1513
+ {
1514
+ Snapshot *ss;
1515
+
1516
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
1517
+ return LONG2FIX(RSTRING_LENINT(ss->blob));
1518
+ }
1519
+
1520
+ __attribute__((visibility("default")))
1521
+ void Init_mini_racer_extension(void)
1522
+ {
1523
+ VALUE c, m;
1524
+
1525
+ m = rb_define_module("MiniRacer");
1526
+ c = rb_define_class_under(m, "Error", rb_eStandardError);
1527
+ snapshot_error = rb_define_class_under(m, "SnapshotError", c);
1528
+ platform_init_error = rb_define_class_under(m, "PlatformAlreadyInitialized", c);
1529
+ context_disposed_error = rb_define_class_under(m, "ContextDisposedError", c);
1530
+
1531
+ c = rb_define_class_under(m, "EvalError", c);
1532
+ parse_error = rb_define_class_under(m, "ParseError", c);
1533
+ memory_error = rb_define_class_under(m, "V8OutOfMemoryError", c);
1534
+ runtime_error = rb_define_class_under(m, "RuntimeError", c);
1535
+ internal_error = rb_define_class_under(m, "InternalError", c);
1536
+ terminated_error = rb_define_class_under(m, "ScriptTerminatedError", c);
1537
+
1538
+ c = context_class = rb_define_class_under(m, "Context", rb_cObject);
1539
+ rb_define_method(c, "initialize", context_initialize, -1);
1540
+ rb_define_method(c, "attach", context_attach, 2);
1541
+ rb_define_method(c, "dispose", context_dispose, 0);
1542
+ rb_define_method(c, "stop", context_stop, 0);
1543
+ rb_define_method(c, "call", context_call, -1);
1544
+ rb_define_method(c, "eval", context_eval, -1);
1545
+ rb_define_method(c, "heap_stats", context_heap_stats, 0);
1546
+ rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
1547
+ rb_define_method(c, "pump_message_loop", context_pump_message_loop, 0);
1548
+ rb_define_method(c, "idle_notification", context_idle_notification, 1);
1549
+ rb_define_method(c, "low_memory_notification", context_low_memory_notification, 0);
1550
+ rb_define_alloc_func(c, context_alloc);
1551
+
1552
+ c = snapshot_class = rb_define_class_under(m, "Snapshot", rb_cObject);
1553
+ rb_define_method(c, "initialize", snapshot_initialize, -1);
1554
+ rb_define_method(c, "warmup!", snapshot_warmup, 1);
1555
+ rb_define_method(c, "dump", snapshot_dump, 0);
1556
+ rb_define_method(c, "size", snapshot_size0, 0);
1557
+ rb_define_alloc_func(c, snapshot_alloc);
1558
+
1559
+ c = rb_define_class_under(m, "Platform", rb_cObject);
1560
+ rb_define_singleton_method(c, "set_flags!", platform_set_flags, -1);
1561
+
1562
+ date_time_class = Qnil; // lazy init
1563
+ js_function_class = rb_define_class_under(m, "JavaScriptFunction", rb_cObject);
1564
+ }