mini_racer 0.21.2 → 0.21.3
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 +7 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +133 -26
- data/lib/mini_racer/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e370b2c6cdb60e5081d5f97840cff28489119f0225a3c9a74ed7f6859caf40e
|
|
4
|
+
data.tar.gz: 7405ed87278cbaa1d155bc72f8c99d8e8c744df743252eb4128b9ae9ff899d07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c9f53c6a9052cca6a5129d023ecece59613c40c17f4c7666c728f8f6327200e34d707557ea4dcd9b626aed332b327c6c3e3f90e365c630adbec0b2d02c4b1c1
|
|
7
|
+
data.tar.gz: 4dc0348a006f7620c4c9688c0b877f2bd9500b7ece37fba4fa6e868006af1517e4279537693fd87b2f18c2b0602067084e2332e2bf9fa862504ebaab4ddbbcfc
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
- 0.21.3 - 18-06-2026
|
|
2
|
+
- 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
|
+
- Avoid intermittent heap corruption during `:single_threaded` context finalization, especially when forked children exit normally after touching inherited contexts
|
|
4
|
+
- Avoid finalizer hangs when a forked child garbage-collects a non-idle inherited `:single_threaded` context
|
|
5
|
+
- Allow Ruby thread interrupts, process shutdown, and cross-thread `Context#dispose` to terminate busy `:single_threaded` JavaScript execution instead of hanging
|
|
6
|
+
- Make `Context#dispose` while an attached Ruby callback is active either terminate safely or raise instead of deadlocking
|
|
7
|
+
|
|
1
8
|
- 0.21.2 - 11-06-2026
|
|
2
9
|
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks
|
|
3
10
|
- 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__)
|
|
@@ -159,6 +160,7 @@ typedef struct Snapshot {
|
|
|
159
160
|
} Snapshot;
|
|
160
161
|
|
|
161
162
|
static void context_destroy(Context *c);
|
|
163
|
+
static void context_abandon(Context *c);
|
|
162
164
|
static void context_free(void *arg);
|
|
163
165
|
static void context_mark(void *arg);
|
|
164
166
|
static size_t context_size(const void *arg);
|
|
@@ -227,6 +229,7 @@ struct rendezvous_nogvl
|
|
|
227
229
|
{
|
|
228
230
|
Context *context;
|
|
229
231
|
Buf *req, *res;
|
|
232
|
+
atomic_int active;
|
|
230
233
|
};
|
|
231
234
|
|
|
232
235
|
struct rendezvous_des
|
|
@@ -870,8 +873,14 @@ void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
|
|
|
870
873
|
{
|
|
871
874
|
buf_reset(&c->req);
|
|
872
875
|
pthread_cond_signal(&c->cv);
|
|
873
|
-
while (!c->req.len)
|
|
876
|
+
while (!c->req.len && !atomic_load(&c->quit))
|
|
874
877
|
pthread_cond_wait(&c->cv, &c->mtx);
|
|
878
|
+
if (!c->req.len && atomic_load(&c->quit)) {
|
|
879
|
+
static const uint8_t disposed[] = "edisposed context";
|
|
880
|
+
*p = disposed;
|
|
881
|
+
*n = sizeof(disposed) - 1;
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
875
884
|
buf_reset(&c->res);
|
|
876
885
|
*p = c->req.buf;
|
|
877
886
|
*n = c->req.len;
|
|
@@ -1007,6 +1016,33 @@ static void *single_threaded_runner(void *arg)
|
|
|
1007
1016
|
return NULL;
|
|
1008
1017
|
}
|
|
1009
1018
|
|
|
1019
|
+
static int single_threaded_recover_after_fork(Context *c)
|
|
1020
|
+
{
|
|
1021
|
+
pthread_condattr_t cattr;
|
|
1022
|
+
pid_t pid;
|
|
1023
|
+
int r;
|
|
1024
|
+
|
|
1025
|
+
pid = getpid();
|
|
1026
|
+
if (!c->single_threaded_thr_started || c->single_threaded_pid == pid)
|
|
1027
|
+
return 0;
|
|
1028
|
+
if (c->depth || c->req.len || c->res.len)
|
|
1029
|
+
return EBUSY;
|
|
1030
|
+
|
|
1031
|
+
if ((r = pthread_condattr_init(&cattr)))
|
|
1032
|
+
return r;
|
|
1033
|
+
#ifndef __APPLE__
|
|
1034
|
+
pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
|
|
1035
|
+
#endif
|
|
1036
|
+
r = pthread_cond_init(&c->cv, &cattr);
|
|
1037
|
+
pthread_condattr_destroy(&cattr);
|
|
1038
|
+
if (r)
|
|
1039
|
+
return r;
|
|
1040
|
+
|
|
1041
|
+
c->single_threaded_thr_started = 0;
|
|
1042
|
+
c->single_threaded_pid = pid;
|
|
1043
|
+
return 0;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1010
1046
|
static int single_threaded_runner_start(Context *c)
|
|
1011
1047
|
{
|
|
1012
1048
|
pid_t pid;
|
|
@@ -1031,7 +1067,10 @@ static inline void *rendezvous_nogvl(void *arg)
|
|
|
1031
1067
|
|
|
1032
1068
|
a = arg;
|
|
1033
1069
|
c = a->context;
|
|
1070
|
+
if (single_threaded && (r = single_threaded_recover_after_fork(c)))
|
|
1071
|
+
return (void *)(intptr_t)r;
|
|
1034
1072
|
pthread_mutex_lock(&c->rr_mtx);
|
|
1073
|
+
atomic_store(&a->active, 1);
|
|
1035
1074
|
if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
|
|
1036
1075
|
fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
|
|
1037
1076
|
fflush(stderr);
|
|
@@ -1039,6 +1078,14 @@ static inline void *rendezvous_nogvl(void *arg)
|
|
|
1039
1078
|
c->depth++;
|
|
1040
1079
|
next:
|
|
1041
1080
|
pthread_mutex_lock(&c->mtx);
|
|
1081
|
+
if (atomic_load(&c->quit)) {
|
|
1082
|
+
buf_reset(a->req);
|
|
1083
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1084
|
+
c->depth--;
|
|
1085
|
+
atomic_store(&a->active, 0);
|
|
1086
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1087
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1088
|
+
}
|
|
1042
1089
|
assert(c->req.len == 0);
|
|
1043
1090
|
assert(c->res.len == 0);
|
|
1044
1091
|
buf_move(a->req, &c->req); // v8 thread takes ownership of req
|
|
@@ -1048,6 +1095,7 @@ next:
|
|
|
1048
1095
|
buf_move(&c->req, a->req);
|
|
1049
1096
|
pthread_mutex_unlock(&c->mtx);
|
|
1050
1097
|
c->depth--;
|
|
1098
|
+
atomic_store(&a->active, 0);
|
|
1051
1099
|
pthread_mutex_unlock(&c->rr_mtx);
|
|
1052
1100
|
return (void *)(intptr_t)r;
|
|
1053
1101
|
}
|
|
@@ -1058,29 +1106,66 @@ next:
|
|
|
1058
1106
|
do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
|
|
1059
1107
|
}
|
|
1060
1108
|
buf_move(&c->res, a->res);
|
|
1109
|
+
pthread_cond_broadcast(&c->cv);
|
|
1061
1110
|
pthread_mutex_unlock(&c->mtx);
|
|
1062
1111
|
if (*a->res->buf == 'c') { // js -> ruby callback?
|
|
1063
1112
|
rb_thread_call_with_gvl(rendezvous_callback, a);
|
|
1064
1113
|
buf_reset(a->res);
|
|
1114
|
+
if (atomic_load(&c->quit)) {
|
|
1115
|
+
buf_reset(a->req);
|
|
1116
|
+
c->depth--;
|
|
1117
|
+
atomic_store(&a->active, 0);
|
|
1118
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1119
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1120
|
+
}
|
|
1065
1121
|
goto next;
|
|
1066
1122
|
}
|
|
1067
1123
|
c->depth--;
|
|
1124
|
+
atomic_store(&a->active, 0);
|
|
1068
1125
|
pthread_mutex_unlock(&c->rr_mtx);
|
|
1069
1126
|
return NULL;
|
|
1070
1127
|
}
|
|
1071
1128
|
|
|
1129
|
+
static void rendezvous_ubf(void *arg)
|
|
1130
|
+
{
|
|
1131
|
+
struct rendezvous_nogvl *a;
|
|
1132
|
+
Context *c;
|
|
1133
|
+
|
|
1134
|
+
a = arg;
|
|
1135
|
+
if (!atomic_load(&a->active))
|
|
1136
|
+
return;
|
|
1137
|
+
c = a->context;
|
|
1138
|
+
if (c->pst)
|
|
1139
|
+
v8_terminate_execution(c->pst);
|
|
1140
|
+
pthread_cond_broadcast(&c->cv);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
static void terminate_ubf(void *arg)
|
|
1144
|
+
{
|
|
1145
|
+
Context *c;
|
|
1146
|
+
|
|
1147
|
+
c = arg;
|
|
1148
|
+
if (c->pst)
|
|
1149
|
+
v8_terminate_execution(c->pst);
|
|
1150
|
+
pthread_cond_broadcast(&c->cv);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1072
1153
|
static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
|
|
1073
1154
|
{
|
|
1074
1155
|
void *r;
|
|
1156
|
+
struct rendezvous_nogvl a;
|
|
1075
1157
|
|
|
1076
1158
|
if (atomic_load(&c->quit)) {
|
|
1077
1159
|
buf_reset(req);
|
|
1078
1160
|
rb_raise(context_disposed_error, "disposed context");
|
|
1079
1161
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1162
|
+
a.context = c;
|
|
1163
|
+
a.req = req;
|
|
1164
|
+
a.res = res;
|
|
1165
|
+
atomic_init(&a.active, 0);
|
|
1166
|
+
r = rb_nogvl(rendezvous_nogvl, &a, rendezvous_ubf, &a, 0);
|
|
1082
1167
|
if (r)
|
|
1083
|
-
rb_raise(runtime_error, "
|
|
1168
|
+
rb_raise(runtime_error, "single-threaded runner: %s", strerror((int)(intptr_t)r));
|
|
1084
1169
|
}
|
|
1085
1170
|
|
|
1086
1171
|
// send request to & receive reply from v8 thread; takes ownership of |req|
|
|
@@ -1096,7 +1181,8 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
|
|
|
1096
1181
|
c->exception = Qnil;
|
|
1097
1182
|
// if js land didn't handle exception from ruby callback, re-raise it now
|
|
1098
1183
|
if (res.len == 1 && *res.buf == 'e') {
|
|
1099
|
-
|
|
1184
|
+
if (NIL_P(r))
|
|
1185
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
1100
1186
|
rb_exc_raise(r);
|
|
1101
1187
|
}
|
|
1102
1188
|
r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
|
|
@@ -1251,11 +1337,19 @@ fail0:
|
|
|
1251
1337
|
return Qnil; // pacify compiler
|
|
1252
1338
|
}
|
|
1253
1339
|
|
|
1254
|
-
static void *
|
|
1340
|
+
static void *context_free_do(void *arg)
|
|
1255
1341
|
{
|
|
1256
1342
|
Context *c;
|
|
1257
1343
|
|
|
1258
1344
|
c = arg;
|
|
1345
|
+
if (single_threaded && single_threaded_recover_after_fork(c)) {
|
|
1346
|
+
// The child forked while this inherited context was not idle. There is
|
|
1347
|
+
// no live runner thread to join and the inherited V8/pthread state is
|
|
1348
|
+
// not safe to tear down. A finalizer must not hang here; let the OS
|
|
1349
|
+
// reclaim the abandoned V8 state when the child exits.
|
|
1350
|
+
context_abandon(c);
|
|
1351
|
+
return NULL;
|
|
1352
|
+
}
|
|
1259
1353
|
if (single_threaded && c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1260
1354
|
pthread_mutex_lock(&c->mtx);
|
|
1261
1355
|
atomic_store(&c->quit, 2);
|
|
@@ -1270,31 +1364,16 @@ static void *context_free_thread_do(void *arg)
|
|
|
1270
1364
|
return NULL;
|
|
1271
1365
|
}
|
|
1272
1366
|
|
|
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
1367
|
static void context_free(void *arg)
|
|
1292
1368
|
{
|
|
1293
1369
|
Context *c;
|
|
1294
1370
|
|
|
1295
1371
|
c = arg;
|
|
1296
1372
|
if (single_threaded) {
|
|
1297
|
-
|
|
1373
|
+
// Free synchronously. A detached cleanup thread can race normal Ruby
|
|
1374
|
+
// process shutdown and trip glibc malloc corruption checks while V8 is
|
|
1375
|
+
// tearing down single-threaded contexts.
|
|
1376
|
+
context_free_do(c);
|
|
1298
1377
|
} else {
|
|
1299
1378
|
pthread_mutex_lock(&c->mtx);
|
|
1300
1379
|
c->quit = 2; // 2 = v8 thread frees
|
|
@@ -1303,6 +1382,14 @@ static void context_free(void *arg)
|
|
|
1303
1382
|
}
|
|
1304
1383
|
}
|
|
1305
1384
|
|
|
1385
|
+
static void context_abandon(Context *c)
|
|
1386
|
+
{
|
|
1387
|
+
buf_reset(&c->snapshot);
|
|
1388
|
+
buf_reset(&c->req);
|
|
1389
|
+
buf_reset(&c->res);
|
|
1390
|
+
ruby_xfree(c);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1306
1393
|
static void context_destroy(Context *c)
|
|
1307
1394
|
{
|
|
1308
1395
|
pthread_mutex_unlock(&c->mtx);
|
|
@@ -1360,8 +1447,25 @@ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
|
|
|
1360
1447
|
static void *context_dispose_do(void *arg)
|
|
1361
1448
|
{
|
|
1362
1449
|
Context *c;
|
|
1450
|
+
int r;
|
|
1363
1451
|
|
|
1364
1452
|
c = arg;
|
|
1453
|
+
if (single_threaded) {
|
|
1454
|
+
if ((r = single_threaded_recover_after_fork(c)))
|
|
1455
|
+
return (void *)(intptr_t)r;
|
|
1456
|
+
}
|
|
1457
|
+
if (c->depth > 0) {
|
|
1458
|
+
r = pthread_mutex_trylock(&c->rr_mtx);
|
|
1459
|
+
if (!r) {
|
|
1460
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1461
|
+
return (void *)(intptr_t)EBUSY;
|
|
1462
|
+
}
|
|
1463
|
+
if (r != EBUSY)
|
|
1464
|
+
return (void *)(intptr_t)r;
|
|
1465
|
+
if (c->pst)
|
|
1466
|
+
v8_terminate_execution(c->pst);
|
|
1467
|
+
pthread_cond_broadcast(&c->cv);
|
|
1468
|
+
}
|
|
1365
1469
|
if (single_threaded) {
|
|
1366
1470
|
pthread_mutex_lock(&c->mtx);
|
|
1367
1471
|
while (c->req.len || c->res.len)
|
|
@@ -1389,9 +1493,12 @@ static void *context_dispose_do(void *arg)
|
|
|
1389
1493
|
static VALUE context_dispose(VALUE self)
|
|
1390
1494
|
{
|
|
1391
1495
|
Context *c;
|
|
1496
|
+
void *r;
|
|
1392
1497
|
|
|
1393
1498
|
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1394
|
-
rb_thread_call_without_gvl(context_dispose_do, c,
|
|
1499
|
+
r = rb_thread_call_without_gvl(context_dispose_do, c, terminate_ubf, c);
|
|
1500
|
+
if (r)
|
|
1501
|
+
rb_raise(runtime_error, "context dispose: %s", strerror((int)(intptr_t)r));
|
|
1395
1502
|
return Qnil;
|
|
1396
1503
|
}
|
|
1397
1504
|
|
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.3
|
|
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.3/CHANGELOG
|
|
140
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.3
|
|
141
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.3
|
|
142
142
|
rdoc_options: []
|
|
143
143
|
require_paths:
|
|
144
144
|
- lib
|