mini_racer 0.2.10 → 0.2.15

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: a50c6aea93635efc351ce95def252df6edb3fbede6a0372272f8c9cd58f9aaed
4
- data.tar.gz: b01cd6a98eca13db18d539ed259e4acd968eb073582979ff06f78a5df54a4207
3
+ metadata.gz: 359264fa5fcef999d505e7a76075d98d08310e88c4bdc983c9e3ce87122a990c
4
+ data.tar.gz: 46444c7126d86d41a78a8c44c97f31433eff7e4c7551ba46a7b19e7f2b64100c
5
5
  SHA512:
6
- metadata.gz: cac0258ac9f1f93245b00b313d004504d770e9dce3eb5ac9a8d547a1b24cd3649ec9039d9c7429ff7871f9f8d3c07e260997d86647905ba433688f361bd72611
7
- data.tar.gz: b054a38013e7bcffe689c3732d7d95abbaf61c350dab275ff7e04e667bc5f15ae9863a706beb2848681b8d69cc1194c0bb87014a4c49e2f49d14a4289b1f6436
6
+ metadata.gz: 6c682ae187454565667c71132a39eed4b179b642d34cf9e48d9a9333973394dbf368000758b89dc721ee3e264708e44cc6812f5530afc3c0aad976697a7fe9a7
7
+ data.tar.gz: cc032e45e917c57ac329813344dd241dc12ba95b02b5088d0d945a0bf15ea0db8feb9bb147caf1efc8795d1abfec6e321cc16798ea1c554789d26ee9d44cd389
data/CHANGELOG CHANGED
@@ -1,3 +1,36 @@
1
+ - 29-06-2020
2
+
3
+ - 0.2.15
4
+
5
+ - FEATURE: basic wasm support via pump_message_loop
6
+
7
+ - 15-05-2020
8
+
9
+ - 0.2.14
10
+
11
+ - FIX: ensure_gc_after_idle should take in milliseconds like the rest of the APIs not seconds
12
+ - FEATURE: strict params on MiniRacer::Context.new
13
+
14
+ - 15-05-2020
15
+
16
+ - 0.2.13
17
+
18
+ - FIX: edge case around ensure_gc_after_idle possibly firing when context is not idle
19
+
20
+ - 15-05-2020
21
+
22
+ - 0.2.12
23
+
24
+ - FEATURE: isolate.low_memory_notification which can force a full GC
25
+ - FEATURE: MiniRacer::Context.new(ensure_gc_after_idle: 2) - to force full GC 2 seconds after context is idle, this allows you to conserve memory on isolates
26
+
27
+ - 14-05-2020
28
+
29
+ - 0.2.11
30
+
31
+ - FIX: dumping heap snapshots was not flushing the file leading to corrupt snapshots
32
+ - FIX: a use-after-free shutdown crash
33
+
1
34
  - 0.2.10
2
35
 
3
36
  - 22-04-2020
data/README.md CHANGED
@@ -230,12 +230,17 @@ context = MiniRacer::Context.new(isolate: isolate)
230
230
  # give up to 100ms for V8 garbage collection
231
231
  isolate.idle_notification(100)
232
232
 
233
+ # force V8 to perform a full GC
234
+ isolate.low_memory_notification
235
+
233
236
  ```
234
237
 
235
238
  This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
236
239
 
237
240
  Note that this method maps directly to [`v8::Isolate::IdleNotification`](http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#aea16cbb2e351de9a3ae7be2b7cb48297), and that in particular its return value is the same (true if there is no further garbage to collect, false otherwise) and the same caveats apply, in particular that `there is no guarantee that the [call will return] within the time limit.`
238
241
 
242
+ Additionally you may automate this process on a context by defining it with `MiniRacer::Content.new(ensure_gc_after_idle: 1000)`. Using this will ensure V8 will run a full GC using `context.isolate.low_memory_notification` 1 second after the last eval on the context. Low memory notification is both slower and more aggressive than an idle_notification and will ensure long living isolates use minimal amounts of memory.
243
+
239
244
  ### V8 Runtime flags
240
245
 
241
246
  It is possible to set V8 Runtime flags:
@@ -310,6 +315,16 @@ context.eval("a = 2")
310
315
  # nothing works on the context from now on, its a shell waiting to be disposed
311
316
  ```
312
317
 
318
+ A MiniRacer context can also be dumped in a heapsnapshot file using `#write_heap_snapshot(file_or_io)`
319
+
320
+ ```ruby
321
+ context = MiniRacer::Context.new(timeout: 5)
322
+ context.eval("let a='testing';")
323
+ context.write_heap_snapshot("test.heapsnapshot")
324
+ ```
325
+
326
+ This file can then be loaded in the memory tab of the chrome dev console.
327
+
313
328
  ### Function call
314
329
 
315
330
  This calls the function passed as first argument:
@@ -156,6 +156,10 @@ static VALUE rb_cDateTime = Qnil;
156
156
  static std::unique_ptr<Platform> current_platform = NULL;
157
157
  static std::mutex platform_lock;
158
158
 
159
+ static pthread_attr_t *thread_attr_p;
160
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
161
+ static bool ruby_exiting; // guarded by exit_lock
162
+
159
163
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
160
164
  bool platform_already_initialized = false;
161
165
 
@@ -765,6 +769,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
765
769
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
766
770
  }
767
771
 
772
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
773
+ IsolateInfo* isolate_info;
774
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
775
+
776
+ if (current_platform == NULL) return Qfalse;
777
+
778
+ isolate_info->isolate->LowMemoryNotification();
779
+ return Qnil;
780
+ }
781
+
782
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
783
+ IsolateInfo* isolate_info;
784
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
785
+
786
+ if (current_platform == NULL) return Qfalse;
787
+
788
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
789
+ return Qtrue;
790
+ } else {
791
+ return Qfalse;
792
+ }
793
+ }
794
+
768
795
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
769
796
  ContextInfo* context_info;
770
797
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -1205,7 +1232,7 @@ void free_isolate(IsolateInfo* isolate_info) {
1205
1232
  delete isolate_info->allocator;
1206
1233
  }
1207
1234
 
1208
- static void *free_context_raw(void* arg) {
1235
+ static void free_context_raw(void *arg) {
1209
1236
  ContextInfo* context_info = (ContextInfo*)arg;
1210
1237
  IsolateInfo* isolate_info = context_info->isolate_info;
1211
1238
  Persistent<Context>* context = context_info->context;
@@ -1222,6 +1249,20 @@ static void *free_context_raw(void* arg) {
1222
1249
  }
1223
1250
 
1224
1251
  xfree(context_info);
1252
+ }
1253
+
1254
+ static void *free_context_thr(void* arg) {
1255
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1256
+ return NULL;
1257
+ }
1258
+ if (ruby_exiting) {
1259
+ return NULL;
1260
+ }
1261
+
1262
+ free_context_raw(arg);
1263
+
1264
+ pthread_rwlock_unlock(&exit_lock);
1265
+
1225
1266
  return NULL;
1226
1267
  }
1227
1268
 
@@ -1235,22 +1276,17 @@ static void free_context(ContextInfo* context_info) {
1235
1276
  context_info_copy->context = context_info->context;
1236
1277
 
1237
1278
  if (isolate_info && isolate_info->refs() > 1) {
1238
- pthread_t free_context_thread;
1239
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1240
- fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1241
- }
1242
-
1279
+ pthread_t free_context_thread;
1280
+ if (pthread_create(&free_context_thread, thread_attr_p,
1281
+ free_context_thr, (void*)context_info_copy)) {
1282
+ fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1283
+ }
1243
1284
  } else {
1244
1285
  free_context_raw(context_info_copy);
1245
1286
  }
1246
1287
 
1247
- if (context_info->context && isolate_info && isolate_info->isolate) {
1248
- context_info->context = NULL;
1249
- }
1250
-
1251
- if (isolate_info) {
1252
- context_info->isolate_info = NULL;
1253
- }
1288
+ context_info->context = NULL;
1289
+ context_info->isolate_info = NULL;
1254
1290
  }
1255
1291
 
1256
1292
  static void deallocate_isolate(void* data) {
@@ -1408,6 +1444,8 @@ rb_heap_snapshot(VALUE self, VALUE file) {
1408
1444
  FileOutputStream stream(fp);
1409
1445
  snap->Serialize(&stream, HeapSnapshot::kJSON);
1410
1446
 
1447
+ fflush(fp);
1448
+
1411
1449
  const_cast<HeapSnapshot*>(snap)->Delete();
1412
1450
 
1413
1451
  return Qtrue;
@@ -1582,6 +1620,16 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1582
1620
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1583
1621
  }
1584
1622
 
1623
+ static void set_ruby_exiting(VALUE value) {
1624
+ (void)value;
1625
+
1626
+ int res = pthread_rwlock_wrlock(&exit_lock);
1627
+ ruby_exiting = true;
1628
+ if (res == 0) {
1629
+ pthread_rwlock_unlock(&exit_lock);
1630
+ }
1631
+ }
1632
+
1585
1633
  extern "C" {
1586
1634
 
1587
1635
  void Init_mini_racer_extension ( void )
@@ -1632,8 +1680,19 @@ extern "C" {
1632
1680
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1633
1681
 
1634
1682
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1683
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1684
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1635
1685
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1636
1686
 
1637
1687
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1688
+
1689
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1690
+
1691
+ static pthread_attr_t attr;
1692
+ if (pthread_attr_init(&attr) == 0) {
1693
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1694
+ thread_attr_p = &attr;
1695
+ }
1696
+ }
1638
1697
  }
1639
1698
  }
@@ -130,21 +130,29 @@ module MiniRacer
130
130
  end
131
131
  end
132
132
 
133
- def initialize(options = nil)
133
+ def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil)
134
134
  options ||= {}
135
135
 
136
- check_init_options!(options)
136
+ check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
137
137
 
138
138
  @functions = {}
139
139
  @timeout = nil
140
140
  @max_memory = nil
141
141
  @current_exception = nil
142
- @timeout = options[:timeout]
143
- if options[:max_memory].is_a?(Numeric) && options[:max_memory] > 0
144
- @max_memory = options[:max_memory]
145
- end
142
+ @timeout = timeout
143
+ @max_memory = max_memory
144
+
146
145
  # false signals it should be fetched if requested
147
- @isolate = options[:isolate] || false
146
+ @isolate = isolate || false
147
+
148
+ @ensure_gc_after_idle = ensure_gc_after_idle
149
+
150
+ if @ensure_gc_after_idle
151
+ @last_eval = nil
152
+ @ensure_gc_thread = nil
153
+ @ensure_gc_mutex = Mutex.new
154
+ end
155
+
148
156
  @disposed = false
149
157
 
150
158
  @callback_mutex = Mutex.new
@@ -153,7 +161,7 @@ module MiniRacer
153
161
  @eval_thread = nil
154
162
 
155
163
  # defined in the C class
156
- init_unsafe(options[:isolate], options[:snapshot])
164
+ init_unsafe(isolate, snapshot)
157
165
  end
158
166
 
159
167
  def isolate
@@ -203,6 +211,7 @@ module MiniRacer
203
211
  end
204
212
  ensure
205
213
  @eval_thread = nil
214
+ ensure_gc_thread if @ensure_gc_after_idle
206
215
  end
207
216
 
208
217
  def call(function_name, *arguments)
@@ -216,15 +225,17 @@ module MiniRacer
216
225
  end
217
226
  ensure
218
227
  @eval_thread = nil
228
+ ensure_gc_thread if @ensure_gc_after_idle
219
229
  end
220
230
 
221
231
  def dispose
222
232
  return if @disposed
223
233
  isolate_mutex.synchronize do
234
+ return if @disposed
224
235
  dispose_unsafe
236
+ @disposed = true
237
+ @isolate = nil # allow it to be garbage collected, if set
225
238
  end
226
- @disposed = true
227
- @isolate = nil # allow it to be garbage collected, if set
228
239
  end
229
240
 
230
241
 
@@ -273,6 +284,38 @@ module MiniRacer
273
284
 
274
285
  private
275
286
 
287
+ def ensure_gc_thread
288
+ @last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
289
+ @ensure_gc_mutex.synchronize do
290
+ @ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
291
+ @ensure_gc_thread ||= Thread.new do
292
+ ensure_gc_after_idle_seconds = @ensure_gc_after_idle / 1000.0
293
+ done = false
294
+ while !done
295
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
296
+
297
+ if @disposed
298
+ @ensure_gc_thread = nil
299
+ break
300
+ end
301
+
302
+ if !@eval_thread && ensure_gc_after_idle_seconds < now - @last_eval
303
+ @ensure_gc_mutex.synchronize do
304
+ isolate_mutex.synchronize do
305
+ if !@eval_thread
306
+ isolate.low_memory_notification if !@disposed
307
+ @ensure_gc_thread = nil
308
+ done = true
309
+ end
310
+ end
311
+ end
312
+ end
313
+ sleep ensure_gc_after_idle_seconds if !done
314
+ end
315
+ end
316
+ end
317
+ end
318
+
276
319
  def stop_attached
277
320
  @callback_mutex.synchronize{
278
321
  if @callback_running
@@ -326,15 +369,29 @@ module MiniRacer
326
369
  rp.close if rp
327
370
  end
328
371
 
329
- def check_init_options!(options)
330
- assert_option_is_nil_or_a('isolate', options[:isolate], Isolate)
331
- assert_option_is_nil_or_a('snapshot', options[:snapshot], Snapshot)
372
+ def check_init_options!(isolate:, snapshot:, max_memory:, ensure_gc_after_idle:, timeout:)
373
+ assert_option_is_nil_or_a('isolate', isolate, Isolate)
374
+ assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
375
+
376
+ assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000)
377
+ assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
378
+ assert_numeric_or_nil('timeout', timeout, min_value: 1)
332
379
 
333
- if options[:isolate] && options[:snapshot]
380
+ if isolate && snapshot
334
381
  raise ArgumentError, 'can only pass one of isolate and snapshot options'
335
382
  end
336
383
  end
337
384
 
385
+ def assert_numeric_or_nil(option_name, object, min_value:)
386
+ if object.is_a?(Numeric) && object < min_value
387
+ raise ArgumentError, "#{option_name} must be larger than #{min_value}"
388
+ end
389
+
390
+ if !object.nil? && !object.is_a?(Numeric)
391
+ raise ArgumentError, "#{option_name} must be a number, passed a #{object.inspect}"
392
+ end
393
+ end
394
+
338
395
  def assert_option_is_nil_or_a(option_name, object, klass)
339
396
  unless object.nil? || object.is_a?(klass)
340
397
  raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniRacer
2
- VERSION = "0.2.10"
4
+ VERSION = "0.2.15"
3
5
  end
@@ -27,9 +27,10 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_development_dependency "bundler"
30
- spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rake", ">= 12.3.3"
31
31
  spec.add_development_dependency "minitest", "~> 5.0"
32
32
  spec.add_development_dependency "rake-compiler"
33
+ spec.add_development_dependency "m"
33
34
 
34
35
  spec.add_dependency 'libv8', '> 7.3'
35
36
  spec.require_paths = ["lib", "ext"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.2.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-22 00:00:00.000000000 Z
11
+ date: 2020-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 12.3.3
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: m
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: libv8
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,10 +122,10 @@ licenses:
108
122
  - MIT
109
123
  metadata:
110
124
  bug_tracker_uri: https://github.com/discourse/mini_racer/issues
111
- changelog_uri: https://github.com/discourse/mini_racer/blob/v0.2.10/CHANGELOG
112
- documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.2.10
113
- source_code_uri: https://github.com/discourse/mini_racer/tree/v0.2.10
114
- post_install_message:
125
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.2.15/CHANGELOG
126
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.2.15
127
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.2.15
128
+ post_install_message:
115
129
  rdoc_options: []
116
130
  require_paths:
117
131
  - lib
@@ -128,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
142
  version: '0'
129
143
  requirements: []
130
144
  rubygems_version: 3.0.3
131
- signing_key:
145
+ signing_key:
132
146
  specification_version: 4
133
147
  summary: Minimal embedded v8 for Ruby
134
148
  test_files: []