mini_racer 0.21.2 → 0.21.4
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2e290a77bd6b38286dc4e4224d71579a1e8e41bbb8fd70029584f518383ba7f
|
|
4
|
+
data.tar.gz: 479d9793ade9c46075986dd53c0a7ebacf62e1afb199fc6983e5b7c4fb4e0faa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc69216098ec6076dc90cf731fc5fd229526a4c21d3383fdb94448aace72068f36fc6bf6910cb09d8f42bf57c6926bf1527a2fd0871c11c5b08df820643cc23e
|
|
7
|
+
data.tar.gz: b7b4b851b4cbc9ad35c56e39db1d5c7ccc19b23dabd9dd87f084ba37f0a59fb584c833128827d58ecdb0c99f75969383333d3b304d00f816ba544e83e5436639
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
- 0.21.4 - 24-06-2026
|
|
2
|
+
- Fix stale V8 termination state after interrupts/timeouts so contexts remain usable after cancelled evaluations
|
|
3
|
+
- Let Ruby interrupts wake MiniRacer calls without immediately terminating V8, allowing signal traps and nested callbacks to unwind safely
|
|
4
|
+
- Add benchmark suite covering eval, serialization/deserialization, and transpilation workloads
|
|
5
|
+
|
|
6
|
+
- 0.21.3 - 18-06-2026
|
|
7
|
+
- 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
|
|
8
|
+
- Avoid intermittent heap corruption during `:single_threaded` context finalization, especially when forked children exit normally after touching inherited contexts
|
|
9
|
+
- Avoid finalizer hangs when a forked child garbage-collects a non-idle inherited `:single_threaded` context
|
|
10
|
+
- Allow Ruby thread interrupts, process shutdown, and cross-thread `Context#dispose` to terminate busy `:single_threaded` JavaScript execution instead of hanging
|
|
11
|
+
- Make `Context#dispose` while an attached Ruby callback is active either terminate safely or raise instead of deadlocking
|
|
12
|
+
|
|
1
13
|
- 0.21.2 - 11-06-2026
|
|
2
14
|
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks
|
|
3
15
|
- Fix native memory leaks in `Context#heap_snapshot`/`Context#write_heap_snapshot`; thanks to Pranjali Thakur from depthfirst.com
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <string.h>
|
|
6
6
|
#include <pthread.h>
|
|
7
7
|
#include <unistd.h>
|
|
8
|
+
#include <errno.h>
|
|
8
9
|
#include <math.h>
|
|
9
10
|
|
|
10
11
|
#if defined(__linux__) && !defined(__GLIBC__)
|
|
@@ -136,6 +137,8 @@ typedef struct Context
|
|
|
136
137
|
VALUE procs; // array of js -> ruby callbacks
|
|
137
138
|
VALUE exception; // pending exception or Qnil
|
|
138
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
|
|
139
142
|
Buf snapshot;
|
|
140
143
|
pthread_t single_threaded_thr;
|
|
141
144
|
pid_t single_threaded_pid;
|
|
@@ -159,6 +162,7 @@ typedef struct Snapshot {
|
|
|
159
162
|
} Snapshot;
|
|
160
163
|
|
|
161
164
|
static void context_destroy(Context *c);
|
|
165
|
+
static void context_abandon(Context *c);
|
|
162
166
|
static void context_free(void *arg);
|
|
163
167
|
static void context_mark(void *arg);
|
|
164
168
|
static size_t context_size(const void *arg);
|
|
@@ -227,6 +231,9 @@ struct rendezvous_nogvl
|
|
|
227
231
|
{
|
|
228
232
|
Context *context;
|
|
229
233
|
Buf *req, *res;
|
|
234
|
+
atomic_int active;
|
|
235
|
+
atomic_int interrupted;
|
|
236
|
+
int started, finished, has_rr_mtx;
|
|
230
237
|
};
|
|
231
238
|
|
|
232
239
|
struct rendezvous_des
|
|
@@ -819,11 +826,28 @@ static void dispatch1(Context *c, const uint8_t *p, size_t n)
|
|
|
819
826
|
fflush(stderr);
|
|
820
827
|
}
|
|
821
828
|
|
|
822
|
-
static void
|
|
829
|
+
static void dispatch_buf(Context *c, Buf *req)
|
|
823
830
|
{
|
|
831
|
+
Buf local_req;
|
|
832
|
+
|
|
833
|
+
// Called with Context.mtx held. Release it while V8 runs so an rb_nogvl
|
|
834
|
+
// UBF can wake the Ruby thread to process interrupts with
|
|
835
|
+
// rb_thread_check_ints(), without forcing termination first. Move the
|
|
836
|
+
// request out first so cancellation cannot free a buffer V8 is reading.
|
|
837
|
+
buf_move(req, &local_req);
|
|
824
838
|
buf_reset(&c->res);
|
|
825
|
-
|
|
826
|
-
|
|
839
|
+
c->res_ready = 0;
|
|
840
|
+
pthread_mutex_unlock(&c->mtx);
|
|
841
|
+
dispatch1(c, local_req.buf, local_req.len);
|
|
842
|
+
pthread_mutex_lock(&c->mtx);
|
|
843
|
+
buf_reset(&local_req);
|
|
844
|
+
c->res_ready = 1;
|
|
845
|
+
pthread_cond_signal(&c->cv);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
static void dispatch(Context *c)
|
|
849
|
+
{
|
|
850
|
+
dispatch_buf(c, &c->req);
|
|
827
851
|
}
|
|
828
852
|
|
|
829
853
|
// called by v8_isolate_and_context
|
|
@@ -856,31 +880,45 @@ void v8_thread_main(Context *c, struct State *pst)
|
|
|
856
880
|
}
|
|
857
881
|
}
|
|
858
882
|
|
|
859
|
-
// called by v8_thread_main and from mini_racer_v8.cc
|
|
860
|
-
// in all cases with Context.mtx held
|
|
883
|
+
// called by v8_thread_main and from mini_racer_v8.cc
|
|
861
884
|
void v8_dispatch(Context *c)
|
|
862
885
|
{
|
|
863
|
-
|
|
864
|
-
|
|
886
|
+
pthread_mutex_lock(&c->mtx);
|
|
887
|
+
dispatch_buf(c, &c->v8_req);
|
|
888
|
+
pthread_mutex_unlock(&c->mtx);
|
|
865
889
|
}
|
|
866
890
|
|
|
867
|
-
// called from mini_racer_v8.cc with Context.mtx held
|
|
868
891
|
// only called when inside v8_call, v8_eval, or v8_pump_message_loop
|
|
869
892
|
void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
|
|
870
893
|
{
|
|
894
|
+
pthread_mutex_lock(&c->mtx);
|
|
895
|
+
buf_reset(&c->v8_req);
|
|
871
896
|
buf_reset(&c->req);
|
|
897
|
+
if (c->res.len)
|
|
898
|
+
c->res_ready = 1;
|
|
872
899
|
pthread_cond_signal(&c->cv);
|
|
873
|
-
while (!c->req.len)
|
|
900
|
+
while (!c->req.len && !atomic_load(&c->quit))
|
|
874
901
|
pthread_cond_wait(&c->cv, &c->mtx);
|
|
902
|
+
if (!c->req.len && atomic_load(&c->quit)) {
|
|
903
|
+
static const uint8_t disposed[] = "edisposed context";
|
|
904
|
+
*p = disposed;
|
|
905
|
+
*n = sizeof(disposed) - 1;
|
|
906
|
+
pthread_mutex_unlock(&c->mtx);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
875
909
|
buf_reset(&c->res);
|
|
876
|
-
|
|
877
|
-
|
|
910
|
+
c->res_ready = 0;
|
|
911
|
+
buf_move(&c->req, &c->v8_req);
|
|
912
|
+
*p = c->v8_req.buf;
|
|
913
|
+
*n = c->v8_req.len;
|
|
914
|
+
pthread_mutex_unlock(&c->mtx);
|
|
878
915
|
}
|
|
879
916
|
|
|
880
|
-
// called from mini_racer_v8.cc with Context.mtx held
|
|
881
917
|
void v8_reply(Context *c, const uint8_t *p, size_t n)
|
|
882
918
|
{
|
|
919
|
+
pthread_mutex_lock(&c->mtx);
|
|
883
920
|
buf_put(&c->res, p, n);
|
|
921
|
+
pthread_mutex_unlock(&c->mtx);
|
|
884
922
|
}
|
|
885
923
|
|
|
886
924
|
static void v8_once_init(void)
|
|
@@ -1007,6 +1045,33 @@ static void *single_threaded_runner(void *arg)
|
|
|
1007
1045
|
return NULL;
|
|
1008
1046
|
}
|
|
1009
1047
|
|
|
1048
|
+
static int single_threaded_recover_after_fork(Context *c)
|
|
1049
|
+
{
|
|
1050
|
+
pthread_condattr_t cattr;
|
|
1051
|
+
pid_t pid;
|
|
1052
|
+
int r;
|
|
1053
|
+
|
|
1054
|
+
pid = getpid();
|
|
1055
|
+
if (!c->single_threaded_thr_started || c->single_threaded_pid == pid)
|
|
1056
|
+
return 0;
|
|
1057
|
+
if (c->depth || c->req.len || c->res.len)
|
|
1058
|
+
return EBUSY;
|
|
1059
|
+
|
|
1060
|
+
if ((r = pthread_condattr_init(&cattr)))
|
|
1061
|
+
return r;
|
|
1062
|
+
#ifndef __APPLE__
|
|
1063
|
+
pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
|
|
1064
|
+
#endif
|
|
1065
|
+
r = pthread_cond_init(&c->cv, &cattr);
|
|
1066
|
+
pthread_condattr_destroy(&cattr);
|
|
1067
|
+
if (r)
|
|
1068
|
+
return r;
|
|
1069
|
+
|
|
1070
|
+
c->single_threaded_thr_started = 0;
|
|
1071
|
+
c->single_threaded_pid = pid;
|
|
1072
|
+
return 0;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1010
1075
|
static int single_threaded_runner_start(Context *c)
|
|
1011
1076
|
{
|
|
1012
1077
|
pid_t pid;
|
|
@@ -1023,6 +1088,19 @@ static int single_threaded_runner_start(Context *c)
|
|
|
1023
1088
|
return r;
|
|
1024
1089
|
}
|
|
1025
1090
|
|
|
1091
|
+
static void rendezvous_release(struct rendezvous_nogvl *a)
|
|
1092
|
+
{
|
|
1093
|
+
Context *c;
|
|
1094
|
+
|
|
1095
|
+
atomic_store(&a->active, 0);
|
|
1096
|
+
if (!a->has_rr_mtx)
|
|
1097
|
+
return;
|
|
1098
|
+
c = a->context;
|
|
1099
|
+
c->depth--;
|
|
1100
|
+
a->has_rr_mtx = 0;
|
|
1101
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1026
1104
|
static inline void *rendezvous_nogvl(void *arg)
|
|
1027
1105
|
{
|
|
1028
1106
|
struct rendezvous_nogvl *a;
|
|
@@ -1031,56 +1109,200 @@ static inline void *rendezvous_nogvl(void *arg)
|
|
|
1031
1109
|
|
|
1032
1110
|
a = arg;
|
|
1033
1111
|
c = a->context;
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1112
|
+
if (!a->started) {
|
|
1113
|
+
if (single_threaded && (r = single_threaded_recover_after_fork(c)))
|
|
1114
|
+
return (void *)(intptr_t)r;
|
|
1115
|
+
pthread_mutex_lock(&c->rr_mtx);
|
|
1116
|
+
a->has_rr_mtx = 1;
|
|
1117
|
+
if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
|
|
1118
|
+
fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
|
|
1119
|
+
fflush(stderr);
|
|
1120
|
+
}
|
|
1121
|
+
c->depth++;
|
|
1122
|
+
a->started = 1;
|
|
1038
1123
|
}
|
|
1039
|
-
|
|
1124
|
+
|
|
1040
1125
|
next:
|
|
1126
|
+
atomic_store(&a->active, 1);
|
|
1041
1127
|
pthread_mutex_lock(&c->mtx);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1128
|
+
if (atomic_load(&c->quit)) {
|
|
1129
|
+
buf_reset(a->req);
|
|
1130
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1131
|
+
a->finished = 1;
|
|
1132
|
+
rendezvous_release(a);
|
|
1133
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1134
|
+
}
|
|
1135
|
+
if (a->req->len) {
|
|
1136
|
+
assert(c->req.len == 0);
|
|
1137
|
+
assert(!c->res_ready);
|
|
1138
|
+
buf_move(a->req, &c->req); // v8 thread takes ownership of req
|
|
1139
|
+
if (single_threaded) {
|
|
1140
|
+
r = single_threaded_runner_start(c);
|
|
1141
|
+
if (r) {
|
|
1142
|
+
buf_move(&c->req, a->req);
|
|
1143
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1144
|
+
a->finished = 1;
|
|
1145
|
+
rendezvous_release(a);
|
|
1146
|
+
return (void *)(intptr_t)r;
|
|
1147
|
+
}
|
|
1053
1148
|
}
|
|
1054
1149
|
pthread_cond_signal(&c->cv);
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1150
|
+
}
|
|
1151
|
+
while (!c->res_ready && !atomic_load(&a->interrupted) && !atomic_load(&c->quit))
|
|
1152
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1153
|
+
if (!c->res_ready && atomic_load(&a->interrupted)) {
|
|
1154
|
+
atomic_store(&a->active, 0);
|
|
1155
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1156
|
+
return (void *)(intptr_t)EINTR;
|
|
1157
|
+
}
|
|
1158
|
+
if (!c->res_ready && atomic_load(&c->quit)) {
|
|
1159
|
+
buf_reset(a->req);
|
|
1160
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1161
|
+
a->finished = 1;
|
|
1162
|
+
rendezvous_release(a);
|
|
1163
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1059
1164
|
}
|
|
1060
1165
|
buf_move(&c->res, a->res);
|
|
1166
|
+
c->res_ready = 0;
|
|
1167
|
+
pthread_cond_broadcast(&c->cv);
|
|
1061
1168
|
pthread_mutex_unlock(&c->mtx);
|
|
1169
|
+
atomic_store(&a->active, 0);
|
|
1062
1170
|
if (*a->res->buf == 'c') { // js -> ruby callback?
|
|
1063
1171
|
rb_thread_call_with_gvl(rendezvous_callback, a);
|
|
1064
1172
|
buf_reset(a->res);
|
|
1173
|
+
if (atomic_load(&c->quit)) {
|
|
1174
|
+
buf_reset(a->req);
|
|
1175
|
+
a->finished = 1;
|
|
1176
|
+
rendezvous_release(a);
|
|
1177
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1178
|
+
}
|
|
1065
1179
|
goto next;
|
|
1066
1180
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1181
|
+
a->finished = 1;
|
|
1182
|
+
rendezvous_release(a);
|
|
1183
|
+
return NULL;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
static void rendezvous_ubf(void *arg)
|
|
1187
|
+
{
|
|
1188
|
+
struct rendezvous_nogvl *a;
|
|
1189
|
+
Context *c;
|
|
1190
|
+
|
|
1191
|
+
a = arg;
|
|
1192
|
+
if (!atomic_load(&a->active))
|
|
1193
|
+
return;
|
|
1194
|
+
atomic_store(&a->interrupted, 1);
|
|
1195
|
+
c = a->context;
|
|
1196
|
+
pthread_cond_broadcast(&c->cv);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
static void terminate_ubf(void *arg)
|
|
1200
|
+
{
|
|
1201
|
+
Context *c;
|
|
1202
|
+
|
|
1203
|
+
c = arg;
|
|
1204
|
+
if (c->pst)
|
|
1205
|
+
v8_terminate_execution(c->pst);
|
|
1206
|
+
pthread_cond_broadcast(&c->cv);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
static void *rendezvous_cancel_nogvl(void *arg)
|
|
1210
|
+
{
|
|
1211
|
+
// Reply to any pending JS->Ruby callback with an 'e' marker plus message
|
|
1212
|
+
// so V8 throws, unwinds, and reaches its normal termination cleanup.
|
|
1213
|
+
static const uint8_t terminated[] = "eterminated";
|
|
1214
|
+
struct rendezvous_nogvl *a;
|
|
1215
|
+
Context *c;
|
|
1216
|
+
|
|
1217
|
+
a = arg;
|
|
1218
|
+
c = a->context;
|
|
1219
|
+
atomic_store(&a->active, 0);
|
|
1220
|
+
if (c->pst)
|
|
1221
|
+
v8_terminate_execution(c->pst);
|
|
1222
|
+
pthread_mutex_lock(&c->mtx);
|
|
1223
|
+
pthread_cond_broadcast(&c->cv);
|
|
1224
|
+
while (!atomic_load(&c->quit)) {
|
|
1225
|
+
while (!c->res_ready && !atomic_load(&c->quit))
|
|
1226
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1227
|
+
if (!c->res_ready)
|
|
1228
|
+
break;
|
|
1229
|
+
if (c->res.len && *c->res.buf != 'c')
|
|
1230
|
+
break;
|
|
1231
|
+
buf_reset(&c->res);
|
|
1232
|
+
c->res_ready = 0;
|
|
1233
|
+
buf_reset(&c->req);
|
|
1234
|
+
buf_put(&c->req, terminated, sizeof(terminated) - 1);
|
|
1235
|
+
pthread_cond_signal(&c->cv);
|
|
1236
|
+
}
|
|
1237
|
+
buf_reset(&c->req);
|
|
1238
|
+
buf_reset(&c->res);
|
|
1239
|
+
buf_reset(&c->v8_req);
|
|
1240
|
+
c->res_ready = 0;
|
|
1241
|
+
pthread_cond_broadcast(&c->cv);
|
|
1242
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1243
|
+
if (c->pst)
|
|
1244
|
+
v8_cancel_terminate_execution(c->pst);
|
|
1245
|
+
a->finished = 1;
|
|
1246
|
+
rendezvous_release(a);
|
|
1069
1247
|
return NULL;
|
|
1070
1248
|
}
|
|
1071
1249
|
|
|
1250
|
+
static VALUE rendezvous_no_des_body(VALUE arg)
|
|
1251
|
+
{
|
|
1252
|
+
struct rendezvous_nogvl *a;
|
|
1253
|
+
void *r;
|
|
1254
|
+
|
|
1255
|
+
a = (void *)arg;
|
|
1256
|
+
for (;;) {
|
|
1257
|
+
// Let the UBF wake this wait without deciding why Ruby interrupted it.
|
|
1258
|
+
// Back under the GVL, rb_thread_check_ints() runs signal traps and
|
|
1259
|
+
// raises real asynchronous exceptions (Timeout, Thread#raise, kill).
|
|
1260
|
+
atomic_store(&a->interrupted, 0);
|
|
1261
|
+
atomic_store(&a->active, 1);
|
|
1262
|
+
r = rb_nogvl(rendezvous_nogvl, a, rendezvous_ubf, a, RB_NOGVL_INTR_FAIL);
|
|
1263
|
+
atomic_store(&a->active, 0);
|
|
1264
|
+
if (a->finished || (r && (int)(intptr_t)r != EINTR))
|
|
1265
|
+
return LONG2NUM((long)(intptr_t)r);
|
|
1266
|
+
rb_thread_check_ints();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
static VALUE rendezvous_no_des_ensure(VALUE arg)
|
|
1271
|
+
{
|
|
1272
|
+
struct rendezvous_nogvl *a;
|
|
1273
|
+
|
|
1274
|
+
a = (void *)arg;
|
|
1275
|
+
if (a->started && !a->finished)
|
|
1276
|
+
rb_nogvl(rendezvous_cancel_nogvl, a, NULL, NULL, 0);
|
|
1277
|
+
buf_reset(a->req);
|
|
1278
|
+
return Qnil;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1072
1281
|
static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
|
|
1073
1282
|
{
|
|
1074
1283
|
void *r;
|
|
1284
|
+
VALUE rv;
|
|
1285
|
+
struct rendezvous_nogvl a;
|
|
1075
1286
|
|
|
1076
1287
|
if (atomic_load(&c->quit)) {
|
|
1077
1288
|
buf_reset(req);
|
|
1078
1289
|
rb_raise(context_disposed_error, "disposed context");
|
|
1079
1290
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1291
|
+
a.context = c;
|
|
1292
|
+
a.req = req;
|
|
1293
|
+
a.res = res;
|
|
1294
|
+
atomic_init(&a.active, 0);
|
|
1295
|
+
atomic_init(&a.interrupted, 0);
|
|
1296
|
+
a.started = 0;
|
|
1297
|
+
a.finished = 0;
|
|
1298
|
+
a.has_rr_mtx = 0;
|
|
1299
|
+
rv = rb_ensure(rendezvous_no_des_body, (VALUE)&a,
|
|
1300
|
+
rendezvous_no_des_ensure, (VALUE)&a);
|
|
1301
|
+
r = (void *)(intptr_t)NUM2LONG(rv);
|
|
1302
|
+
if ((int)(intptr_t)r == ECANCELED)
|
|
1303
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
1082
1304
|
if (r)
|
|
1083
|
-
rb_raise(runtime_error, "
|
|
1305
|
+
rb_raise(runtime_error, "single-threaded runner: %s", strerror((int)(intptr_t)r));
|
|
1084
1306
|
}
|
|
1085
1307
|
|
|
1086
1308
|
// send request to & receive reply from v8 thread; takes ownership of |req|
|
|
@@ -1096,7 +1318,8 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
|
|
|
1096
1318
|
c->exception = Qnil;
|
|
1097
1319
|
// if js land didn't handle exception from ruby callback, re-raise it now
|
|
1098
1320
|
if (res.len == 1 && *res.buf == 'e') {
|
|
1099
|
-
|
|
1321
|
+
if (NIL_P(r))
|
|
1322
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
1100
1323
|
rb_exc_raise(r);
|
|
1101
1324
|
}
|
|
1102
1325
|
r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
|
|
@@ -1197,6 +1420,7 @@ static VALUE context_alloc(VALUE klass)
|
|
|
1197
1420
|
buf_init(&c->snapshot);
|
|
1198
1421
|
buf_init(&c->req);
|
|
1199
1422
|
buf_init(&c->res);
|
|
1423
|
+
buf_init(&c->v8_req);
|
|
1200
1424
|
cause = "pthread_condattr_init";
|
|
1201
1425
|
if ((r = pthread_condattr_init(&cattr)))
|
|
1202
1426
|
goto fail0;
|
|
@@ -1251,11 +1475,19 @@ fail0:
|
|
|
1251
1475
|
return Qnil; // pacify compiler
|
|
1252
1476
|
}
|
|
1253
1477
|
|
|
1254
|
-
static void *
|
|
1478
|
+
static void *context_free_do(void *arg)
|
|
1255
1479
|
{
|
|
1256
1480
|
Context *c;
|
|
1257
1481
|
|
|
1258
1482
|
c = arg;
|
|
1483
|
+
if (single_threaded && single_threaded_recover_after_fork(c)) {
|
|
1484
|
+
// The child forked while this inherited context was not idle. There is
|
|
1485
|
+
// no live runner thread to join and the inherited V8/pthread state is
|
|
1486
|
+
// not safe to tear down. A finalizer must not hang here; let the OS
|
|
1487
|
+
// reclaim the abandoned V8 state when the child exits.
|
|
1488
|
+
context_abandon(c);
|
|
1489
|
+
return NULL;
|
|
1490
|
+
}
|
|
1259
1491
|
if (single_threaded && c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1260
1492
|
pthread_mutex_lock(&c->mtx);
|
|
1261
1493
|
atomic_store(&c->quit, 2);
|
|
@@ -1270,31 +1502,16 @@ static void *context_free_thread_do(void *arg)
|
|
|
1270
1502
|
return NULL;
|
|
1271
1503
|
}
|
|
1272
1504
|
|
|
1273
|
-
static void context_free_thread(Context *c)
|
|
1274
|
-
{
|
|
1275
|
-
pthread_t thr;
|
|
1276
|
-
int r;
|
|
1277
|
-
|
|
1278
|
-
// dispose on another thread so we don't block when trying to
|
|
1279
|
-
// enter an isolate that's in a stuck state; that *should* be
|
|
1280
|
-
// impossible but apparently it happened regularly before the
|
|
1281
|
-
// rewrite and I'm carrying it over out of an abundance of caution
|
|
1282
|
-
if ((r = pthread_create(&thr, NULL, context_free_thread_do, c))) {
|
|
1283
|
-
fprintf(stderr, "mini_racer: pthread_create: %s", strerror(r));
|
|
1284
|
-
fflush(stderr);
|
|
1285
|
-
context_free_thread_do(c);
|
|
1286
|
-
} else {
|
|
1287
|
-
pthread_detach(thr);
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
1505
|
static void context_free(void *arg)
|
|
1292
1506
|
{
|
|
1293
1507
|
Context *c;
|
|
1294
1508
|
|
|
1295
1509
|
c = arg;
|
|
1296
1510
|
if (single_threaded) {
|
|
1297
|
-
|
|
1511
|
+
// Free synchronously. A detached cleanup thread can race normal Ruby
|
|
1512
|
+
// process shutdown and trip glibc malloc corruption checks while V8 is
|
|
1513
|
+
// tearing down single-threaded contexts.
|
|
1514
|
+
context_free_do(c);
|
|
1298
1515
|
} else {
|
|
1299
1516
|
pthread_mutex_lock(&c->mtx);
|
|
1300
1517
|
c->quit = 2; // 2 = v8 thread frees
|
|
@@ -1303,6 +1520,15 @@ static void context_free(void *arg)
|
|
|
1303
1520
|
}
|
|
1304
1521
|
}
|
|
1305
1522
|
|
|
1523
|
+
static void context_abandon(Context *c)
|
|
1524
|
+
{
|
|
1525
|
+
buf_reset(&c->snapshot);
|
|
1526
|
+
buf_reset(&c->req);
|
|
1527
|
+
buf_reset(&c->res);
|
|
1528
|
+
buf_reset(&c->v8_req);
|
|
1529
|
+
ruby_xfree(c);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1306
1532
|
static void context_destroy(Context *c)
|
|
1307
1533
|
{
|
|
1308
1534
|
pthread_mutex_unlock(&c->mtx);
|
|
@@ -1315,6 +1541,7 @@ static void context_destroy(Context *c)
|
|
|
1315
1541
|
buf_reset(&c->snapshot);
|
|
1316
1542
|
buf_reset(&c->req);
|
|
1317
1543
|
buf_reset(&c->res);
|
|
1544
|
+
buf_reset(&c->v8_req);
|
|
1318
1545
|
ruby_xfree(c);
|
|
1319
1546
|
}
|
|
1320
1547
|
|
|
@@ -1360,8 +1587,25 @@ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
|
|
|
1360
1587
|
static void *context_dispose_do(void *arg)
|
|
1361
1588
|
{
|
|
1362
1589
|
Context *c;
|
|
1590
|
+
int r;
|
|
1363
1591
|
|
|
1364
1592
|
c = arg;
|
|
1593
|
+
if (single_threaded) {
|
|
1594
|
+
if ((r = single_threaded_recover_after_fork(c)))
|
|
1595
|
+
return (void *)(intptr_t)r;
|
|
1596
|
+
}
|
|
1597
|
+
if (c->depth > 0) {
|
|
1598
|
+
r = pthread_mutex_trylock(&c->rr_mtx);
|
|
1599
|
+
if (!r) {
|
|
1600
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1601
|
+
return (void *)(intptr_t)EBUSY;
|
|
1602
|
+
}
|
|
1603
|
+
if (r != EBUSY)
|
|
1604
|
+
return (void *)(intptr_t)r;
|
|
1605
|
+
if (c->pst)
|
|
1606
|
+
v8_terminate_execution(c->pst);
|
|
1607
|
+
pthread_cond_broadcast(&c->cv);
|
|
1608
|
+
}
|
|
1365
1609
|
if (single_threaded) {
|
|
1366
1610
|
pthread_mutex_lock(&c->mtx);
|
|
1367
1611
|
while (c->req.len || c->res.len)
|
|
@@ -1389,9 +1633,12 @@ static void *context_dispose_do(void *arg)
|
|
|
1389
1633
|
static VALUE context_dispose(VALUE self)
|
|
1390
1634
|
{
|
|
1391
1635
|
Context *c;
|
|
1636
|
+
void *r;
|
|
1392
1637
|
|
|
1393
1638
|
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1394
|
-
rb_thread_call_without_gvl(context_dispose_do, c,
|
|
1639
|
+
r = rb_thread_call_without_gvl(context_dispose_do, c, terminate_ubf, c);
|
|
1640
|
+
if (r)
|
|
1641
|
+
rb_raise(runtime_error, "context dispose: %s", strerror((int)(intptr_t)r));
|
|
1395
1642
|
return Qnil;
|
|
1396
1643
|
}
|
|
1397
1644
|
|
|
@@ -1014,6 +1014,14 @@ extern "C" void v8_terminate_execution(State *pst)
|
|
|
1014
1014
|
pst->isolate->TerminateExecution();
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
+
// called from ruby thread
|
|
1018
|
+
extern "C" void v8_cancel_terminate_execution(State *pst)
|
|
1019
|
+
{
|
|
1020
|
+
// TerminateExecution can race with V8 completing and queue a termination
|
|
1021
|
+
// for the next entry without IsExecutionTerminating() becoming true.
|
|
1022
|
+
pst->isolate->CancelTerminateExecution();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1017
1025
|
extern "C" void v8_single_threaded_enter(State *pst, Context *c, void (*f)(Context *c))
|
|
1018
1026
|
{
|
|
1019
1027
|
State& st = *pst;
|
|
@@ -48,6 +48,7 @@ void v8_snapshot(struct State *pst, const uint8_t *p, size_t n);
|
|
|
48
48
|
void v8_warmup(struct State *pst, const uint8_t *p, size_t n);
|
|
49
49
|
void v8_low_memory_notification(struct State *pst);
|
|
50
50
|
void v8_terminate_execution(struct State *pst); // called from ruby or watchdog thread
|
|
51
|
+
void v8_cancel_terminate_execution(struct State *pst); // called from ruby thread
|
|
51
52
|
void v8_single_threaded_enter(struct State *pst, struct Context *c, void (*f)(struct Context *c));
|
|
52
53
|
void v8_single_threaded_dispose(struct State *pst);
|
|
53
54
|
|
data/lib/mini_racer/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mini_racer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.21.
|
|
4
|
+
version: 0.21.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
@@ -136,9 +136,9 @@ licenses:
|
|
|
136
136
|
- MIT
|
|
137
137
|
metadata:
|
|
138
138
|
bug_tracker_uri: https://github.com/discourse/mini_racer/issues
|
|
139
|
-
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.21.
|
|
140
|
-
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.
|
|
141
|
-
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.
|
|
139
|
+
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.21.4/CHANGELOG
|
|
140
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.4
|
|
141
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.4
|
|
142
142
|
rdoc_options: []
|
|
143
143
|
require_paths:
|
|
144
144
|
- lib
|