mini_racer 0.21.4 → 0.22.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 +4 -4
- data/CHANGELOG +6 -0
- data/README.md +34 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +45 -12
- data/ext/mini_racer_extension/mini_racer_v8.cc +180 -17
- data/ext/mini_racer_extension/mini_racer_v8.h +5 -1
- data/lib/mini_racer/shared.rb +151 -111
- data/lib/mini_racer/truffleruby.rb +89 -66
- data/lib/mini_racer/version.rb +1 -1
- data/lib/mini_racer.rb +17 -9
- metadata +33 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a20d79f246e2bd3f9aa86cdf19c83413d2b782b7738f0bfc13017227e2c20f7
|
|
4
|
+
data.tar.gz: b1c6d7b75c8bbc7520f0722ed255d042e70e4e606bb627a59926523135640921
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c828f6078e1d6f48503bf9870cab6ced783e853decf679c0534735d7c3a9a8d6efb3c06ed7528f88f1ea8f90ddf5e2c8d50c73e89d8e7672d5b03ad7b85e3ef
|
|
7
|
+
data.tar.gz: 62eb803f2a6aea12b7a80cc6385858631a60dadb61c2eb409e1f5ecf79cfa969f3d9350519377c0a169bf0ffd15987c06635da2d3aaa84f3017183d51b5216bb
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
- 0.22.0 - 12-08-2026
|
|
2
|
+
- Add `Context#call_await` and `Context#eval_await`: like `call`/`eval` but block until a returned Promise settles and return the settled value; rejections raise `MiniRacer::RuntimeError`
|
|
3
|
+
- Fix a `call` or `eval` made from a Ruby callback taking the timeout or `stop` meant for the evaluation around it, which then kept running
|
|
4
|
+
- Fix a timeout that fires just after its own evaluation ends, where it would stop the next evaluation instead
|
|
5
|
+
- Fix a race introduced in 0.21.4 where a request sent right after a nested dispatch (e.g. `perform_microtask_checkpoint` or a nested `call` from an attached callback) could be dropped, deadlocking the context
|
|
6
|
+
|
|
1
7
|
- 0.21.4 - 24-06-2026
|
|
2
8
|
- Fix stale V8 termination state after interrupts/timeouts so contexts remain usable after cancelled evaluations
|
|
3
9
|
- Let Ruby interrupts wake MiniRacer calls without immediately terminating V8, allowing signal traps and nested callbacks to unwind safely
|
data/README.md
CHANGED
|
@@ -348,6 +348,40 @@ Performance is slightly better than running `context.eval("hello('George')")` si
|
|
|
348
348
|
* compilation of eval'd string is avoided
|
|
349
349
|
* function arguments don't need to be converted to JSON
|
|
350
350
|
|
|
351
|
+
### Promises: call_await and eval_await
|
|
352
|
+
|
|
353
|
+
`call_await` and `eval_await` work like `call` and `eval`, but when the result is a
|
|
354
|
+
Promise they block until it settles and return the settled value. A rejected
|
|
355
|
+
promise raises `MiniRacer::RuntimeError`, just like a synchronous `throw`:
|
|
356
|
+
|
|
357
|
+
```ruby
|
|
358
|
+
context = MiniRacer::Context.new
|
|
359
|
+
context.eval("async function f(x) { await Promise.resolve(); return x * 2 }")
|
|
360
|
+
context.call_await("f", 21)
|
|
361
|
+
# => 42
|
|
362
|
+
|
|
363
|
+
context.eval_await("(async () => 6 * 7)()")
|
|
364
|
+
# => 42
|
|
365
|
+
|
|
366
|
+
context.eval("async function boom() { throw new Error('kaboom') }")
|
|
367
|
+
context.call_await("boom")
|
|
368
|
+
# => raises MiniRacer::RuntimeError (Error: kaboom)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Non-Promise results pass through unchanged, so `call_await` is a drop-in
|
|
372
|
+
superset of `call` (same for `eval_await`/`eval`).
|
|
373
|
+
|
|
374
|
+
A promise that never settles blocks forever, just like an infinite loop. The
|
|
375
|
+
`timeout:` option and `Context#stop` both interrupt it, raising
|
|
376
|
+
`MiniRacer::ScriptTerminatedError`.
|
|
377
|
+
|
|
378
|
+
Calling `call_await` or `eval_await` recursively on the same context from an
|
|
379
|
+
attached Ruby callback is not supported and raises `MiniRacer::RuntimeError`.
|
|
380
|
+
V8 cannot run the nested microtask checkpoint needed to settle such a call.
|
|
381
|
+
Synchronous nested `call` and `eval` remain supported.
|
|
382
|
+
|
|
383
|
+
`call_await` and `eval_await` are not currently supported on TruffleRuby.
|
|
384
|
+
|
|
351
385
|
### Microtask checkpoints
|
|
352
386
|
|
|
353
387
|
V8 drains its microtask queue (e.g. callbacks queued via `Promise.resolve().then(...)`) automatically when script execution returns to the embedder, so most code "just works":
|
|
@@ -152,7 +152,8 @@ typedef struct Context
|
|
|
152
152
|
struct {
|
|
153
153
|
pthread_mutex_t mtx;
|
|
154
154
|
pthread_cond_t cv;
|
|
155
|
-
int
|
|
155
|
+
int active; // dispatch thread only
|
|
156
|
+
int cancel; // protected by |mtx|
|
|
156
157
|
} wd; // watchdog
|
|
157
158
|
Barrier early_init, late_init;
|
|
158
159
|
} Context;
|
|
@@ -772,7 +773,7 @@ static void *v8_watchdog(void *arg)
|
|
|
772
773
|
if (c->wd.cancel)
|
|
773
774
|
break;
|
|
774
775
|
if (deadline_exceeded(deadline)) {
|
|
775
|
-
|
|
776
|
+
v8_terminate_watchdog(c->pst);
|
|
776
777
|
break;
|
|
777
778
|
}
|
|
778
779
|
}
|
|
@@ -786,8 +787,14 @@ static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
|
|
|
786
787
|
pthread_t thr;
|
|
787
788
|
int r;
|
|
788
789
|
|
|
789
|
-
|
|
790
|
-
|
|
790
|
+
if (c->timeout <= 0 || c->wd.active) {
|
|
791
|
+
func(c->pst, p, n);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
c->wd.active = 1;
|
|
795
|
+
r = pthread_create(&thr, NULL, v8_watchdog, c);
|
|
796
|
+
if (r) {
|
|
797
|
+
c->wd.active = 0;
|
|
791
798
|
fprintf(stderr, "mini_racer: watchdog: pthread_create: %s\n", strerror(r));
|
|
792
799
|
fflush(stderr);
|
|
793
800
|
}
|
|
@@ -799,7 +806,9 @@ static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
|
|
|
799
806
|
pthread_cond_signal(&c->wd.cv);
|
|
800
807
|
pthread_mutex_unlock(&c->wd.mtx);
|
|
801
808
|
pthread_join(thr, NULL);
|
|
809
|
+
v8_cancel_watchdog_termination(c->pst);
|
|
802
810
|
c->wd.cancel = 0;
|
|
811
|
+
c->wd.active = 0;
|
|
803
812
|
}
|
|
804
813
|
|
|
805
814
|
static void dispatch1(Context *c, const uint8_t *p, size_t n)
|
|
@@ -810,7 +819,9 @@ static void dispatch1(Context *c, const uint8_t *p, size_t n)
|
|
|
810
819
|
switch (*p) {
|
|
811
820
|
case 'A': return v8_attach(c->pst, p+1, n-1);
|
|
812
821
|
case 'C': return v8_timedwait(c, p+1, n-1, v8_call);
|
|
822
|
+
case 'D': return v8_timedwait(c, p+1, n-1, v8_call_await);
|
|
813
823
|
case 'E': return v8_timedwait(c, p+1, n-1, v8_eval);
|
|
824
|
+
case 'F': return v8_timedwait(c, p+1, n-1, v8_eval_await);
|
|
814
825
|
case 'H': return v8_heap_snapshot(c->pst);
|
|
815
826
|
case 'M': return v8_perform_microtask_checkpoint(c->pst);
|
|
816
827
|
case 'P': return v8_pump_message_loop(c->pst);
|
|
@@ -888,12 +899,12 @@ void v8_dispatch(Context *c)
|
|
|
888
899
|
pthread_mutex_unlock(&c->mtx);
|
|
889
900
|
}
|
|
890
901
|
|
|
891
|
-
// only called when inside v8_call, v8_eval
|
|
902
|
+
// only called when inside v8_call, v8_eval (and their await variants),
|
|
903
|
+
// or v8_pump_message_loop
|
|
892
904
|
void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
|
|
893
905
|
{
|
|
894
906
|
pthread_mutex_lock(&c->mtx);
|
|
895
907
|
buf_reset(&c->v8_req);
|
|
896
|
-
buf_reset(&c->req);
|
|
897
908
|
if (c->res.len)
|
|
898
909
|
c->res_ready = 1;
|
|
899
910
|
pthread_cond_signal(&c->cv);
|
|
@@ -1655,7 +1666,7 @@ static VALUE context_stop(VALUE self)
|
|
|
1655
1666
|
return Qnil;
|
|
1656
1667
|
}
|
|
1657
1668
|
|
|
1658
|
-
static VALUE
|
|
1669
|
+
static VALUE context_call_common(int argc, VALUE *argv, VALUE self, char op)
|
|
1659
1670
|
{
|
|
1660
1671
|
VALUE name, args;
|
|
1661
1672
|
VALUE a, e;
|
|
@@ -1666,8 +1677,8 @@ static VALUE context_call(int argc, VALUE *argv, VALUE self)
|
|
|
1666
1677
|
rb_scan_args(argc, argv, "1*", &name, &args);
|
|
1667
1678
|
Check_Type(name, T_STRING);
|
|
1668
1679
|
rb_ary_unshift(args, name);
|
|
1669
|
-
// request is (C)all, [name, args...] array
|
|
1670
|
-
ser_init1(&s,
|
|
1680
|
+
// request is (C)all or (D) call_await, [name, args...] array
|
|
1681
|
+
ser_init1(&s, op);
|
|
1671
1682
|
if (serialize(&s, args)) {
|
|
1672
1683
|
ser_reset(&s);
|
|
1673
1684
|
rb_raise(runtime_error, "Context.call: %s", s.err);
|
|
@@ -1679,7 +1690,17 @@ static VALUE context_call(int argc, VALUE *argv, VALUE self)
|
|
|
1679
1690
|
return rb_ary_pop(a);
|
|
1680
1691
|
}
|
|
1681
1692
|
|
|
1682
|
-
static VALUE
|
|
1693
|
+
static VALUE context_call(int argc, VALUE *argv, VALUE self)
|
|
1694
|
+
{
|
|
1695
|
+
return context_call_common(argc, argv, self, 'C');
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
static VALUE context_call_await(int argc, VALUE *argv, VALUE self)
|
|
1699
|
+
{
|
|
1700
|
+
return context_call_common(argc, argv, self, 'D');
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
static VALUE context_eval_common(int argc, VALUE *argv, VALUE self, char op)
|
|
1683
1704
|
{
|
|
1684
1705
|
VALUE a, e, source, filename, kwargs;
|
|
1685
1706
|
Context *c;
|
|
@@ -1694,8 +1715,8 @@ static VALUE context_eval(int argc, VALUE *argv, VALUE self)
|
|
|
1694
1715
|
if (NIL_P(filename))
|
|
1695
1716
|
filename = rb_str_new_cstr("<eval>");
|
|
1696
1717
|
Check_Type(filename, T_STRING);
|
|
1697
|
-
// request is (E)val, [filename, source] array
|
|
1698
|
-
ser_init1(&s,
|
|
1718
|
+
// request is (E)val or (F) eval_await, [filename, source] array
|
|
1719
|
+
ser_init1(&s, op);
|
|
1699
1720
|
ser_array_begin(&s, 2);
|
|
1700
1721
|
add_string(&s, filename);
|
|
1701
1722
|
add_string(&s, source);
|
|
@@ -1707,6 +1728,16 @@ static VALUE context_eval(int argc, VALUE *argv, VALUE self)
|
|
|
1707
1728
|
return rb_ary_pop(a);
|
|
1708
1729
|
}
|
|
1709
1730
|
|
|
1731
|
+
static VALUE context_eval(int argc, VALUE *argv, VALUE self)
|
|
1732
|
+
{
|
|
1733
|
+
return context_eval_common(argc, argv, self, 'E');
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
static VALUE context_eval_await(int argc, VALUE *argv, VALUE self)
|
|
1737
|
+
{
|
|
1738
|
+
return context_eval_common(argc, argv, self, 'F');
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1710
1741
|
static VALUE context_heap_stats(VALUE self)
|
|
1711
1742
|
{
|
|
1712
1743
|
VALUE a, h, k, v;
|
|
@@ -2147,7 +2178,9 @@ void Init_mini_racer_extension(void)
|
|
|
2147
2178
|
rb_define_method(c, "dispose", context_dispose, 0);
|
|
2148
2179
|
rb_define_method(c, "stop", context_stop, 0);
|
|
2149
2180
|
rb_define_method(c, "call", context_call, -1);
|
|
2181
|
+
rb_define_method(c, "call_await", context_call_await, -1);
|
|
2150
2182
|
rb_define_method(c, "eval", context_eval, -1);
|
|
2183
|
+
rb_define_method(c, "eval_await", context_eval_await, -1);
|
|
2151
2184
|
rb_define_method(c, "heap_stats", context_heap_stats, 0);
|
|
2152
2185
|
rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
|
|
2153
2186
|
rb_define_method(c, "perform_microtask_checkpoint", context_perform_microtask_checkpoint, 0);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include "v8-profiler.h"
|
|
3
3
|
#include "libplatform/libplatform.h"
|
|
4
4
|
#include "mini_racer_v8.h"
|
|
5
|
+
#include <atomic>
|
|
5
6
|
#include <memory>
|
|
6
7
|
#include <vector>
|
|
7
8
|
#include <cassert>
|
|
@@ -68,6 +69,12 @@ struct Callback
|
|
|
68
69
|
int32_t id;
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
enum : unsigned
|
|
73
|
+
{
|
|
74
|
+
NON_WATCHDOG_TERMINATION = 1,
|
|
75
|
+
WATCHDOG_TERMINATION = 2,
|
|
76
|
+
};
|
|
77
|
+
|
|
71
78
|
// NOTE: do *not* use thread_locals to store state. In single-threaded
|
|
72
79
|
// mode, V8 runs on the same thread as Ruby and the Ruby runtime clobbers
|
|
73
80
|
// thread-locals when it context-switches threads. Ruby 3.4.0 has a new
|
|
@@ -91,6 +98,14 @@ struct State
|
|
|
91
98
|
Context *ruby_context;
|
|
92
99
|
int64_t max_memory;
|
|
93
100
|
int err_reason;
|
|
101
|
+
// TerminateExecution() while idle doesn't make IsExecutionTerminating() true
|
|
102
|
+
std::atomic<unsigned> terminate_requested;
|
|
103
|
+
// Armed before checking terminate_requested so a concurrent terminator
|
|
104
|
+
// either gets observed or posts a task. Uses sequential consistency.
|
|
105
|
+
std::atomic<bool> waiting_for_task;
|
|
106
|
+
// Tracks reentrant call/eval dispatches so nested await calls can fail
|
|
107
|
+
// instead of deadlocking and nested pumps preserve outer termination.
|
|
108
|
+
int javascript_call_depth;
|
|
94
109
|
bool verbose_exceptions;
|
|
95
110
|
std::vector<Callback*> callbacks;
|
|
96
111
|
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator;
|
|
@@ -416,6 +431,7 @@ void v8_gc_callback(v8::Isolate*, v8::GCType, v8::GCCallbackFlags, void *data)
|
|
|
416
431
|
int64_t used_heap_size = static_cast<int64_t>(s.used_heap_size());
|
|
417
432
|
if (used_heap_size > st.max_memory) {
|
|
418
433
|
st.err_reason = MEMORY_ERROR;
|
|
434
|
+
st.terminate_requested.fetch_or(NON_WATCHDOG_TERMINATION);
|
|
419
435
|
st.isolate->TerminateExecution();
|
|
420
436
|
}
|
|
421
437
|
}
|
|
@@ -586,8 +602,76 @@ fail:
|
|
|
586
602
|
reply_retry(st, err);
|
|
587
603
|
}
|
|
588
604
|
|
|
605
|
+
struct JavascriptCallScope
|
|
606
|
+
{
|
|
607
|
+
int& depth;
|
|
608
|
+
|
|
609
|
+
explicit JavascriptCallScope(int& depth) : depth(depth) { depth++; }
|
|
610
|
+
~JavascriptCallScope() { depth--; }
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
void throw_nested_await_call(State& st)
|
|
614
|
+
{
|
|
615
|
+
// V8 does not run microtask checkpoints recursively. A nested await call
|
|
616
|
+
// from an attached Ruby callback can therefore deadlock, so reject it
|
|
617
|
+
// before entering JavaScript.
|
|
618
|
+
auto message = v8::String::NewFromUtf8Literal(
|
|
619
|
+
st.isolate, "nested call_await/eval_await is not supported");
|
|
620
|
+
st.isolate->ThrowException(v8::Exception::Error(message));
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// awaits |*result| if it's a promise; false means an exception is pending
|
|
624
|
+
bool await_promise(State& st, v8::Local<v8::Value> *result)
|
|
625
|
+
{
|
|
626
|
+
if (!(*result)->IsPromise()) return true;
|
|
627
|
+
auto promise = result->As<v8::Promise>();
|
|
628
|
+
for (;;) {
|
|
629
|
+
v8::MicrotasksScope::PerformCheckpoint(st.isolate);
|
|
630
|
+
switch (promise->State()) {
|
|
631
|
+
case v8::Promise::kFulfilled:
|
|
632
|
+
*result = promise->Result();
|
|
633
|
+
return true;
|
|
634
|
+
case v8::Promise::kRejected:
|
|
635
|
+
st.isolate->ThrowException(promise->Result());
|
|
636
|
+
return false;
|
|
637
|
+
case v8::Promise::kPending:
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
st.waiting_for_task.store(true);
|
|
641
|
+
if (st.terminate_requested.load() || st.isolate->IsExecutionTerminating()) {
|
|
642
|
+
st.waiting_for_task.store(false);
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
// blocks until the next task; a concurrent v8_terminate_execution
|
|
646
|
+
// observes waiting_for_task and posts one to end the wait
|
|
647
|
+
v8::platform::PumpMessageLoop(
|
|
648
|
+
platform, st.isolate,
|
|
649
|
+
v8::platform::MessageLoopBehavior::kWaitForWork);
|
|
650
|
+
st.waiting_for_task.store(false);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Nested calls report termination but leave it pending for the enclosing call.
|
|
655
|
+
// Outermost calls consume it as before.
|
|
656
|
+
bool suspend_termination(State& st, bool nested, int& cause)
|
|
657
|
+
{
|
|
658
|
+
unsigned requests = nested ? st.terminate_requested.load()
|
|
659
|
+
: st.terminate_requested.exchange(0);
|
|
660
|
+
if (!requests && !st.isolate->IsExecutionTerminating()) return false;
|
|
661
|
+
st.isolate->CancelTerminateExecution();
|
|
662
|
+
cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
|
|
663
|
+
if (!nested) st.err_reason = NO_ERROR;
|
|
664
|
+
return nested;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
void restore_termination(State& st, bool suspended)
|
|
668
|
+
{
|
|
669
|
+
if (suspended)
|
|
670
|
+
st.isolate->TerminateExecution();
|
|
671
|
+
}
|
|
672
|
+
|
|
589
673
|
// response is errback [result, err] array
|
|
590
|
-
|
|
674
|
+
void v8_call_impl(State *pst, const uint8_t *p, size_t n, bool await)
|
|
591
675
|
{
|
|
592
676
|
State& st = *pst;
|
|
593
677
|
v8::TryCatch try_catch(st.isolate);
|
|
@@ -598,6 +682,14 @@ extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
|
|
|
598
682
|
des.ReadHeader(st.context).Check();
|
|
599
683
|
v8::Local<v8::Value> result;
|
|
600
684
|
int cause = INTERNAL_ERROR;
|
|
685
|
+
bool preserve_termination = false;
|
|
686
|
+
bool nested = st.javascript_call_depth > 0;
|
|
687
|
+
JavascriptCallScope call_scope(st.javascript_call_depth);
|
|
688
|
+
if (await && nested) {
|
|
689
|
+
throw_nested_await_call(st);
|
|
690
|
+
cause = RUNTIME_ERROR;
|
|
691
|
+
goto fail;
|
|
692
|
+
}
|
|
601
693
|
{
|
|
602
694
|
v8::Local<v8::Value> request_v;
|
|
603
695
|
if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
|
|
@@ -645,16 +737,16 @@ extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
|
|
|
645
737
|
auto maybe_result_v = function->Call(st.context, obj, args.size(), args.data());
|
|
646
738
|
v8::Local<v8::Value> result_v;
|
|
647
739
|
if (!maybe_result_v.ToLocal(&result_v)) goto fail;
|
|
740
|
+
if (await && !await_promise(st, &result_v)) goto fail;
|
|
648
741
|
result = sanitize(st, result_v);
|
|
649
742
|
}
|
|
650
743
|
cause = NO_ERROR;
|
|
651
744
|
fail:
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
745
|
+
preserve_termination = suspend_termination(st, nested, cause);
|
|
746
|
+
if (bubble_up_ruby_exception(st, &try_catch)) {
|
|
747
|
+
restore_termination(st, preserve_termination);
|
|
748
|
+
return;
|
|
656
749
|
}
|
|
657
|
-
if (bubble_up_ruby_exception(st, &try_catch)) return;
|
|
658
750
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
659
751
|
if (cause) result = v8::Undefined(st.isolate);
|
|
660
752
|
auto err = to_error(st, &try_catch, cause);
|
|
@@ -662,10 +754,21 @@ fail:
|
|
|
662
754
|
assert(try_catch.HasCaught());
|
|
663
755
|
goto fail; // retry; can be termination exception
|
|
664
756
|
}
|
|
757
|
+
restore_termination(st, preserve_termination);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
|
|
761
|
+
{
|
|
762
|
+
v8_call_impl(pst, p, n, false);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
extern "C" void v8_call_await(State *pst, const uint8_t *p, size_t n)
|
|
766
|
+
{
|
|
767
|
+
v8_call_impl(pst, p, n, true);
|
|
665
768
|
}
|
|
666
769
|
|
|
667
770
|
// response is errback [result, err] array
|
|
668
|
-
|
|
771
|
+
void v8_eval_impl(State *pst, const uint8_t *p, size_t n, bool await)
|
|
669
772
|
{
|
|
670
773
|
State& st = *pst;
|
|
671
774
|
v8::TryCatch try_catch(st.isolate);
|
|
@@ -675,6 +778,14 @@ extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n)
|
|
|
675
778
|
des.ReadHeader(st.context).Check();
|
|
676
779
|
v8::Local<v8::Value> result;
|
|
677
780
|
int cause = INTERNAL_ERROR;
|
|
781
|
+
bool preserve_termination = false;
|
|
782
|
+
bool nested = st.javascript_call_depth > 0;
|
|
783
|
+
JavascriptCallScope call_scope(st.javascript_call_depth);
|
|
784
|
+
if (await && nested) {
|
|
785
|
+
throw_nested_await_call(st);
|
|
786
|
+
cause = RUNTIME_ERROR;
|
|
787
|
+
goto fail;
|
|
788
|
+
}
|
|
678
789
|
{
|
|
679
790
|
v8::Local<v8::Value> request_v;
|
|
680
791
|
if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
|
|
@@ -694,16 +805,16 @@ extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n)
|
|
|
694
805
|
cause = RUNTIME_ERROR;
|
|
695
806
|
auto maybe_result_v = script->Run(st.context);
|
|
696
807
|
if (!maybe_result_v.ToLocal(&result_v)) goto fail;
|
|
808
|
+
if (await && !await_promise(st, &result_v)) goto fail;
|
|
697
809
|
result = sanitize(st, result_v);
|
|
698
810
|
}
|
|
699
811
|
cause = NO_ERROR;
|
|
700
812
|
fail:
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
813
|
+
preserve_termination = suspend_termination(st, nested, cause);
|
|
814
|
+
if (bubble_up_ruby_exception(st, &try_catch)) {
|
|
815
|
+
restore_termination(st, preserve_termination);
|
|
816
|
+
return;
|
|
705
817
|
}
|
|
706
|
-
if (bubble_up_ruby_exception(st, &try_catch)) return;
|
|
707
818
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
708
819
|
if (cause) result = v8::Undefined(st.isolate);
|
|
709
820
|
auto err = to_error(st, &try_catch, cause);
|
|
@@ -711,6 +822,17 @@ fail:
|
|
|
711
822
|
assert(try_catch.HasCaught());
|
|
712
823
|
goto fail; // retry; can be termination exception
|
|
713
824
|
}
|
|
825
|
+
restore_termination(st, preserve_termination);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n)
|
|
829
|
+
{
|
|
830
|
+
v8_eval_impl(pst, p, n, false);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
extern "C" void v8_eval_await(State *pst, const uint8_t *p, size_t n)
|
|
834
|
+
{
|
|
835
|
+
v8_eval_impl(pst, p, n, true);
|
|
714
836
|
}
|
|
715
837
|
|
|
716
838
|
extern "C" void v8_heap_stats(State *pst)
|
|
@@ -775,6 +897,7 @@ extern "C" void v8_perform_microtask_checkpoint(State *pst)
|
|
|
775
897
|
// Leave any termination active so the enclosing v8_call/v8_eval frame
|
|
776
898
|
// surfaces OOM (set by v8_gc_callback) or watchdog termination to Ruby.
|
|
777
899
|
State& st = *pst;
|
|
900
|
+
JavascriptCallScope call_scope(st.javascript_call_depth);
|
|
778
901
|
v8::TryCatch try_catch(st.isolate);
|
|
779
902
|
try_catch.SetVerbose(st.verbose_exceptions);
|
|
780
903
|
v8::HandleScope handle_scope(st.isolate);
|
|
@@ -785,6 +908,8 @@ extern "C" void v8_perform_microtask_checkpoint(State *pst)
|
|
|
785
908
|
extern "C" void v8_pump_message_loop(State *pst)
|
|
786
909
|
{
|
|
787
910
|
State& st = *pst;
|
|
911
|
+
bool nested = st.javascript_call_depth > 0;
|
|
912
|
+
JavascriptCallScope call_scope(st.javascript_call_depth);
|
|
788
913
|
v8::TryCatch try_catch(st.isolate);
|
|
789
914
|
try_catch.SetVerbose(st.verbose_exceptions);
|
|
790
915
|
v8::HandleScope handle_scope(st.isolate);
|
|
@@ -800,7 +925,10 @@ extern "C" void v8_pump_message_loop(State *pst)
|
|
|
800
925
|
if (try_catch.HasCaught()) goto fail;
|
|
801
926
|
}
|
|
802
927
|
fail:
|
|
803
|
-
|
|
928
|
+
// A nested pump must leave termination active for the enclosing call/eval.
|
|
929
|
+
if (!nested &&
|
|
930
|
+
(st.terminate_requested.exchange(0) ||
|
|
931
|
+
st.isolate->IsExecutionTerminating())) {
|
|
804
932
|
st.isolate->CancelTerminateExecution();
|
|
805
933
|
st.err_reason = NO_ERROR;
|
|
806
934
|
}
|
|
@@ -914,7 +1042,8 @@ extern "C" void v8_snapshot(State *pst, const uint8_t *p, size_t n)
|
|
|
914
1042
|
}
|
|
915
1043
|
cause = NO_ERROR;
|
|
916
1044
|
fail:
|
|
917
|
-
if (st.
|
|
1045
|
+
if (st.terminate_requested.exchange(0) ||
|
|
1046
|
+
st.isolate->IsExecutionTerminating()) {
|
|
918
1047
|
st.isolate->CancelTerminateExecution();
|
|
919
1048
|
cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
|
|
920
1049
|
st.err_reason = NO_ERROR;
|
|
@@ -984,7 +1113,8 @@ extern "C" void v8_warmup(State *pst, const uint8_t *p, size_t n)
|
|
|
984
1113
|
}
|
|
985
1114
|
cause = NO_ERROR;
|
|
986
1115
|
fail:
|
|
987
|
-
if (st.
|
|
1116
|
+
if (st.terminate_requested.exchange(0) ||
|
|
1117
|
+
st.isolate->IsExecutionTerminating()) {
|
|
988
1118
|
st.isolate->CancelTerminateExecution();
|
|
989
1119
|
cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
|
|
990
1120
|
st.err_reason = NO_ERROR;
|
|
@@ -1008,10 +1138,42 @@ extern "C" void v8_low_memory_notification(State *pst)
|
|
|
1008
1138
|
pst->isolate->LowMemoryNotification();
|
|
1009
1139
|
}
|
|
1010
1140
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1141
|
+
struct WakeupTask : public v8::Task
|
|
1142
|
+
{
|
|
1143
|
+
void Run() final {}
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
// called from ruby or watchdog thread
|
|
1147
|
+
void request_termination(State *pst, unsigned request)
|
|
1013
1148
|
{
|
|
1149
|
+
pst->terminate_requested.fetch_or(request);
|
|
1014
1150
|
pst->isolate->TerminateExecution();
|
|
1151
|
+
// Wake await_promise if it may be blocked. A racing early return can leave
|
|
1152
|
+
// one harmless no-op task queued.
|
|
1153
|
+
if (pst->waiting_for_task.exchange(false)) {
|
|
1154
|
+
platform->GetForegroundTaskRunner(pst->isolate)
|
|
1155
|
+
->PostTask(std::make_unique<WakeupTask>());
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
extern "C" void v8_terminate_execution(State *pst)
|
|
1160
|
+
{
|
|
1161
|
+
request_termination(pst, NON_WATCHDOG_TERMINATION);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
extern "C" void v8_terminate_watchdog(State *pst)
|
|
1165
|
+
{
|
|
1166
|
+
request_termination(pst, WATCHDOG_TERMINATION);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
extern "C" void v8_cancel_watchdog_termination(State *pst)
|
|
1170
|
+
{
|
|
1171
|
+
unsigned requests = pst->terminate_requested.fetch_and(~WATCHDOG_TERMINATION);
|
|
1172
|
+
if (requests == WATCHDOG_TERMINATION) {
|
|
1173
|
+
pst->isolate->CancelTerminateExecution();
|
|
1174
|
+
if (pst->terminate_requested.load())
|
|
1175
|
+
pst->isolate->TerminateExecution();
|
|
1176
|
+
}
|
|
1015
1177
|
}
|
|
1016
1178
|
|
|
1017
1179
|
// called from ruby thread
|
|
@@ -1019,6 +1181,7 @@ extern "C" void v8_cancel_terminate_execution(State *pst)
|
|
|
1019
1181
|
{
|
|
1020
1182
|
// TerminateExecution can race with V8 completing and queue a termination
|
|
1021
1183
|
// for the next entry without IsExecutionTerminating() becoming true.
|
|
1184
|
+
pst->terminate_requested.store(0);
|
|
1022
1185
|
pst->isolate->CancelTerminateExecution();
|
|
1023
1186
|
}
|
|
1024
1187
|
|
|
@@ -39,7 +39,9 @@ struct State *v8_thread_init(struct Context *c, const uint8_t *snapshot_buf,
|
|
|
39
39
|
int verbose_exceptions); // calls v8_thread_main
|
|
40
40
|
void v8_attach(struct State *pst, const uint8_t *p, size_t n);
|
|
41
41
|
void v8_call(struct State *pst, const uint8_t *p, size_t n);
|
|
42
|
+
void v8_call_await(struct State *pst, const uint8_t *p, size_t n);
|
|
42
43
|
void v8_eval(struct State *pst, const uint8_t *p, size_t n);
|
|
44
|
+
void v8_eval_await(struct State *pst, const uint8_t *p, size_t n);
|
|
43
45
|
void v8_heap_stats(struct State *pst);
|
|
44
46
|
void v8_heap_snapshot(struct State *pst);
|
|
45
47
|
void v8_perform_microtask_checkpoint(struct State *pst);
|
|
@@ -47,7 +49,9 @@ void v8_pump_message_loop(struct State *pst);
|
|
|
47
49
|
void v8_snapshot(struct State *pst, const uint8_t *p, size_t n);
|
|
48
50
|
void v8_warmup(struct State *pst, const uint8_t *p, size_t n);
|
|
49
51
|
void v8_low_memory_notification(struct State *pst);
|
|
50
|
-
void v8_terminate_execution(struct State *pst); // called from ruby
|
|
52
|
+
void v8_terminate_execution(struct State *pst); // called from ruby thread
|
|
53
|
+
void v8_terminate_watchdog(struct State *pst); // called from watchdog thread
|
|
54
|
+
void v8_cancel_watchdog_termination(struct State *pst); // called from v8 thread
|
|
51
55
|
void v8_cancel_terminate_execution(struct State *pst); // called from ruby thread
|
|
52
56
|
void v8_single_threaded_enter(struct State *pst, struct Context *c, void (*f)(struct Context *c));
|
|
53
57
|
void v8_single_threaded_dispose(struct State *pst);
|