isomorfeus-speednode 0.6.2 → 0.7.0

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: 6853c585356e8829a315a146dbf12f7625f90eb2d23683147279c5eab581fe4f
4
- data.tar.gz: 7fd22dc8309c7d25685cc921853ac52f9ed342dac22ab71883226c2525ca48a0
3
+ metadata.gz: 91aa4cb54a3cb13afcf15476f841f711ce8de37c0573dfcdbd535aeda3a5dcbd
4
+ data.tar.gz: 4f1f9ccd06769d21ea4f25171cf128a69d8e4ea942f4417b10413f0df830b944
5
5
  SHA512:
6
- metadata.gz: 67668d00cf0294ed3df27c4992e95f671799cfea181cb01b33ed4c789870a7e3ab1f953c0d09e0010a8e119e6f5101aa6a31716f54d1498ca1ea5e83bfebc486
7
- data.tar.gz: 2ea56bc9974145a6c1bab593c15001e9f7e56edb432ee38a656c2f88087ebb6935ed0f5653f4841bd362d0a70a601e01a55dcedcd9986bf5c5bae6de8257429e
6
+ metadata.gz: ae92dbfa6c44909c8d9b27392cab6ae86854d25343074441b5b5657c54d36bbc5bf332b19c25fd9694f2a58d75abfdc859982cf79a08a7128916943e40c04ae3
7
+ data.tar.gz: b68a5bd5ebc54b333c67918f6a0a51f8fe871d5607c6b36b987163dd195243b1cb21181e4f52f714528b381ea7a81f43160b5327ddbf4380472cfc58daece9f2
data/README.md CHANGED
@@ -53,6 +53,13 @@ Evaluation in a permissive context:
53
53
  ExecJS.permissive_eval('1+1')
54
54
  ```
55
55
 
56
+ #### Stopping Contexts
57
+ Contexts can be stopped programmatically. If all contexts of a VM are stopped, the VM itself will be shut down with Node exiting, freeing memory and resources.
58
+ ```ruby
59
+ context = ExecJS.compile('Test = "test"') # will start a node process
60
+ ExecJS::Runtimes::Speednode.stop_context(context) # will kill the node process
61
+ ```
62
+
56
63
  ### Precompiling and Storing scripts for repeated execution
57
64
 
58
65
  Scripts can be precompiled and stored for repeated execution, which leads to a significant performance improvement, especially for larger scripts:
@@ -132,6 +132,13 @@ CircularJSON.stringify = function stringify(value, replacer, space, doNotResolve
132
132
  };
133
133
  /*** end of circular-json ***/
134
134
 
135
+ // serialize Map as Object, also below in attach
136
+ function simple_map_replacer(key, value) {
137
+ if (value && typeof value === 'object' && value.constructor.name === "Map")
138
+ return Object.fromEntries(value);
139
+ return value;
140
+ }
141
+
135
142
  function attachFunctionSource(responder_path, context, func) {
136
143
  return `${func} = async function(...method_args) {
137
144
  let context = '${context}';
@@ -158,8 +165,13 @@ function attachFunctionSource(responder_path, context, func) {
158
165
  }, 10)
159
166
  });
160
167
  }
168
+ function simple_map_replacer(key, value) {
169
+ if (value && typeof value === 'object' && value.constructor.name === "Map")
170
+ return Object.fromEntries(value);
171
+ return value;
172
+ }
161
173
  return new Promise(function(resolve, reject) {
162
- let request_json = JSON.stringify(request);
174
+ let request_json = JSON.stringify(request, simple_map_replacer);
163
175
  let buffer = Buffer.alloc(0);
164
176
  let socket = net.connect(responder_path);
165
177
  socket.setTimeout(2000);
@@ -228,18 +240,15 @@ function getContext(uuid) {
228
240
 
229
241
  function getContextOptions(uuid) {
230
242
  let options = { filename: "(execjs)", displayErrors: true };
231
- if (contexts[uuid].options.timeout) {
243
+ if (contexts[uuid].options.timeout)
232
244
  options.timeout = contexts[uuid].options.timeout;
233
- }
234
245
  return options;
235
246
  }
236
247
 
237
248
  function massageStackTrace(stack) {
238
- if (stack && stack.indexOf("SyntaxError") == 0) {
249
+ if (stack && stack.indexOf("SyntaxError") == 0)
239
250
  return "(execjs):1\n" + stack;
240
- } else {
241
- return stack;
242
- }
251
+ return stack;
243
252
  }
244
253
 
245
254
  let socket_path = process.env.SOCKET_PATH;
@@ -300,7 +309,7 @@ let commands = {
300
309
  deleteContext: function(uuid) {
301
310
  delete contexts[uuid];
302
311
  delete scripts[uuid]
303
- return [1];
312
+ return ['ok', Object.keys(contexts).length];
304
313
  },
305
314
  exit: function(code) {
306
315
  process_exit = code;
@@ -384,9 +393,9 @@ let server = net.createServer(function(s) {
384
393
  return;
385
394
  }
386
395
 
387
- try { outputJSON = JSON.stringify(result); }
396
+ try { outputJSON = JSON.stringify(result, simple_map_replacer); }
388
397
  catch(err) {
389
- if (err.message.includes('circular')) { outputJSON = CircularJSON.stringify(result); }
398
+ if (err.message.includes('circular')) { outputJSON = CircularJSON.stringify(result, simple_map_replacer); }
390
399
  else { outputJSON = JSON.stringify([['', err].join(''), err.stack]); }
391
400
  s.write([outputJSON, "\x04"].join(''));
392
401
  if (process_exit !== false) { process.exit(process_exit); }
@@ -4,22 +4,20 @@ module Isomorfeus
4
4
  class Context < ::ExecJS::Runtime::Context
5
5
  def self.finalize(runtime, uuid)
6
6
  proc do
7
- runtime.vm.delete_context(uuid) rescue nil # if delete_context fails, the vm exited before probably
7
+ runtime.unregister_context(uuid)
8
8
  end
9
9
  end
10
10
 
11
11
  def initialize(runtime, source = "", options = {})
12
12
  @runtime = runtime
13
13
  @uuid = SecureRandom.uuid
14
+ @runtime.register_context(@uuid, self)
14
15
  @permissive = !!options.delete(:permissive)
15
16
  @debug = @permissive ? !!options.delete(:debug) { false } : false
16
17
  @debug = false unless ENV['NODE_OPTIONS']&.include?('--inspect')
17
18
  @vm = @runtime.vm
18
- @context = nil
19
19
  @timeout = options[:timeout] ? options[:timeout]/1000 : 600
20
-
21
- ObjectSpace.define_finalizer(self, self.class.finalize(@runtime, @uuid))
22
-
20
+
23
21
  filename = options.delete(:filename)
24
22
  source = File.read(filename) if filename
25
23
 
@@ -28,6 +26,9 @@ module Isomorfeus
28
26
  rescue
29
27
  source = source.force_encoding('UTF-8')
30
28
  end
29
+
30
+ ObjectSpace.define_finalizer(self, self.class.finalize(@runtime, @uuid))
31
+
31
32
  if @debug && @permissive
32
33
  raw_created(source, options)
33
34
  elsif @permissive
@@ -35,7 +36,8 @@ module Isomorfeus
35
36
  else
36
37
  raw_create(source, options)
37
38
  end
38
- self.add_script(key: :_internal_exec_fin, source: '!global.__LastExecutionFinished')
39
+
40
+ add_script(key: :_internal_exec_fin, source: '!global.__LastExecutionFinished')
39
41
  end
40
42
 
41
43
  # def options
@@ -104,6 +106,10 @@ module Isomorfeus
104
106
  raw_exec("(function(){#{source}})()")
105
107
  end
106
108
 
109
+ def stop
110
+ @runtime.unregister_context(@uuid)
111
+ end
112
+
107
113
  protected
108
114
 
109
115
  def raw_bench(source)
@@ -119,26 +125,20 @@ module Isomorfeus
119
125
  end
120
126
 
121
127
  def raw_create(source, options)
122
- return if @context
123
128
  source = encode(source)
124
129
  result = @vm.create(@uuid, source, options)
125
- @context = true
126
130
  extract_result(result)
127
131
  end
128
132
 
129
133
  def raw_created(source, options)
130
- return if @context
131
134
  source = encode(source)
132
135
  result = @vm.created(@uuid, source, options)
133
- @context = true
134
136
  extract_result(result)
135
137
  end
136
138
 
137
139
  def raw_createp(source, options)
138
- return if @context
139
140
  source = encode(source)
140
141
  result = @vm.createp(@uuid, source, options)
141
- @context = true
142
142
  extract_result(result)
143
143
  end
144
144
 
@@ -165,7 +165,7 @@ module Isomorfeus
165
165
  end
166
166
 
167
167
  def await_result
168
- start_time = Time.now
168
+ start_time = ::Time.now
169
169
  while eval_script(key: :_internal_exec_fin) && !timed_out?(start_time)
170
170
  sleep 0.005
171
171
  end
@@ -193,7 +193,7 @@ module Isomorfeus
193
193
  end
194
194
 
195
195
  def timed_out?(start_time)
196
- if (Time.now - start_time) > @timeout
196
+ if (::Time.now - start_time) > @timeout
197
197
  raise "IsomorfeusSpeednode: Command Execution timed out!"
198
198
  end
199
199
  false
@@ -5,7 +5,7 @@ if Gem.win_platform?
5
5
  module Win32
6
6
  class Pipe
7
7
  def write(data)
8
- bytes = FFI::MemoryPointer.new(:ulong)
8
+ bytes = ::FFI::MemoryPointer.new(:ulong)
9
9
 
10
10
  raise Error, "no pipe created" unless @pipe
11
11
 
@@ -41,20 +41,6 @@ module Isomorfeus
41
41
  module Speednode
42
42
  class Runtime < ExecJS::Runtime
43
43
  class VM
44
- attr_reader :responder
45
-
46
- def initialize(options)
47
- @mutex = Thread::Mutex.new
48
- @socket_path = nil
49
- @options = options
50
- @started = false
51
- @socket = nil
52
- end
53
-
54
- def started?
55
- @started
56
- end
57
-
58
44
  def self.finalize(socket, socket_dir, socket_path, pid)
59
45
  proc do
60
46
  Isomorfeus::Speednode::Runtime.responders[socket_path].kill if Isomorfeus::Speednode::Runtime.responders[socket_path]
@@ -79,6 +65,20 @@ module Isomorfeus
79
65
  nil
80
66
  end
81
67
 
68
+ attr_reader :responder
69
+
70
+ def initialize(options)
71
+ @mutex = ::Thread::Mutex.new
72
+ @socket_path = nil
73
+ @options = options
74
+ @started = false
75
+ @socket = nil
76
+ end
77
+
78
+ def started?
79
+ @started
80
+ end
81
+
82
82
  def evsc(context, key)
83
83
  command('evsc', { 'context' => context, 'key' => key })
84
84
  end
@@ -134,6 +134,16 @@ module Isomorfeus
134
134
  end
135
135
  end
136
136
 
137
+ def stop
138
+ return unless @started
139
+ @mutex.synchronize do
140
+ self.class.exit_node(@socket, @socket_dir, @socket_path, @pid)
141
+ @socket_path = nil
142
+ @started = false
143
+ @socket = nil
144
+ end
145
+ end
146
+
137
147
  private
138
148
 
139
149
  def start_without_synchronization
@@ -15,7 +15,7 @@ module Isomorfeus
15
15
  bytes_to_send = message.bytesize
16
16
  sent_bytes = 0
17
17
 
18
- if ExecJS.windows?
18
+ if ::ExecJS.windows?
19
19
  @socket.write(message)
20
20
  begin
21
21
  result << @socket.read
@@ -19,14 +19,30 @@ module Isomorfeus
19
19
  @name = options[:name]
20
20
  @binary = Isomorfeus::Speednode::NodeCommand.cached(options[:command])
21
21
  @runner_path = options[:runner_path]
22
+ @vm = VM.new(binary: @binary, source_maps: '--enable-source-maps', runner_path: @runner_path)
22
23
  @encoding = options[:encoding]
23
24
  @deprecated = !!options[:deprecated]
24
-
25
- @vm = VM.new(binary: @binary, source_maps: '--enable-source-maps', runner_path: @runner_path)
26
-
27
25
  @popen_options = {}
28
26
  @popen_options[:external_encoding] = @encoding if @encoding
29
27
  @popen_options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8'
28
+ @contexts = {}
29
+ end
30
+
31
+ def register_context(uuid, context)
32
+ @contexts[uuid] = context
33
+ end
34
+
35
+ def unregister_context(uuid)
36
+ context = @contexts.delete(uuid)
37
+ if context && @vm
38
+ ObjectSpace.undefine_finalizer(context)
39
+ _ok, cc = @vm.delete_context(uuid) rescue nil # if delete_context fails, the vm exited before probably
40
+ end
41
+ @vm.stop if @contexts.size == 0 && @vm.started?
42
+ end
43
+
44
+ def stop_context(context)
45
+ unregister_context(context.instance_variable_get(:@uuid))
30
46
  end
31
47
 
32
48
  def available?
@@ -1,5 +1,5 @@
1
1
  module Isomorfeus
2
2
  module Speednode
3
- VERSION = '0.6.2'
3
+ VERSION = '0.7.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: isomorfeus-speednode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Biedermann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-18 00:00:00.000000000 Z
11
+ date: 2023-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs