mini_racer 0.21.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e370b2c6cdb60e5081d5f97840cff28489119f0225a3c9a74ed7f6859caf40e
4
- data.tar.gz: 7405ed87278cbaa1d155bc72f8c99d8e8c744df743252eb4128b9ae9ff899d07
3
+ metadata.gz: 7a20d79f246e2bd3f9aa86cdf19c83413d2b782b7738f0bfc13017227e2c20f7
4
+ data.tar.gz: b1c6d7b75c8bbc7520f0722ed255d042e70e4e606bb627a59926523135640921
5
5
  SHA512:
6
- metadata.gz: 3c9f53c6a9052cca6a5129d023ecece59613c40c17f4c7666c728f8f6327200e34d707557ea4dcd9b626aed332b327c6c3e3f90e365c630adbec0b2d02c4b1c1
7
- data.tar.gz: 4dc0348a006f7620c4c9688c0b877f2bd9500b7ece37fba4fa6e868006af1517e4279537693fd87b2f18c2b0602067084e2332e2bf9fa862504ebaab4ddbbcfc
6
+ metadata.gz: 9c828f6078e1d6f48503bf9870cab6ced783e853decf679c0534735d7c3a9a8d6efb3c06ed7528f88f1ea8f90ddf5e2c8d50c73e89d8e7672d5b03ad7b85e3ef
7
+ data.tar.gz: 62eb803f2a6aea12b7a80cc6385858631a60dadb61c2eb409e1f5ecf79cfa969f3d9350519377c0a169bf0ffd15987c06635da2d3aaa84f3017183d51b5216bb
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
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
+
7
+ - 0.21.4 - 24-06-2026
8
+ - Fix stale V8 termination state after interrupts/timeouts so contexts remain usable after cancelled evaluations
9
+ - Let Ruby interrupts wake MiniRacer calls without immediately terminating V8, allowing signal traps and nested callbacks to unwind safely
10
+ - Add benchmark suite covering eval, serialization/deserialization, and transpilation workloads
11
+
1
12
  - 0.21.3 - 18-06-2026
2
13
  - Fix `:single_threaded` contexts inherited across `fork` by recovering idle reusable native runners in the child process without falling back to per-dispatch native thread spawning
3
14
  - Avoid intermittent heap corruption during `:single_threaded` context finalization, especially when forked children exit normally after touching inherited contexts
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":
@@ -137,6 +137,8 @@ typedef struct Context
137
137
  VALUE procs; // array of js -> ruby callbacks
138
138
  VALUE exception; // pending exception or Qnil
139
139
  Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
140
+ Buf v8_req; // stable v8-side copy of a request returned by v8_roundtrip
141
+ int res_ready; // protected by |mtx|; response may be filled before ready
140
142
  Buf snapshot;
141
143
  pthread_t single_threaded_thr;
142
144
  pid_t single_threaded_pid;
@@ -150,7 +152,8 @@ typedef struct Context
150
152
  struct {
151
153
  pthread_mutex_t mtx;
152
154
  pthread_cond_t cv;
153
- int cancel;
155
+ int active; // dispatch thread only
156
+ int cancel; // protected by |mtx|
154
157
  } wd; // watchdog
155
158
  Barrier early_init, late_init;
156
159
  } Context;
@@ -230,6 +233,8 @@ struct rendezvous_nogvl
230
233
  Context *context;
231
234
  Buf *req, *res;
232
235
  atomic_int active;
236
+ atomic_int interrupted;
237
+ int started, finished, has_rr_mtx;
233
238
  };
234
239
 
235
240
  struct rendezvous_des
@@ -768,7 +773,7 @@ static void *v8_watchdog(void *arg)
768
773
  if (c->wd.cancel)
769
774
  break;
770
775
  if (deadline_exceeded(deadline)) {
771
- v8_terminate_execution(c->pst);
776
+ v8_terminate_watchdog(c->pst);
772
777
  break;
773
778
  }
774
779
  }
@@ -782,8 +787,14 @@ static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
782
787
  pthread_t thr;
783
788
  int r;
784
789
 
785
- r = -1;
786
- if (c->timeout > 0 && (r = pthread_create(&thr, NULL, v8_watchdog, c))) {
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;
787
798
  fprintf(stderr, "mini_racer: watchdog: pthread_create: %s\n", strerror(r));
788
799
  fflush(stderr);
789
800
  }
@@ -795,7 +806,9 @@ static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
795
806
  pthread_cond_signal(&c->wd.cv);
796
807
  pthread_mutex_unlock(&c->wd.mtx);
797
808
  pthread_join(thr, NULL);
809
+ v8_cancel_watchdog_termination(c->pst);
798
810
  c->wd.cancel = 0;
811
+ c->wd.active = 0;
799
812
  }
800
813
 
801
814
  static void dispatch1(Context *c, const uint8_t *p, size_t n)
@@ -806,7 +819,9 @@ static void dispatch1(Context *c, const uint8_t *p, size_t n)
806
819
  switch (*p) {
807
820
  case 'A': return v8_attach(c->pst, p+1, n-1);
808
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);
809
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);
810
825
  case 'H': return v8_heap_snapshot(c->pst);
811
826
  case 'M': return v8_perform_microtask_checkpoint(c->pst);
812
827
  case 'P': return v8_pump_message_loop(c->pst);
@@ -822,11 +837,28 @@ static void dispatch1(Context *c, const uint8_t *p, size_t n)
822
837
  fflush(stderr);
823
838
  }
824
839
 
825
- static void dispatch(Context *c)
840
+ static void dispatch_buf(Context *c, Buf *req)
826
841
  {
842
+ Buf local_req;
843
+
844
+ // Called with Context.mtx held. Release it while V8 runs so an rb_nogvl
845
+ // UBF can wake the Ruby thread to process interrupts with
846
+ // rb_thread_check_ints(), without forcing termination first. Move the
847
+ // request out first so cancellation cannot free a buffer V8 is reading.
848
+ buf_move(req, &local_req);
827
849
  buf_reset(&c->res);
828
- dispatch1(c, c->req.buf, c->req.len);
829
- buf_reset(&c->req);
850
+ c->res_ready = 0;
851
+ pthread_mutex_unlock(&c->mtx);
852
+ dispatch1(c, local_req.buf, local_req.len);
853
+ pthread_mutex_lock(&c->mtx);
854
+ buf_reset(&local_req);
855
+ c->res_ready = 1;
856
+ pthread_cond_signal(&c->cv);
857
+ }
858
+
859
+ static void dispatch(Context *c)
860
+ {
861
+ dispatch_buf(c, &c->req);
830
862
  }
831
863
 
832
864
  // called by v8_isolate_and_context
@@ -859,19 +891,22 @@ void v8_thread_main(Context *c, struct State *pst)
859
891
  }
860
892
  }
861
893
 
862
- // called by v8_thread_main and from mini_racer_v8.cc,
863
- // in all cases with Context.mtx held
894
+ // called by v8_thread_main and from mini_racer_v8.cc
864
895
  void v8_dispatch(Context *c)
865
896
  {
866
- dispatch1(c, c->req.buf, c->req.len);
867
- buf_reset(&c->req);
897
+ pthread_mutex_lock(&c->mtx);
898
+ dispatch_buf(c, &c->v8_req);
899
+ pthread_mutex_unlock(&c->mtx);
868
900
  }
869
901
 
870
- // called from mini_racer_v8.cc with Context.mtx held
871
- // only called when inside v8_call, v8_eval, or v8_pump_message_loop
902
+ // only called when inside v8_call, v8_eval (and their await variants),
903
+ // or v8_pump_message_loop
872
904
  void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
873
905
  {
874
- buf_reset(&c->req);
906
+ pthread_mutex_lock(&c->mtx);
907
+ buf_reset(&c->v8_req);
908
+ if (c->res.len)
909
+ c->res_ready = 1;
875
910
  pthread_cond_signal(&c->cv);
876
911
  while (!c->req.len && !atomic_load(&c->quit))
877
912
  pthread_cond_wait(&c->cv, &c->mtx);
@@ -879,17 +914,22 @@ void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
879
914
  static const uint8_t disposed[] = "edisposed context";
880
915
  *p = disposed;
881
916
  *n = sizeof(disposed) - 1;
917
+ pthread_mutex_unlock(&c->mtx);
882
918
  return;
883
919
  }
884
920
  buf_reset(&c->res);
885
- *p = c->req.buf;
886
- *n = c->req.len;
921
+ c->res_ready = 0;
922
+ buf_move(&c->req, &c->v8_req);
923
+ *p = c->v8_req.buf;
924
+ *n = c->v8_req.len;
925
+ pthread_mutex_unlock(&c->mtx);
887
926
  }
888
927
 
889
- // called from mini_racer_v8.cc with Context.mtx held
890
928
  void v8_reply(Context *c, const uint8_t *p, size_t n)
891
929
  {
930
+ pthread_mutex_lock(&c->mtx);
892
931
  buf_put(&c->res, p, n);
932
+ pthread_mutex_unlock(&c->mtx);
893
933
  }
894
934
 
895
935
  static void v8_once_init(void)
@@ -1059,6 +1099,19 @@ static int single_threaded_runner_start(Context *c)
1059
1099
  return r;
1060
1100
  }
1061
1101
 
1102
+ static void rendezvous_release(struct rendezvous_nogvl *a)
1103
+ {
1104
+ Context *c;
1105
+
1106
+ atomic_store(&a->active, 0);
1107
+ if (!a->has_rr_mtx)
1108
+ return;
1109
+ c = a->context;
1110
+ c->depth--;
1111
+ a->has_rr_mtx = 0;
1112
+ pthread_mutex_unlock(&c->rr_mtx);
1113
+ }
1114
+
1062
1115
  static inline void *rendezvous_nogvl(void *arg)
1063
1116
  {
1064
1117
  struct rendezvous_nogvl *a;
@@ -1067,62 +1120,77 @@ static inline void *rendezvous_nogvl(void *arg)
1067
1120
 
1068
1121
  a = arg;
1069
1122
  c = a->context;
1070
- if (single_threaded && (r = single_threaded_recover_after_fork(c)))
1071
- return (void *)(intptr_t)r;
1072
- pthread_mutex_lock(&c->rr_mtx);
1073
- atomic_store(&a->active, 1);
1074
- if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
1075
- fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
1076
- fflush(stderr);
1123
+ if (!a->started) {
1124
+ if (single_threaded && (r = single_threaded_recover_after_fork(c)))
1125
+ return (void *)(intptr_t)r;
1126
+ pthread_mutex_lock(&c->rr_mtx);
1127
+ a->has_rr_mtx = 1;
1128
+ if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
1129
+ fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
1130
+ fflush(stderr);
1131
+ }
1132
+ c->depth++;
1133
+ a->started = 1;
1077
1134
  }
1078
- c->depth++;
1135
+
1079
1136
  next:
1137
+ atomic_store(&a->active, 1);
1080
1138
  pthread_mutex_lock(&c->mtx);
1081
1139
  if (atomic_load(&c->quit)) {
1082
1140
  buf_reset(a->req);
1083
1141
  pthread_mutex_unlock(&c->mtx);
1084
- c->depth--;
1085
- atomic_store(&a->active, 0);
1086
- pthread_mutex_unlock(&c->rr_mtx);
1142
+ a->finished = 1;
1143
+ rendezvous_release(a);
1087
1144
  return (void *)(intptr_t)ECANCELED;
1088
1145
  }
1089
- assert(c->req.len == 0);
1090
- assert(c->res.len == 0);
1091
- buf_move(a->req, &c->req); // v8 thread takes ownership of req
1092
- if (single_threaded) {
1093
- r = single_threaded_runner_start(c);
1094
- if (r) {
1095
- buf_move(&c->req, a->req);
1096
- pthread_mutex_unlock(&c->mtx);
1097
- c->depth--;
1098
- atomic_store(&a->active, 0);
1099
- pthread_mutex_unlock(&c->rr_mtx);
1100
- return (void *)(intptr_t)r;
1146
+ if (a->req->len) {
1147
+ assert(c->req.len == 0);
1148
+ assert(!c->res_ready);
1149
+ buf_move(a->req, &c->req); // v8 thread takes ownership of req
1150
+ if (single_threaded) {
1151
+ r = single_threaded_runner_start(c);
1152
+ if (r) {
1153
+ buf_move(&c->req, a->req);
1154
+ pthread_mutex_unlock(&c->mtx);
1155
+ a->finished = 1;
1156
+ rendezvous_release(a);
1157
+ return (void *)(intptr_t)r;
1158
+ }
1101
1159
  }
1102
1160
  pthread_cond_signal(&c->cv);
1103
- do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
1104
- } else {
1105
- pthread_cond_signal(&c->cv);
1106
- do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
1161
+ }
1162
+ while (!c->res_ready && !atomic_load(&a->interrupted) && !atomic_load(&c->quit))
1163
+ pthread_cond_wait(&c->cv, &c->mtx);
1164
+ if (!c->res_ready && atomic_load(&a->interrupted)) {
1165
+ atomic_store(&a->active, 0);
1166
+ pthread_mutex_unlock(&c->mtx);
1167
+ return (void *)(intptr_t)EINTR;
1168
+ }
1169
+ if (!c->res_ready && atomic_load(&c->quit)) {
1170
+ buf_reset(a->req);
1171
+ pthread_mutex_unlock(&c->mtx);
1172
+ a->finished = 1;
1173
+ rendezvous_release(a);
1174
+ return (void *)(intptr_t)ECANCELED;
1107
1175
  }
1108
1176
  buf_move(&c->res, a->res);
1177
+ c->res_ready = 0;
1109
1178
  pthread_cond_broadcast(&c->cv);
1110
1179
  pthread_mutex_unlock(&c->mtx);
1180
+ atomic_store(&a->active, 0);
1111
1181
  if (*a->res->buf == 'c') { // js -> ruby callback?
1112
1182
  rb_thread_call_with_gvl(rendezvous_callback, a);
1113
1183
  buf_reset(a->res);
1114
1184
  if (atomic_load(&c->quit)) {
1115
1185
  buf_reset(a->req);
1116
- c->depth--;
1117
- atomic_store(&a->active, 0);
1118
- pthread_mutex_unlock(&c->rr_mtx);
1186
+ a->finished = 1;
1187
+ rendezvous_release(a);
1119
1188
  return (void *)(intptr_t)ECANCELED;
1120
1189
  }
1121
1190
  goto next;
1122
1191
  }
1123
- c->depth--;
1124
- atomic_store(&a->active, 0);
1125
- pthread_mutex_unlock(&c->rr_mtx);
1192
+ a->finished = 1;
1193
+ rendezvous_release(a);
1126
1194
  return NULL;
1127
1195
  }
1128
1196
 
@@ -1134,9 +1202,8 @@ static void rendezvous_ubf(void *arg)
1134
1202
  a = arg;
1135
1203
  if (!atomic_load(&a->active))
1136
1204
  return;
1205
+ atomic_store(&a->interrupted, 1);
1137
1206
  c = a->context;
1138
- if (c->pst)
1139
- v8_terminate_execution(c->pst);
1140
1207
  pthread_cond_broadcast(&c->cv);
1141
1208
  }
1142
1209
 
@@ -1150,9 +1217,82 @@ static void terminate_ubf(void *arg)
1150
1217
  pthread_cond_broadcast(&c->cv);
1151
1218
  }
1152
1219
 
1220
+ static void *rendezvous_cancel_nogvl(void *arg)
1221
+ {
1222
+ // Reply to any pending JS->Ruby callback with an 'e' marker plus message
1223
+ // so V8 throws, unwinds, and reaches its normal termination cleanup.
1224
+ static const uint8_t terminated[] = "eterminated";
1225
+ struct rendezvous_nogvl *a;
1226
+ Context *c;
1227
+
1228
+ a = arg;
1229
+ c = a->context;
1230
+ atomic_store(&a->active, 0);
1231
+ if (c->pst)
1232
+ v8_terminate_execution(c->pst);
1233
+ pthread_mutex_lock(&c->mtx);
1234
+ pthread_cond_broadcast(&c->cv);
1235
+ while (!atomic_load(&c->quit)) {
1236
+ while (!c->res_ready && !atomic_load(&c->quit))
1237
+ pthread_cond_wait(&c->cv, &c->mtx);
1238
+ if (!c->res_ready)
1239
+ break;
1240
+ if (c->res.len && *c->res.buf != 'c')
1241
+ break;
1242
+ buf_reset(&c->res);
1243
+ c->res_ready = 0;
1244
+ buf_reset(&c->req);
1245
+ buf_put(&c->req, terminated, sizeof(terminated) - 1);
1246
+ pthread_cond_signal(&c->cv);
1247
+ }
1248
+ buf_reset(&c->req);
1249
+ buf_reset(&c->res);
1250
+ buf_reset(&c->v8_req);
1251
+ c->res_ready = 0;
1252
+ pthread_cond_broadcast(&c->cv);
1253
+ pthread_mutex_unlock(&c->mtx);
1254
+ if (c->pst)
1255
+ v8_cancel_terminate_execution(c->pst);
1256
+ a->finished = 1;
1257
+ rendezvous_release(a);
1258
+ return NULL;
1259
+ }
1260
+
1261
+ static VALUE rendezvous_no_des_body(VALUE arg)
1262
+ {
1263
+ struct rendezvous_nogvl *a;
1264
+ void *r;
1265
+
1266
+ a = (void *)arg;
1267
+ for (;;) {
1268
+ // Let the UBF wake this wait without deciding why Ruby interrupted it.
1269
+ // Back under the GVL, rb_thread_check_ints() runs signal traps and
1270
+ // raises real asynchronous exceptions (Timeout, Thread#raise, kill).
1271
+ atomic_store(&a->interrupted, 0);
1272
+ atomic_store(&a->active, 1);
1273
+ r = rb_nogvl(rendezvous_nogvl, a, rendezvous_ubf, a, RB_NOGVL_INTR_FAIL);
1274
+ atomic_store(&a->active, 0);
1275
+ if (a->finished || (r && (int)(intptr_t)r != EINTR))
1276
+ return LONG2NUM((long)(intptr_t)r);
1277
+ rb_thread_check_ints();
1278
+ }
1279
+ }
1280
+
1281
+ static VALUE rendezvous_no_des_ensure(VALUE arg)
1282
+ {
1283
+ struct rendezvous_nogvl *a;
1284
+
1285
+ a = (void *)arg;
1286
+ if (a->started && !a->finished)
1287
+ rb_nogvl(rendezvous_cancel_nogvl, a, NULL, NULL, 0);
1288
+ buf_reset(a->req);
1289
+ return Qnil;
1290
+ }
1291
+
1153
1292
  static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
1154
1293
  {
1155
1294
  void *r;
1295
+ VALUE rv;
1156
1296
  struct rendezvous_nogvl a;
1157
1297
 
1158
1298
  if (atomic_load(&c->quit)) {
@@ -1163,7 +1303,15 @@ static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
1163
1303
  a.req = req;
1164
1304
  a.res = res;
1165
1305
  atomic_init(&a.active, 0);
1166
- r = rb_nogvl(rendezvous_nogvl, &a, rendezvous_ubf, &a, 0);
1306
+ atomic_init(&a.interrupted, 0);
1307
+ a.started = 0;
1308
+ a.finished = 0;
1309
+ a.has_rr_mtx = 0;
1310
+ rv = rb_ensure(rendezvous_no_des_body, (VALUE)&a,
1311
+ rendezvous_no_des_ensure, (VALUE)&a);
1312
+ r = (void *)(intptr_t)NUM2LONG(rv);
1313
+ if ((int)(intptr_t)r == ECANCELED)
1314
+ rb_raise(context_disposed_error, "disposed context");
1167
1315
  if (r)
1168
1316
  rb_raise(runtime_error, "single-threaded runner: %s", strerror((int)(intptr_t)r));
1169
1317
  }
@@ -1283,6 +1431,7 @@ static VALUE context_alloc(VALUE klass)
1283
1431
  buf_init(&c->snapshot);
1284
1432
  buf_init(&c->req);
1285
1433
  buf_init(&c->res);
1434
+ buf_init(&c->v8_req);
1286
1435
  cause = "pthread_condattr_init";
1287
1436
  if ((r = pthread_condattr_init(&cattr)))
1288
1437
  goto fail0;
@@ -1387,6 +1536,7 @@ static void context_abandon(Context *c)
1387
1536
  buf_reset(&c->snapshot);
1388
1537
  buf_reset(&c->req);
1389
1538
  buf_reset(&c->res);
1539
+ buf_reset(&c->v8_req);
1390
1540
  ruby_xfree(c);
1391
1541
  }
1392
1542
 
@@ -1402,6 +1552,7 @@ static void context_destroy(Context *c)
1402
1552
  buf_reset(&c->snapshot);
1403
1553
  buf_reset(&c->req);
1404
1554
  buf_reset(&c->res);
1555
+ buf_reset(&c->v8_req);
1405
1556
  ruby_xfree(c);
1406
1557
  }
1407
1558
 
@@ -1515,7 +1666,7 @@ static VALUE context_stop(VALUE self)
1515
1666
  return Qnil;
1516
1667
  }
1517
1668
 
1518
- static VALUE context_call(int argc, VALUE *argv, VALUE self)
1669
+ static VALUE context_call_common(int argc, VALUE *argv, VALUE self, char op)
1519
1670
  {
1520
1671
  VALUE name, args;
1521
1672
  VALUE a, e;
@@ -1526,8 +1677,8 @@ static VALUE context_call(int argc, VALUE *argv, VALUE self)
1526
1677
  rb_scan_args(argc, argv, "1*", &name, &args);
1527
1678
  Check_Type(name, T_STRING);
1528
1679
  rb_ary_unshift(args, name);
1529
- // request is (C)all, [name, args...] array
1530
- ser_init1(&s, 'C');
1680
+ // request is (C)all or (D) call_await, [name, args...] array
1681
+ ser_init1(&s, op);
1531
1682
  if (serialize(&s, args)) {
1532
1683
  ser_reset(&s);
1533
1684
  rb_raise(runtime_error, "Context.call: %s", s.err);
@@ -1539,7 +1690,17 @@ static VALUE context_call(int argc, VALUE *argv, VALUE self)
1539
1690
  return rb_ary_pop(a);
1540
1691
  }
1541
1692
 
1542
- static VALUE context_eval(int argc, VALUE *argv, VALUE self)
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)
1543
1704
  {
1544
1705
  VALUE a, e, source, filename, kwargs;
1545
1706
  Context *c;
@@ -1554,8 +1715,8 @@ static VALUE context_eval(int argc, VALUE *argv, VALUE self)
1554
1715
  if (NIL_P(filename))
1555
1716
  filename = rb_str_new_cstr("<eval>");
1556
1717
  Check_Type(filename, T_STRING);
1557
- // request is (E)val, [filename, source] array
1558
- ser_init1(&s, 'E');
1718
+ // request is (E)val or (F) eval_await, [filename, source] array
1719
+ ser_init1(&s, op);
1559
1720
  ser_array_begin(&s, 2);
1560
1721
  add_string(&s, filename);
1561
1722
  add_string(&s, source);
@@ -1567,6 +1728,16 @@ static VALUE context_eval(int argc, VALUE *argv, VALUE self)
1567
1728
  return rb_ary_pop(a);
1568
1729
  }
1569
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
+
1570
1741
  static VALUE context_heap_stats(VALUE self)
1571
1742
  {
1572
1743
  VALUE a, h, k, v;
@@ -2007,7 +2178,9 @@ void Init_mini_racer_extension(void)
2007
2178
  rb_define_method(c, "dispose", context_dispose, 0);
2008
2179
  rb_define_method(c, "stop", context_stop, 0);
2009
2180
  rb_define_method(c, "call", context_call, -1);
2181
+ rb_define_method(c, "call_await", context_call_await, -1);
2010
2182
  rb_define_method(c, "eval", context_eval, -1);
2183
+ rb_define_method(c, "eval_await", context_eval_await, -1);
2011
2184
  rb_define_method(c, "heap_stats", context_heap_stats, 0);
2012
2185
  rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
2013
2186
  rb_define_method(c, "perform_microtask_checkpoint", context_perform_microtask_checkpoint, 0);