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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cd44fe0925a58e264712d933d805af54b517d31cdbefc26455e7e139e234279
4
- data.tar.gz: 17190bc083b885c838b0dfc3424c3fc08fb02df354972a3007a292ddf70afc22
3
+ metadata.gz: 6c838b1bc35ed1adb0156f0e01d699afc180c174718f197a6afc1356fe9e4815
4
+ data.tar.gz: 7a4b00c4bcd4572bf7fdcce3df6455ded628706975fa8540946faee17e53617d
5
5
  SHA512:
6
- metadata.gz: 9cdd4cdf20f9be0e3ceb34daa48f7f1eeb93c4c11eada10c23e2c3e3dbd82cf7a13c8102e67140cc15c028c51a468c6e74ac518c76050cadceb33aa99fd71b7d
7
- data.tar.gz: c7110aabb771e18bccd85283696e8f811970cf8d03bb0ef1f573a84f2fa7da2ef1014b5138513b09d80825de96ddebe7e6316f79efbe8adecbd1ff202b14e4d5
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
- if (single_threaded) {
875
- assert(*c->res.buf == 'c'); // js -> ruby callback
876
- args = &(struct rendezvous_nogvl){c, &c->req, &c->res};
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
- v8_single_threaded_enter(c->pst, c, dispatch);
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
- NULL, NULL, 0);
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
- v8_single_threaded_dispose(c->pst);
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
- // intentionally a no-op for now
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 likely need to `export TRUFFLERUBYOPT='--jvm --polyglot'`\n" \
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
- @is_object_or_array_func, @is_map_func, @is_map_iterator_func, @is_time_func, @js_date_to_time_func, @is_symbol_func, @js_symbol_to_symbol_func, @js_new_date_func, @js_new_array_func, @js_new_uint8array_func = eval_in_context <<-CODE
106
- [
107
- (x) => { return (x instanceof Object || x instanceof Array) && !(x instanceof Date) && !(x instanceof Function) },
108
- (x) => { return x instanceof Map },
109
- (x) => { return x[Symbol.toStringTag] === 'Map Iterator' },
110
- (x) => { return x instanceof Date },
111
- (x) => { return x.getTime(x) },
112
- (x) => { return typeof x === 'symbol' },
113
- (x) => { var r = x.description; return r === undefined ? 'undefined' : r },
114
- (x) => { return new Date(x) },
115
- (x) => { return new Array(x) },
116
- (x) => { return new Uint8Array(x) },
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacer
4
- VERSION = "0.21.0"
4
+ VERSION = "0.21.1"
5
5
  LIBV8_NODE_VERSION = "~> 24.12.0.1"
6
6
  end
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.0
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.0/CHANGELOG
140
- documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.0
141
- source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.0
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