mini_racer 0.2.10 → 0.2.15

Sign up to get free protection for your applications and to get access to all the features.
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: []