isomorfeus-speednode 0.5.2 → 0.5.3

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: 676da9269c67e955ccfe546dbbbc82bf079e796d1070dd8d699c6523dbc537ba
4
- data.tar.gz: b78918192805e28a455e9c5d45594fdc3b683d93ddc6f0a38f13207afecb808a
3
+ metadata.gz: 11a836ac72dbce40b8926276342a91f3c7d3999b562f5be8670102e9b2196751
4
+ data.tar.gz: 1110a497795537221829ea361959a1b9c00e1570447744141b047efedecf2685
5
5
  SHA512:
6
- metadata.gz: b933335621ed51658ade944312fea14e1a8b89ec71585e4b99a46fe3a2dc6c94ccc073f6013281ed19ad07d889e91a09bfad75784c9f5004c8a35e3124b4dd2f
7
- data.tar.gz: 17c4dd51349c07013c961d714daee25f06c524edae7bb876cc0c78f7eda9519ea8be8c966840b86a08f019ef6b44540b31af09d52e5e8fd36c2576a84b702c44
6
+ metadata.gz: 0c4f86cf44a01774584782e12c94cb8bd4677c348f914aca3740c697297be2f843da12642683932bb5bd7470c7c1e87009373429644cfe871c11b50082d594df
7
+ data.tar.gz: 3ef559f6eecf237c5fb4a46ef3eadd243d8f482fef64170391fcaeeaed373a900401dfe4259d6d7bebf62200529ad54e8efc69f4c6cd887c022b02f701d91092
data/README.md CHANGED
@@ -53,7 +53,7 @@ Evaluation in a permissive context:
53
53
  ExecJS.permissive_eval('1+1')
54
54
  ```
55
55
 
56
- ### Storing scripts for repeated execution
56
+ ### Precompiling and Storing scripts for repeated execution
57
57
 
58
58
  Scripts can be precompiled and stored for repeated execution, which leads to a significant performance improvement, especially for larger scripts:
59
59
  ```ruby
@@ -61,6 +61,7 @@ context = ExecJS.compile('Test = "test"')
61
61
  context.add_script(key: 'super', source: some_large_javascript) # will compile and store the script
62
62
  context.eval_script(:key: 'super') # will run the precompiled script in the context
63
63
  ```
64
+ For the actual performance benefit see below.
64
65
 
65
66
  ### Async function support
66
67
 
@@ -95,21 +96,24 @@ Attaching and calling ruby methods to/from permissive contexts is not that fast.
95
96
 
96
97
  Highly scientific, maybe.
97
98
 
98
- 1000 rounds using node 16.6.2.:
99
+ 1000 rounds using node 16.14.0 on linux (ctx = using context, scsc = using precompiled scripts):
99
100
  ```
100
101
  standard ExecJS CoffeeScript eval benchmark:
101
- user system total real
102
- Isomorfeus Speednode Node.js (V8) Windows 0.079000 0.031000 0.110000 ( 0.518821)
103
- Isomorfeus Speednode Node.js (V8) Linux 0.092049 0.039669 0.131718 ( 0.546846)
104
- mini_racer (V8) 0.4.0 Linux only 0.776317 0.062923 0.839240 ( 0.480490)
105
- Node.js (V8) Linux 0.330156 0.354536 52.523972 ( 51.203355)
106
-
107
- evel overhead benchmark:
108
- user system total real
109
- Isomorfeus Speednode Node.js (V8) Windows 0.032000 0.031000 0.063000 ( 0.227634)
110
- Isomorfeus Speednode Node.js (V8) Linux 0.085135 0.022002 0.107137 ( 0.153055)
111
- mini_racer (V8) 0.4.0 Linux only 0.083562 0.115612 0.199174 ( 0.128455)
112
- Node.js (V8) Linux 0.273238 0.265101 30.169976 ( 29.636668)
102
+ user system total real
103
+ Isomorfeus Speednode Node.js (V8): 0.086443 0.061089 0.147532 ( 0.567715)
104
+ Isomorfeus Speednode Node.js (V8) ctx: 0.080821 0.047469 0.128290 ( 0.468664)
105
+ Isomorfeus Speednode Node.js (V8) scsc: 0.089423 0.046541 0.135964 ( 0.381198) <- fastest
106
+ mini_racer (V8): 0.723352 0.179944 0.903296 ( 0.513696)
107
+ mini_racer (V8) ctx: 0.475681 0.226101 0.701782 ( 0.429463)
108
+
109
+ eval overhead benchmark:
110
+ user system total real
111
+ Isomorfeus Speednode Node.js (V8): 0.028312 0.062895 0.091207 ( 0.199176)
112
+ Isomorfeus Speednode Node.js (V8) ctx: 0.061245 0.027093 0.088338 ( 0.183092)
113
+ Isomorfeus Speednode Node.js (V8) scsc: 0.061062 0.019203 0.080265 ( 0.107898) <- fastest
114
+ mini_racer (V8): 0.103914 0.092251 0.196165 ( 0.125081)
115
+ mini_racer (V8) ctx: 0.120440 0.060329 0.180769 ( 0.112695)
116
+
113
117
  ```
114
118
  minify discourse benchmark from mini_racer:
115
119
  ```
@@ -10,9 +10,9 @@ module Isomorfeus
10
10
  ERROR_IO_PENDING = 997
11
11
  ERROR_PIPE_CONNECTED = 535
12
12
  ERROR_SUCCESS = 0
13
-
13
+
14
14
  FILE_FLAG_OVERLAPPED = 0x40000000
15
-
15
+
16
16
  INFINITE = 0xFFFFFFFF
17
17
  INVALID_HANDLE_VALUE = FFI::Pointer.new(-1).address
18
18
 
@@ -91,7 +91,7 @@ module Isomorfeus
91
91
  overlap[:hEvent] = @events[0]
92
92
 
93
93
  @pipe = { overlap: overlap, instance: nil, request: FFI::Buffer.new(1, BUFFER_SIZE), bytes_read: 0, reply: FFI::Buffer.new(1, BUFFER_SIZE), bytes_to_write: 0, state: nil, pending_io: false }
94
- @pipe[:instance] = CreateNamedPipe(@full_pipe_name,
94
+ @pipe[:instance] = CreateNamedPipe(@full_pipe_name,
95
95
  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
96
96
  PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
97
97
  4,
@@ -102,7 +102,7 @@ module Isomorfeus
102
102
  raise "CreateNamedPipe failed with #{GetLastError()}" if @pipe[:instance] == INVALID_HANDLE_VALUE
103
103
  @pipe[:pending_io] = connect_to_new_client
104
104
  @pipe[:state] = @pipe[:pending_io] ? CONNECTING_STATE : READING_STATE
105
-
105
+
106
106
  @events_pointer.write_array_of_ulong_long(@events)
107
107
  nil
108
108
  end
@@ -194,9 +194,9 @@ module Isomorfeus
194
194
  def disconnect_and_reconnect
195
195
  FlushFileBuffers(@pipe[:instance])
196
196
  STDERR.puts("DisconnectNamedPipe failed with #{GetLastError()}") if !DisconnectNamedPipe(@pipe[:instance])
197
-
197
+
198
198
  @pipe[:pending_io] = connect_to_new_client
199
-
199
+
200
200
  @pipe[:state] = @pipe[:pending_io] ? CONNECTING_STATE : READING_STATE
201
201
  end
202
202
 
@@ -207,7 +207,7 @@ module Isomorfeus
207
207
  connected = ConnectNamedPipe(@pipe[:instance], @pipe[:overlap].to_ptr)
208
208
  last_error = GetLastError()
209
209
  raise "ConnectNamedPipe failed with #{last_error} - #{connected}" if connected != 0
210
-
210
+
211
211
  case last_error
212
212
  when ERROR_IO_PENDING
213
213
  pending_io = true
@@ -29,9 +29,8 @@ module Isomorfeus
29
29
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
30
30
  end
31
31
  end
32
- sleep 0.005
33
32
  ret = begin
34
- IO.select([@socket], nil, nil, 1) || next
33
+ IO.select([@socket], nil, nil, nil) || next
35
34
  rescue Errno::EBADF
36
35
  end
37
36
  end
@@ -43,4 +42,4 @@ module Isomorfeus
43
42
  end
44
43
  end
45
44
  end
46
- end
45
+ end
@@ -243,6 +243,18 @@ function massageStackTrace(stack) {
243
243
 
244
244
  let socket_path = process.env.SOCKET_PATH;
245
245
  if (!socket_path) { throw 'No SOCKET_PATH given!'; };
246
+ let debug_contexts = [];
247
+ let commands_swapped = false;
248
+
249
+ function swap_commands(c) {
250
+ c.oexec = c.exec;
251
+ c.exec = c.execd;
252
+ c.oeval = c.eval;
253
+ c.eval = c.evald;
254
+ c.oevsc = c.evsc;
255
+ c.evsc = c.evscd;
256
+ commands_swapped = true;
257
+ }
246
258
 
247
259
  let commands = {
248
260
  attach: function(input) {
@@ -258,6 +270,14 @@ let commands = {
258
270
  let result = vm.runInContext(input.source, context, getContextOptions(input.context));
259
271
  return formatResult(result);
260
272
  },
273
+ created: function (input) {
274
+ debug_contexts.push(input.context);
275
+ if (!commands_swapped) {
276
+ swap_commands(commands);
277
+ let result = eval(input.source);
278
+ return formatResult(result);
279
+ } else { return formatResult(true) }
280
+ },
261
281
  createp: function (input) {
262
282
  let context = createPermissiveContext(input.context, input.options);
263
283
  let result = vm.runInContext(input.source, context, getContextOptions(input.context));
@@ -276,12 +296,30 @@ let commands = {
276
296
  let result = vm.runInContext(input.source, getContext(input.context), getContextOptions(input.context));
277
297
  return formatResult(result);
278
298
  },
299
+ execd: function(input) {
300
+ if (debug_contexts.includes(input.context)) {
301
+ let result = eval(input.source);
302
+ return formatResult(result);
303
+ } else {
304
+ return commands.oexec(input);
305
+ }
306
+ },
279
307
  eval: function (input) {
280
308
  if (input.source.match(/^\s*{/)) { input.source = "(" + input.source + ")"; }
281
309
  else if (input.source.match(/^\s*function\s*\(/)) { input.source = "(" + input.source + ")"; }
282
310
  let result = vm.runInContext(input.source, getContext(input.context), getContextOptions(input.context));
283
311
  return formatResult(result);
284
312
  },
313
+ evald: function(input) {
314
+ if (debug_contexts.includes(input.context)) {
315
+ if (input.source.match(/^\s*{/)) { input.source = "(" + input.source + ")"; }
316
+ else if (input.source.match(/^\s*function\s*\(/)) { input.source = "(" + input.source + ")"; }
317
+ let result = eval(input.source);
318
+ return formatResult(result);
319
+ } else {
320
+ return commands.oeval(input);
321
+ }
322
+ },
285
323
  scsc: function (input) {
286
324
  if (!scripts[input.context]) { scripts[input.context] = {}; }
287
325
  scripts[input.context][input.key] = new vm.Script(input.source);
@@ -291,6 +329,14 @@ let commands = {
291
329
  let result = scripts[input.context][input.key].runInContext(getContext(input.context));
292
330
  return formatResult(result);
293
331
  },
332
+ evscd: function(input) {
333
+ if (debug_contexts.includes(input.context)) {
334
+ let result = scripts[input.context][input.key].runInThisContext();
335
+ return formatResult(result);
336
+ } else {
337
+ return commands.oevsc(input);
338
+ }
339
+ }
294
340
  // ctxo: function (input) {
295
341
  // return formatResult(getContextOptions(input.context));
296
342
  // },
@@ -304,7 +350,7 @@ let server = net.createServer(function(s) {
304
350
  if (data[data.length - 1] !== 4) { return; }
305
351
 
306
352
  let request = received_data.join('').toString('utf8');
307
- request = request.substr(0, request.length - 1);
353
+ request = request.substring(0, request.length - 1);
308
354
  received_data = [];
309
355
 
310
356
  let input, result;
@@ -317,7 +363,7 @@ let server = net.createServer(function(s) {
317
363
  return;
318
364
  }
319
365
 
320
- try { result = commands[input.cmd].apply(null, input.args); }
366
+ try { result = commands[input.cmd](input.args); }
321
367
  catch (err) {
322
368
  outputJSON = JSON.stringify(['err', ['', err].join(''), massageStackTrace(err.stack)]);
323
369
  s.write([outputJSON, "\x04"].join(''));
@@ -3,13 +3,17 @@ module Isomorfeus
3
3
  class Runtime < ExecJS::Runtime
4
4
  class Context < ::ExecJS::Runtime::Context
5
5
  def self.finalize(runtime, uuid)
6
- proc { runtime.vm.delete_context(uuid) }
6
+ proc do
7
+ runtime.vm.delete_context(uuid) rescue nil # if delete_context fails, the vm exited before probably
8
+ end
7
9
  end
8
10
 
9
11
  def initialize(runtime, source = "", options = {})
10
12
  @runtime = runtime
11
13
  @uuid = SecureRandom.uuid
12
14
  @permissive = !!options.delete(:permissive)
15
+ @debug = @permissive ? !!options.delete(:debug) { false } : false
16
+ @debug = false unless ENV['NODE_OPTIONS']&.include?('--inspect')
13
17
  @vm = @runtime.vm
14
18
  @context = nil
15
19
  @timeout = options[:timeout] ? options[:timeout]/1000 : 600
@@ -24,8 +28,14 @@ module Isomorfeus
24
28
  rescue
25
29
  source = source.force_encoding('UTF-8')
26
30
  end
27
-
28
- @permissive ? raw_createp(source, options) : raw_create(source, options)
31
+ if @debug && @permissive
32
+ raw_created(source, options)
33
+ elsif @permissive
34
+ raw_createp(source, options)
35
+ else
36
+ raw_create(source, options)
37
+ end
38
+ self.add_script(key: :_internal_exec_fin, source: '!global.__LastExecutionFinished')
29
39
  end
30
40
 
31
41
  # def options
@@ -108,6 +118,14 @@ module Isomorfeus
108
118
  extract_result(result)
109
119
  end
110
120
 
121
+ def raw_created(source, options)
122
+ return if @context
123
+ source = encode(source)
124
+ result = @vm.created(@uuid, source, options)
125
+ @context = true
126
+ extract_result(result)
127
+ end
128
+
111
129
  def raw_createp(source, options)
112
130
  return if @context
113
131
  source = encode(source)
@@ -140,7 +158,7 @@ module Isomorfeus
140
158
 
141
159
  def await_result
142
160
  start_time = Time.now
143
- while !execution_finished? && !timed_out?(start_time)
161
+ while eval_script(key: :_internal_exec_fin) && !timed_out?(start_time)
144
162
  sleep 0.005
145
163
  end
146
164
  result = exec <<~JAVASCRIPT
@@ -166,13 +184,9 @@ module Isomorfeus
166
184
  extract_result(result)
167
185
  end
168
186
 
169
- def execution_finished?
170
- eval 'global.__LastExecutionFinished'
171
- end
172
-
173
187
  def timed_out?(start_time)
174
188
  if (Time.now - start_time) > @timeout
175
- raise "Command Execution timed out!"
189
+ raise "IsomorfeusSpeednode: Command Execution timed out!"
176
190
  end
177
191
  false
178
192
  end
@@ -63,7 +63,7 @@ module Isomorfeus
63
63
  end
64
64
 
65
65
  def self.exit_node(socket, socket_dir, socket_path, pid)
66
- VMCommand.new(socket, "exit", [0]).execute
66
+ VMCommand.new(socket, "exit", 0).execute rescue nil
67
67
  socket.close
68
68
  File.unlink(socket_path) if File.exist?(socket_path)
69
69
  Dir.rmdir(socket_dir) if socket_dir && Dir.exist?(socket_dir)
@@ -99,6 +99,10 @@ module Isomorfeus
99
99
  command('create', {'context' => context, 'source' => source, 'options' => options})
100
100
  end
101
101
 
102
+ def created(context, source, options)
103
+ command('created', {'context' => context, 'source' => source, 'options' => options})
104
+ end
105
+
102
106
  def createp(context, source, options)
103
107
  command('createp', {'context' => context, 'source' => source, 'options' => options})
104
108
  end
@@ -110,7 +114,7 @@ module Isomorfeus
110
114
 
111
115
  def delete_context(context)
112
116
  @mutex.synchronize do
113
- VMCommand.new(@socket, "deleteContext", [context]).execute rescue nil
117
+ VMCommand.new(@socket, "deleteContext", context).execute rescue nil
114
118
  end
115
119
  rescue ThreadError
116
120
  nil
@@ -139,7 +143,7 @@ module Isomorfeus
139
143
  end
140
144
  @pid = Process.spawn({"SOCKET_PATH" => @socket_path}, @options[:binary], @options[:source_maps], @options[:runner_path])
141
145
 
142
- retries = 100
146
+ retries = 500
143
147
 
144
148
  if ExecJS.windows?
145
149
  timeout_or_connected = false
@@ -166,15 +170,6 @@ module Isomorfeus
166
170
 
167
171
  @started = true
168
172
 
169
- at_exit do
170
- begin
171
- self.class.exit_node(@socket, @socket_dir, @socket_path, @pid) unless @socket.closed?
172
- rescue
173
- # do nothing
174
- end
175
- end
176
-
177
- ObjectSpace.define_finalizer(self, self.class.finalize(@socket, @socket_dir, @socket_path, @pid))
178
173
  Kernel.at_exit { self.class.finalize(@socket, @socket_dir, @socket_path, @pid).call }
179
174
  end
180
175
 
@@ -204,10 +199,10 @@ module Isomorfeus
204
199
  @responder.run
205
200
  end
206
201
 
207
- def command(cmd, *arguments)
202
+ def command(cmd, argument)
208
203
  @mutex.synchronize do
209
204
  start_without_synchronization unless @started
210
- VMCommand.new(@socket, cmd, arguments).execute
205
+ VMCommand.new(@socket, cmd, argument).execute
211
206
  end
212
207
  end
213
208
  end
@@ -1,5 +1,5 @@
1
1
  module Isomorfeus
2
2
  module Speednode
3
- VERSION = '0.5.2'
3
+ VERSION = '0.5.3'
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.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Biedermann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-19 00:00:00.000000000 Z
11
+ date: 2022-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs
@@ -151,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  requirements: []
154
- rubygems_version: 3.3.3
154
+ rubygems_version: 3.3.7
155
155
  signing_key:
156
156
  specification_version: 4
157
157
  summary: A fast ExecJS runtime based on nodejs, tuned for Isomorfeus.