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 +4 -4
- data/README.md +18 -14
- data/lib/isomorfeus/speednode/attach_pipe.rb +7 -7
- data/lib/isomorfeus/speednode/attach_socket.rb +2 -3
- data/lib/isomorfeus/speednode/runner.js +48 -2
- data/lib/isomorfeus/speednode/runtime/context.rb +23 -9
- data/lib/isomorfeus/speednode/runtime/vm.rb +9 -14
- data/lib/isomorfeus/speednode/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11a836ac72dbce40b8926276342a91f3c7d3999b562f5be8670102e9b2196751
|
4
|
+
data.tar.gz: 1110a497795537221829ea361959a1b9c00e1570447744141b047efedecf2685
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
102
|
-
Isomorfeus Speednode Node.js (V8)
|
103
|
-
Isomorfeus Speednode Node.js (V8)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
Isomorfeus Speednode Node.js (V8)
|
111
|
-
|
112
|
-
Node.js (V8)
|
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,
|
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.
|
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]
|
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
|
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
|
-
|
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
|
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",
|
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",
|
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 =
|
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,
|
202
|
+
def command(cmd, argument)
|
208
203
|
@mutex.synchronize do
|
209
204
|
start_without_synchronization unless @started
|
210
|
-
VMCommand.new(@socket, cmd,
|
205
|
+
VMCommand.new(@socket, cmd, argument).execute
|
211
206
|
end
|
212
207
|
end
|
213
208
|
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.
|
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-
|
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.
|
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.
|