isomorfeus-speednode 0.5.2 → 0.6.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: 676da9269c67e955ccfe546dbbbc82bf079e796d1070dd8d699c6523dbc537ba
4
- data.tar.gz: b78918192805e28a455e9c5d45594fdc3b683d93ddc6f0a38f13207afecb808a
3
+ metadata.gz: d163fe97b50cb3d23b187a384ccd95aedd2d11e7092db7e2e2866b9f882427c5
4
+ data.tar.gz: a95a2273adf37112ff71263470b4dd69843e45ad69ec730611defafe20e95d02
5
5
  SHA512:
6
- metadata.gz: b933335621ed51658ade944312fea14e1a8b89ec71585e4b99a46fe3a2dc6c94ccc073f6013281ed19ad07d889e91a09bfad75784c9f5004c8a35e3124b4dd2f
7
- data.tar.gz: 17c4dd51349c07013c961d714daee25f06c524edae7bb876cc0c78f7eda9519ea8be8c966840b86a08f019ef6b44540b31af09d52e5e8fd36c2576a84b702c44
6
+ metadata.gz: 9f8070f27aec93fa9c9059f269662a1168f2544c5c97095a472f1b1e29b55bf5d564ecd334c203146bfb9923e90a8cd94d7ea3cc0cd1a8da436f2f44cd0f73f5
7
+ data.tar.gz: 05e284e4800da68388373a80620874bd624719e1baaac94633d8c61919c8c1bf4b696a6fc256eb90bfe640927a0305124306e14288483289feabb11eba439345
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
  ```
@@ -1,5 +1,9 @@
1
1
  module ExecJS
2
2
  class << self
3
+ def permissive_bench(source, options = {})
4
+ runtime.permissive_bench(source, options)
5
+ end
6
+
3
7
  def permissive_exec(source, options = {})
4
8
  runtime.permissive_exec(source, options)
5
9
  end
@@ -1,6 +1,11 @@
1
1
  module ExecJS
2
2
  # Abstract base class for runtimes
3
3
  class Runtime
4
+ def permissive_bench(source, options = {})
5
+ context = permissive_compile("", options)
6
+ context.bench(source, options)
7
+ end
8
+
4
9
  def permissive_exec(source, options = {})
5
10
  context = permissive_compile("", options)
6
11
  context.exec(source, options)
@@ -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
@@ -201,6 +201,7 @@ function attachFunctionSource(responder_path, context, func) {
201
201
  function createCompatibleContext(uuid, options) {
202
202
  let c = vm.createContext();
203
203
  vm.runInContext('delete this.console', c, "(execjs)");
204
+ vm.runInContext('delete this.gc', c, "(execjs)");
204
205
  contexts[uuid] = { context: c, options: options };
205
206
  return c;
206
207
  }
@@ -243,6 +244,18 @@ function massageStackTrace(stack) {
243
244
 
244
245
  let socket_path = process.env.SOCKET_PATH;
245
246
  if (!socket_path) { throw 'No SOCKET_PATH given!'; };
247
+ let debug_contexts = [];
248
+ let commands_swapped = false;
249
+
250
+ function swap_commands(c) {
251
+ c.oexec = c.exec;
252
+ c.exec = c.execd;
253
+ c.oeval = c.eval;
254
+ c.eval = c.evald;
255
+ c.oevsc = c.evsc;
256
+ c.evsc = c.evscd;
257
+ commands_swapped = true;
258
+ }
246
259
 
247
260
  let commands = {
248
261
  attach: function(input) {
@@ -253,11 +266,32 @@ let commands = {
253
266
  let result = vm.runInContext(attachFunctionSource(responder_path, input.context, input.func), context, { filename: "(execjs)", displayErrors: true });
254
267
  return formatResult(result);
255
268
  },
269
+ bench: function (input) {
270
+ if (typeof global.gc === "function") { global.gc(); }
271
+ let context = getContext(input.context);
272
+ let options = getContextOptions(input.context);
273
+ performance.mark('start_bench');
274
+ let result = vm.runInContext(input.source, context, options);
275
+ performance.mark('stop_bench');
276
+ let duration = performance.measure('bench_time', 'start_bench', 'stop_bench').duration;
277
+ performance.clearMarks();
278
+ performance.clearMeasures();
279
+ if (typeof global.gc === "function") { global.gc(); }
280
+ return formatResult({ result: result, duration: duration });
281
+ },
256
282
  create: function (input) {
257
283
  let context = createCompatibleContext(input.context, input.options);
258
284
  let result = vm.runInContext(input.source, context, getContextOptions(input.context));
259
285
  return formatResult(result);
260
286
  },
287
+ created: function (input) {
288
+ debug_contexts.push(input.context);
289
+ if (!commands_swapped) {
290
+ swap_commands(commands);
291
+ let result = eval(input.source);
292
+ return formatResult(result);
293
+ } else { return formatResult(true) }
294
+ },
261
295
  createp: function (input) {
262
296
  let context = createPermissiveContext(input.context, input.options);
263
297
  let result = vm.runInContext(input.source, context, getContextOptions(input.context));
@@ -276,12 +310,30 @@ let commands = {
276
310
  let result = vm.runInContext(input.source, getContext(input.context), getContextOptions(input.context));
277
311
  return formatResult(result);
278
312
  },
313
+ execd: function(input) {
314
+ if (debug_contexts.includes(input.context)) {
315
+ let result = eval(input.source);
316
+ return formatResult(result);
317
+ } else {
318
+ return commands.oexec(input);
319
+ }
320
+ },
279
321
  eval: function (input) {
280
322
  if (input.source.match(/^\s*{/)) { input.source = "(" + input.source + ")"; }
281
323
  else if (input.source.match(/^\s*function\s*\(/)) { input.source = "(" + input.source + ")"; }
282
324
  let result = vm.runInContext(input.source, getContext(input.context), getContextOptions(input.context));
283
325
  return formatResult(result);
284
326
  },
327
+ evald: function(input) {
328
+ if (debug_contexts.includes(input.context)) {
329
+ if (input.source.match(/^\s*{/)) { input.source = "(" + input.source + ")"; }
330
+ else if (input.source.match(/^\s*function\s*\(/)) { input.source = "(" + input.source + ")"; }
331
+ let result = eval(input.source);
332
+ return formatResult(result);
333
+ } else {
334
+ return commands.oeval(input);
335
+ }
336
+ },
285
337
  scsc: function (input) {
286
338
  if (!scripts[input.context]) { scripts[input.context] = {}; }
287
339
  scripts[input.context][input.key] = new vm.Script(input.source);
@@ -291,6 +343,14 @@ let commands = {
291
343
  let result = scripts[input.context][input.key].runInContext(getContext(input.context));
292
344
  return formatResult(result);
293
345
  },
346
+ evscd: function(input) {
347
+ if (debug_contexts.includes(input.context)) {
348
+ let result = scripts[input.context][input.key].runInThisContext();
349
+ return formatResult(result);
350
+ } else {
351
+ return commands.oevsc(input);
352
+ }
353
+ }
294
354
  // ctxo: function (input) {
295
355
  // return formatResult(getContextOptions(input.context));
296
356
  // },
@@ -304,7 +364,7 @@ let server = net.createServer(function(s) {
304
364
  if (data[data.length - 1] !== 4) { return; }
305
365
 
306
366
  let request = received_data.join('').toString('utf8');
307
- request = request.substr(0, request.length - 1);
367
+ request = request.substring(0, request.length - 1);
308
368
  received_data = [];
309
369
 
310
370
  let input, result;
@@ -317,7 +377,7 @@ let server = net.createServer(function(s) {
317
377
  return;
318
378
  }
319
379
 
320
- try { result = commands[input.cmd].apply(null, input.args); }
380
+ try { result = commands[input.cmd](input.args); }
321
381
  catch (err) {
322
382
  outputJSON = JSON.stringify(['err', ['', err].join(''), massageStackTrace(err.stack)]);
323
383
  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
@@ -56,6 +66,10 @@ module Isomorfeus
56
66
  await_result
57
67
  end
58
68
 
69
+ def bench(source, _options = nil)
70
+ raw_bench(source) if /\S/ =~ source
71
+ end
72
+
59
73
  def call(identifier, *args)
60
74
  raw_eval("#{identifier}.apply(this, #{::Oj.dump(args, mode: :strict)})")
61
75
  end
@@ -92,6 +106,10 @@ module Isomorfeus
92
106
 
93
107
  protected
94
108
 
109
+ def raw_bench(source)
110
+ extract_result(@vm.bench(@uuid, encode(source)))
111
+ end
112
+
95
113
  def raw_eval(source)
96
114
  extract_result(@vm.eval(@uuid, encode(source)))
97
115
  end
@@ -108,6 +126,14 @@ module Isomorfeus
108
126
  extract_result(result)
109
127
  end
110
128
 
129
+ def raw_created(source, options)
130
+ return if @context
131
+ source = encode(source)
132
+ result = @vm.created(@uuid, source, options)
133
+ @context = true
134
+ extract_result(result)
135
+ end
136
+
111
137
  def raw_createp(source, options)
112
138
  return if @context
113
139
  source = encode(source)
@@ -140,7 +166,7 @@ module Isomorfeus
140
166
 
141
167
  def await_result
142
168
  start_time = Time.now
143
- while !execution_finished? && !timed_out?(start_time)
169
+ while eval_script(key: :_internal_exec_fin) && !timed_out?(start_time)
144
170
  sleep 0.005
145
171
  end
146
172
  result = exec <<~JAVASCRIPT
@@ -166,13 +192,9 @@ module Isomorfeus
166
192
  extract_result(result)
167
193
  end
168
194
 
169
- def execution_finished?
170
- eval 'global.__LastExecutionFinished'
171
- end
172
-
173
195
  def timed_out?(start_time)
174
196
  if (Time.now - start_time) > @timeout
175
- raise "Command Execution timed out!"
197
+ raise "IsomorfeusSpeednode: Command Execution timed out!"
176
198
  end
177
199
  false
178
200
  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)
@@ -95,10 +95,18 @@ module Isomorfeus
95
95
  command('exec', {'context' => context, 'source' => source})
96
96
  end
97
97
 
98
+ def bench(context, source)
99
+ command('bench', {'context' => context, 'source' => source})
100
+ end
101
+
98
102
  def create(context, source, options)
99
103
  command('create', {'context' => context, 'source' => source, 'options' => options})
100
104
  end
101
105
 
106
+ def created(context, source, options)
107
+ command('created', {'context' => context, 'source' => source, 'options' => options})
108
+ end
109
+
102
110
  def createp(context, source, options)
103
111
  command('createp', {'context' => context, 'source' => source, 'options' => options})
104
112
  end
@@ -110,7 +118,7 @@ module Isomorfeus
110
118
 
111
119
  def delete_context(context)
112
120
  @mutex.synchronize do
113
- VMCommand.new(@socket, "deleteContext", [context]).execute rescue nil
121
+ VMCommand.new(@socket, "deleteContext", context).execute rescue nil
114
122
  end
115
123
  rescue ThreadError
116
124
  nil
@@ -137,9 +145,9 @@ module Isomorfeus
137
145
  @socket_dir = Dir.mktmpdir("iso-speednode-")
138
146
  @socket_path = File.join(@socket_dir, "socket")
139
147
  end
140
- @pid = Process.spawn({"SOCKET_PATH" => @socket_path}, @options[:binary], @options[:source_maps], @options[:runner_path])
148
+ @pid = Process.spawn({"SOCKET_PATH" => @socket_path}, @options[:binary], '--expose-gc', @options[:source_maps], @options[:runner_path])
141
149
 
142
- retries = 100
150
+ retries = 500
143
151
 
144
152
  if ExecJS.windows?
145
153
  timeout_or_connected = false
@@ -166,15 +174,6 @@ module Isomorfeus
166
174
 
167
175
  @started = true
168
176
 
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
177
  Kernel.at_exit { self.class.finalize(@socket, @socket_dir, @socket_path, @pid).call }
179
178
  end
180
179
 
@@ -204,10 +203,10 @@ module Isomorfeus
204
203
  @responder.run
205
204
  end
206
205
 
207
- def command(cmd, *arguments)
206
+ def command(cmd, argument)
208
207
  @mutex.synchronize do
209
208
  start_without_synchronization unless @started
210
- VMCommand.new(@socket, cmd, arguments).execute
209
+ VMCommand.new(@socket, cmd, argument).execute
211
210
  end
212
211
  end
213
212
  end
@@ -27,12 +27,12 @@ module Isomorfeus
27
27
  sent_bytes += @socket.sendmsg(message.byteslice((sent_bytes)..-1))
28
28
  end
29
29
  end
30
-
30
+
31
31
  begin
32
32
  result << @socket.recvmsg()[0]
33
33
  end until result.end_with?("\x04")
34
34
  end
35
- ::Oj.load(result.chop!, create_additions: false)
35
+ ::Oj.load(result.chop!, mode: :strict)
36
36
  end
37
37
  end
38
38
  end
@@ -1,5 +1,5 @@
1
1
  module Isomorfeus
2
2
  module Speednode
3
- VERSION = '0.5.2'
3
+ VERSION = '0.6.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.5.2
4
+ version: 0.6.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: 2022-02-19 00:00:00.000000000 Z
11
+ date: 2022-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.13.11
33
+ version: 3.13.21
34
34
  type: :runtime
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: 3.13.11
40
+ version: 3.13.21
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: win32-pipe
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 5.15.0
75
+ version: 5.16.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 5.15.0
82
+ version: 5.16.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -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.4.0.dev
155
155
  signing_key:
156
156
  specification_version: 4
157
157
  summary: A fast ExecJS runtime based on nodejs, tuned for Isomorfeus.