mini_racer 0.21.0 → 0.21.1
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 +5 -0
- data/README.md +9 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +80 -16
- data/lib/mini_racer/truffleruby.rb +31 -20
- 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: 6c838b1bc35ed1adb0156f0e01d699afc180c174718f197a6afc1356fe9e4815
|
|
4
|
+
data.tar.gz: 7a4b00c4bcd4572bf7fdcce3df6455ded628706975fa8540946faee17e53617d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d826ca742dbe6580f92faebf11c827795536c55596c279fa070dd38836d03b7dcecc0b43fe254b6d95590bea1a24d8ec168f9d5008b668ba488a5aff7b6e3aea
|
|
7
|
+
data.tar.gz: 985effeb709d8dfc1481c679dfdc86a3930a0d84de6d3b6d9d154d6801a6766af76ffecd468e22d8b20b68952e02b756ab0ec11b1bc3c93736b078d046719798
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
- 0.21.1 - 25-05-2026
|
|
2
|
+
- Run `:single_threaded` V8 dispatches on a reusable mini_racer-owned native thread so V8 does not execute on Ruby-owned threads
|
|
3
|
+
- Stop and join the reusable `:single_threaded` runner when contexts are disposed
|
|
4
|
+
- Document `:single_threaded` fork-safety requirements for pre-fork contexts
|
|
5
|
+
|
|
1
6
|
- 0.21.0 - 16-04-2026
|
|
2
7
|
- Add MiniRacer::Binary for returning Uint8Array to JavaScript from attached Ruby callbacks
|
|
3
8
|
|
data/README.md
CHANGED
|
@@ -139,6 +139,15 @@ Since 0.6.1 mini_racer does support V8 single threaded platform mode which shoul
|
|
|
139
139
|
MiniRacer::Platform.set_flags!(:single_threaded)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
When using pre-fork `MiniRacer::Context` objects in `:single_threaded` mode,
|
|
143
|
+
ensure the process only forks while MiniRacer is quiescent: no thread may be
|
|
144
|
+
evaluating JavaScript, calling into a context, disposing/freeing a context,
|
|
145
|
+
running a Ruby callback from JavaScript, or otherwise using MiniRacer at the
|
|
146
|
+
instant of `fork`. In multi-threaded applications, guard all MiniRacer context
|
|
147
|
+
operations and the `fork` itself with the same application-level lock. Forking
|
|
148
|
+
while a MiniRacer operation is in progress can leave inherited pthread mutexes
|
|
149
|
+
in an unusable state in the child process.
|
|
150
|
+
|
|
142
151
|
If you want to ensure your application does not leak memory after fork either:
|
|
143
152
|
|
|
144
153
|
1. Ensure no `MiniRacer::Context` objects are created in the master process; or
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <stdlib.h>
|
|
5
5
|
#include <string.h>
|
|
6
6
|
#include <pthread.h>
|
|
7
|
+
#include <unistd.h>
|
|
7
8
|
#include <math.h>
|
|
8
9
|
|
|
9
10
|
#if defined(__linux__) && !defined(__GLIBC__)
|
|
@@ -136,6 +137,9 @@ typedef struct Context
|
|
|
136
137
|
VALUE exception; // pending exception or Qnil
|
|
137
138
|
Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
|
|
138
139
|
Buf snapshot;
|
|
140
|
+
pthread_t single_threaded_thr;
|
|
141
|
+
pid_t single_threaded_pid;
|
|
142
|
+
int single_threaded_thr_started;
|
|
139
143
|
// |rr_mtx| stands for "recursive ruby mutex"; it's used to exclude
|
|
140
144
|
// other ruby threads but allow reentrancy from the same ruby thread
|
|
141
145
|
// (think ruby->js->ruby->js calls)
|
|
@@ -868,18 +872,10 @@ void v8_dispatch(Context *c)
|
|
|
868
872
|
// only called when inside v8_call, v8_eval, or v8_pump_message_loop
|
|
869
873
|
void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
|
|
870
874
|
{
|
|
871
|
-
struct rendezvous_nogvl *args;
|
|
872
|
-
|
|
873
875
|
buf_reset(&c->req);
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
rb_thread_call_with_gvl(rendezvous_callback, args);
|
|
878
|
-
} else {
|
|
879
|
-
pthread_cond_signal(&c->cv);
|
|
880
|
-
while (!c->req.len)
|
|
881
|
-
pthread_cond_wait(&c->cv, &c->mtx);
|
|
882
|
-
}
|
|
876
|
+
pthread_cond_signal(&c->cv);
|
|
877
|
+
while (!c->req.len)
|
|
878
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
883
879
|
buf_reset(&c->res);
|
|
884
880
|
*p = c->req.buf;
|
|
885
881
|
*n = c->req.len;
|
|
@@ -991,10 +987,45 @@ fail:
|
|
|
991
987
|
goto out;
|
|
992
988
|
}
|
|
993
989
|
|
|
990
|
+
static void *single_threaded_runner(void *arg)
|
|
991
|
+
{
|
|
992
|
+
Context *c;
|
|
993
|
+
|
|
994
|
+
c = arg;
|
|
995
|
+
pthread_mutex_lock(&c->mtx);
|
|
996
|
+
for (;;) {
|
|
997
|
+
while (!c->req.len && atomic_load(&c->quit) < 1)
|
|
998
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
999
|
+
if (atomic_load(&c->quit) >= 1)
|
|
1000
|
+
break;
|
|
1001
|
+
v8_single_threaded_enter(c->pst, c, dispatch);
|
|
1002
|
+
pthread_cond_signal(&c->cv);
|
|
1003
|
+
}
|
|
1004
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1005
|
+
return NULL;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
static int single_threaded_runner_start(Context *c)
|
|
1009
|
+
{
|
|
1010
|
+
pid_t pid;
|
|
1011
|
+
int r;
|
|
1012
|
+
|
|
1013
|
+
pid = getpid();
|
|
1014
|
+
if (c->single_threaded_thr_started && c->single_threaded_pid == pid)
|
|
1015
|
+
return 0;
|
|
1016
|
+
c->single_threaded_thr_started = 0;
|
|
1017
|
+
c->single_threaded_pid = pid;
|
|
1018
|
+
r = pthread_create(&c->single_threaded_thr, NULL, single_threaded_runner, c);
|
|
1019
|
+
if (!r)
|
|
1020
|
+
c->single_threaded_thr_started = 1;
|
|
1021
|
+
return r;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
994
1024
|
static inline void *rendezvous_nogvl(void *arg)
|
|
995
1025
|
{
|
|
996
1026
|
struct rendezvous_nogvl *a;
|
|
997
1027
|
Context *c;
|
|
1028
|
+
int r;
|
|
998
1029
|
|
|
999
1030
|
a = arg;
|
|
1000
1031
|
c = a->context;
|
|
@@ -1010,7 +1041,16 @@ next:
|
|
|
1010
1041
|
assert(c->res.len == 0);
|
|
1011
1042
|
buf_move(a->req, &c->req); // v8 thread takes ownership of req
|
|
1012
1043
|
if (single_threaded) {
|
|
1013
|
-
|
|
1044
|
+
r = single_threaded_runner_start(c);
|
|
1045
|
+
if (r) {
|
|
1046
|
+
buf_move(&c->req, a->req);
|
|
1047
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1048
|
+
c->depth--;
|
|
1049
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1050
|
+
return (void *)(intptr_t)r;
|
|
1051
|
+
}
|
|
1052
|
+
pthread_cond_signal(&c->cv);
|
|
1053
|
+
do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
|
|
1014
1054
|
} else {
|
|
1015
1055
|
pthread_cond_signal(&c->cv);
|
|
1016
1056
|
do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
|
|
@@ -1019,6 +1059,7 @@ next:
|
|
|
1019
1059
|
pthread_mutex_unlock(&c->mtx);
|
|
1020
1060
|
if (*a->res->buf == 'c') { // js -> ruby callback?
|
|
1021
1061
|
rb_thread_call_with_gvl(rendezvous_callback, a);
|
|
1062
|
+
buf_reset(a->res);
|
|
1022
1063
|
goto next;
|
|
1023
1064
|
}
|
|
1024
1065
|
c->depth--;
|
|
@@ -1028,12 +1069,16 @@ next:
|
|
|
1028
1069
|
|
|
1029
1070
|
static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
|
|
1030
1071
|
{
|
|
1072
|
+
void *r;
|
|
1073
|
+
|
|
1031
1074
|
if (atomic_load(&c->quit)) {
|
|
1032
1075
|
buf_reset(req);
|
|
1033
1076
|
rb_raise(context_disposed_error, "disposed context");
|
|
1034
1077
|
}
|
|
1035
|
-
rb_nogvl(rendezvous_nogvl, &(struct rendezvous_nogvl){c, req, res},
|
|
1036
|
-
|
|
1078
|
+
r = rb_nogvl(rendezvous_nogvl, &(struct rendezvous_nogvl){c, req, res},
|
|
1079
|
+
NULL, NULL, 0);
|
|
1080
|
+
if (r)
|
|
1081
|
+
rb_raise(runtime_error, "pthread_create: %s", strerror((int)(intptr_t)r));
|
|
1037
1082
|
}
|
|
1038
1083
|
|
|
1039
1084
|
// send request to & receive reply from v8 thread; takes ownership of |req|
|
|
@@ -1190,7 +1235,16 @@ static void *context_free_thread_do(void *arg)
|
|
|
1190
1235
|
Context *c;
|
|
1191
1236
|
|
|
1192
1237
|
c = arg;
|
|
1193
|
-
|
|
1238
|
+
if (single_threaded && c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1239
|
+
pthread_mutex_lock(&c->mtx);
|
|
1240
|
+
atomic_store(&c->quit, 2);
|
|
1241
|
+
pthread_cond_signal(&c->cv);
|
|
1242
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1243
|
+
pthread_join(c->single_threaded_thr, NULL);
|
|
1244
|
+
}
|
|
1245
|
+
if (c->pst)
|
|
1246
|
+
v8_single_threaded_dispose(c->pst);
|
|
1247
|
+
pthread_mutex_lock(&c->mtx);
|
|
1194
1248
|
context_destroy(c);
|
|
1195
1249
|
return NULL;
|
|
1196
1250
|
}
|
|
@@ -1284,8 +1338,18 @@ static void *context_dispose_do(void *arg)
|
|
|
1284
1338
|
|
|
1285
1339
|
c = arg;
|
|
1286
1340
|
if (single_threaded) {
|
|
1341
|
+
pthread_mutex_lock(&c->mtx);
|
|
1342
|
+
while (c->req.len || c->res.len)
|
|
1343
|
+
pthread_cond_wait(&c->cv, &c->mtx);
|
|
1287
1344
|
atomic_store(&c->quit, 1); // disposed
|
|
1288
|
-
|
|
1345
|
+
if (c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1346
|
+
pthread_cond_signal(&c->cv);
|
|
1347
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1348
|
+
pthread_join(c->single_threaded_thr, NULL);
|
|
1349
|
+
pthread_mutex_lock(&c->mtx);
|
|
1350
|
+
c->single_threaded_thr_started = 0;
|
|
1351
|
+
}
|
|
1352
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1289
1353
|
} else {
|
|
1290
1354
|
pthread_mutex_lock(&c->mtx);
|
|
1291
1355
|
while (c->req.len || c->res.len)
|
|
@@ -77,9 +77,13 @@ module MiniRacer
|
|
|
77
77
|
raise "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version"
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
|
|
81
|
+
if TruffleRuby.native?
|
|
82
|
+
raise "You need the TruffleRuby JVM Standalone for mini_racer because it is not possible to install the js component in the the Native standalone"
|
|
83
|
+
end
|
|
84
|
+
|
|
80
85
|
unless Polyglot.languages.include? "js"
|
|
81
|
-
raise "The language 'js' is not available, you
|
|
82
|
-
"You also need to install the 'js' component, see https://github.com/oracle/truffleruby/blob/master/doc/user/polyglot.md#installing-other-languages"
|
|
86
|
+
raise "The language 'js' is not available, you need to install the 'js' component.\nSee https://github.com/oracle/truffleruby/blob/master/doc/user/polyglot.md#installing-other-languages"
|
|
83
87
|
end
|
|
84
88
|
|
|
85
89
|
@context = Polyglot::InnerContext.new(on_cancelled: -> {
|
|
@@ -102,20 +106,19 @@ module MiniRacer
|
|
|
102
106
|
else
|
|
103
107
|
@snapshot = nil
|
|
104
108
|
end
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
CODE
|
|
109
|
+
|
|
110
|
+
@is_object_or_array_func = eval_in_context "(x) => { return (x instanceof Object || x instanceof Array) && !(x instanceof Date) && !(x instanceof Function) }"
|
|
111
|
+
@is_map_func = eval_in_context "(x) => { return x instanceof Map }"
|
|
112
|
+
@is_map_iterator_func = eval_in_context "(x) => { return x[Symbol.toStringTag] === 'Map Iterator' }"
|
|
113
|
+
@is_time_func = eval_in_context "(x) => { return x instanceof Date }"
|
|
114
|
+
@is_symbol_func = eval_in_context "(x) => { return typeof x === 'symbol' }"
|
|
115
|
+
@is_uint8_array_func = eval_in_context "(x) => { return x instanceof Uint8Array }"
|
|
116
|
+
|
|
117
|
+
@js_date_to_time_func = eval_in_context "(x) => { return x.getTime(x) }"
|
|
118
|
+
@js_symbol_to_symbol_func = eval_in_context "(x) => { var r = x.description; return r === undefined ? 'undefined' : r }"
|
|
119
|
+
@js_new_date_func = eval_in_context "(x) => { return new Date(x) }"
|
|
120
|
+
@js_new_array_func = eval_in_context "(x) => { return new Array(x) }"
|
|
121
|
+
@js_new_uint8array_func = eval_in_context "(x) => { return new Uint8Array(x) }"
|
|
119
122
|
end
|
|
120
123
|
|
|
121
124
|
def dispose_unsafe
|
|
@@ -232,6 +235,10 @@ module MiniRacer
|
|
|
232
235
|
elsif value.respond_to?(:to_str)
|
|
233
236
|
value.to_str.dup
|
|
234
237
|
elsif value.respond_to?(:to_ary)
|
|
238
|
+
if uint8_array?(value)
|
|
239
|
+
return value.to_a.pack('C*')
|
|
240
|
+
end
|
|
241
|
+
|
|
235
242
|
value.to_ary.map do |e|
|
|
236
243
|
if e.respond_to?(:call)
|
|
237
244
|
nil
|
|
@@ -281,15 +288,19 @@ module MiniRacer
|
|
|
281
288
|
@is_time_func.call(value)
|
|
282
289
|
end
|
|
283
290
|
|
|
291
|
+
def symbol?(value)
|
|
292
|
+
@is_symbol_func.call(value)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def uint8_array?(value)
|
|
296
|
+
@is_uint8_array_func.call(value)
|
|
297
|
+
end
|
|
298
|
+
|
|
284
299
|
def js_date_to_time(value)
|
|
285
300
|
millis = @js_date_to_time_func.call(value)
|
|
286
301
|
Time.at(Rational(millis, 1000))
|
|
287
302
|
end
|
|
288
303
|
|
|
289
|
-
def symbol?(value)
|
|
290
|
-
@is_symbol_func.call(value)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
304
|
def js_symbol_to_symbol(value)
|
|
294
305
|
@js_symbol_to_symbol_func.call(value).to_s.to_sym
|
|
295
306
|
end
|
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.1
|
|
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.1/CHANGELOG
|
|
140
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.1
|
|
141
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.1
|
|
142
142
|
rdoc_options: []
|
|
143
143
|
require_paths:
|
|
144
144
|
- lib
|