speednode 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +128 -0
- data/lib/speednode/attach_pipe.rb +224 -0
- data/lib/speednode/attach_socket.rb +43 -0
- data/lib/speednode/config.rb +23 -0
- data/lib/speednode/execjs_module.rb +19 -0
- data/lib/speednode/execjs_runtime.rb +23 -0
- data/lib/speednode/execjs_runtimes.rb +11 -0
- data/lib/speednode/node_command.rb +41 -0
- data/lib/speednode/runner.js +412 -0
- data/lib/speednode/runtime/context.rb +202 -0
- data/lib/speednode/runtime/vm.rb +223 -0
- data/lib/speednode/runtime/vm_command.rb +38 -0
- data/lib/speednode/runtime.rb +55 -0
- data/lib/speednode/version.rb +3 -0
- data/lib/speednode.rb +21 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ab3e330c3cd4bf4518c54e6666ff13ee722b553e4de7d8be2602b984811da9fc
|
4
|
+
data.tar.gz: '08218aa8e443770808a35de088c869bfc0d227493beba5f60b98ddcdfe35b51a'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f41ebd4a9b509dc81f815c7dff64ff3ce8ae15a57fa085038ce5cdb139ab37f0f0b26cded89f52450e4e39a0f69afd98238a7ed02d20db50e7d395872617f67
|
7
|
+
data.tar.gz: cb7d8667d95403426e7964e4e2be7d1608a3d2ad43fe1b36a694f125bd9595dfe3ba5b73d983d57215fdc637b2a141d8536f642435082005cbbdc5487af1b2e1
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 John Hawthorn
|
4
|
+
Copyright (c) 2019-2024 Jan Biedermann
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
14
|
+
copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Speednode
|
2
|
+
|
3
|
+
A fast runtime for ExecJS using node js. Works on Linux, BSDs, MacOS and Windows.
|
4
|
+
Inspired by [execjs-fastnode](https://github.com/jhawthorn/execjs-fastnode).
|
5
|
+
|
6
|
+
### Installation
|
7
|
+
|
8
|
+
In Gemfile:
|
9
|
+
`gem 'speednode'`, then `bundle install`
|
10
|
+
|
11
|
+
### Configuration
|
12
|
+
|
13
|
+
Speednode provides one node based runtime `Speednode` which runs scripts in node vms.
|
14
|
+
The runtime can be chosen by:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
ExecJS.runtime = ExecJS::Runtimes::Speednode
|
18
|
+
```
|
19
|
+
If node cant find node modules for the permissive contexts (see below), its possible to set the load path before assigning the runtime:
|
20
|
+
```ruby
|
21
|
+
ENV['NODE_PATH'] = './node_modules'
|
22
|
+
```
|
23
|
+
|
24
|
+
### Contexts
|
25
|
+
|
26
|
+
Each ExecJS context runs in a node vm. Speednode offers two kinds of contexts:
|
27
|
+
- a compatible context, which is compatible with default ExecJS behavior.
|
28
|
+
- a permissive context, which is more permissive and allows to `require` node modules.
|
29
|
+
|
30
|
+
#### Compatible
|
31
|
+
A compatible context can be created with the standard `ExecJS.compile` or code can be executed within a compatible context by using the standard `ExecJS.eval` or `ExecJS.exec`.
|
32
|
+
Example for a compatible context:
|
33
|
+
```ruby
|
34
|
+
compat_context = ExecJS.compile('Test = "test"')
|
35
|
+
compat_context.eval('1+1')
|
36
|
+
```
|
37
|
+
#### Permissive
|
38
|
+
A permissive context can be created with `ExecJS.permissive_compile` or code can be executed within a permissive context by using
|
39
|
+
`ExecJS.permissive_eval` or `ExecJS.permissive_exec`.
|
40
|
+
Example for a permissive context:
|
41
|
+
```ruby
|
42
|
+
perm_context = ExecJS.permissive_compile('Test = "test"')
|
43
|
+
perm_context.eval('1+1')
|
44
|
+
```
|
45
|
+
Evaluation in a permissive context:
|
46
|
+
```ruby
|
47
|
+
ExecJS.permissive_eval('1+1')
|
48
|
+
```
|
49
|
+
|
50
|
+
#### Stopping Contexts
|
51
|
+
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.
|
52
|
+
```ruby
|
53
|
+
context = ExecJS.compile('Test = "test"') # will start a node process
|
54
|
+
ExecJS::Runtimes::Speednode.stop_context(context) # will kill the node process
|
55
|
+
```
|
56
|
+
|
57
|
+
### Precompiling and Storing scripts for repeated execution
|
58
|
+
|
59
|
+
Scripts can be precompiled and stored for repeated execution, which leads to a significant performance improvement, especially for larger scripts:
|
60
|
+
```ruby
|
61
|
+
context = ExecJS.compile('Test = "test"')
|
62
|
+
context.add_script(key: 'super', source: some_large_javascript) # will compile and store the script
|
63
|
+
context.eval_script(key: 'super') # will run the precompiled script in the context
|
64
|
+
```
|
65
|
+
For the actual performance benefit see below.
|
66
|
+
|
67
|
+
### Async function support
|
68
|
+
|
69
|
+
Its possible to call async functions synchronously from ruby using Context#await:
|
70
|
+
```ruby
|
71
|
+
context = ExecJS.compile('')
|
72
|
+
context.eval <<~JAVASCRIPT
|
73
|
+
async function foo(val) {
|
74
|
+
return new Promise(function (resolve, reject) { resolve(val); });
|
75
|
+
}
|
76
|
+
JAVASCRIPT
|
77
|
+
|
78
|
+
context.await("foo('test')") # => 'test'
|
79
|
+
```
|
80
|
+
|
81
|
+
### Attaching ruby methods to Permissive Contexts
|
82
|
+
|
83
|
+
Ruby methods can be attached to Permissive Contexts using Context#attach:
|
84
|
+
```ruby
|
85
|
+
context = ExecJS.permissive_compile(SOURCE)
|
86
|
+
context.attach('foo') { |v| v }
|
87
|
+
context.await('foo("bar")')
|
88
|
+
```
|
89
|
+
The attached method is reflected in the context by a async javascript function. From within javascript the ruby method is best called using await:
|
90
|
+
```javascript
|
91
|
+
r = await foo('test');
|
92
|
+
```
|
93
|
+
or via context#await as in above example.
|
94
|
+
Attaching and calling ruby methods to/from permissive contexts is not that fast. It is recommended to use it sparingly.
|
95
|
+
|
96
|
+
### Benchmarks
|
97
|
+
|
98
|
+
Highly scientific, maybe.
|
99
|
+
|
100
|
+
1000 rounds using speednode 0.8.0 with node 20.11.0 on a older CPU on Linux
|
101
|
+
(ctx = using context, scsc = using precompiled scripts):
|
102
|
+
```
|
103
|
+
ExecJS CoffeeScript eval: real
|
104
|
+
Speednode Node.js (V8): 0.324355
|
105
|
+
Speednode Node.js (V8) ctx: 0.276933
|
106
|
+
Speednode Node.js (V8) scsc: 0.239678
|
107
|
+
mini_racer (0.8.0): 0.204274
|
108
|
+
mini_racer (0.8.0) ctx: 0.134327
|
109
|
+
|
110
|
+
Eval overhead benchmark: real
|
111
|
+
Speednode Node.js (V8): 0.064198
|
112
|
+
Speednode Node.js (V8) ctx: 0.065521
|
113
|
+
Speednode Node.js (V8) scsc: 0.050652
|
114
|
+
mini_racer (0.8.0): 0.010653
|
115
|
+
mini_racer (0.8.0) ctx: 0.007764
|
116
|
+
```
|
117
|
+
|
118
|
+
To run benchmarks:
|
119
|
+
- clone repo
|
120
|
+
- `bundle install`
|
121
|
+
- `bundle exec rake bench`
|
122
|
+
|
123
|
+
### Tests
|
124
|
+
|
125
|
+
To run tests:
|
126
|
+
- clone repo
|
127
|
+
- `bundle install`
|
128
|
+
- `bundle exec rake`
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Speednode
|
4
|
+
module WindowsyThings
|
5
|
+
extend FFI::Library
|
6
|
+
|
7
|
+
ffi_lib :kernel32, :user32
|
8
|
+
|
9
|
+
ERROR_IO_PENDING = 997
|
10
|
+
ERROR_PIPE_CONNECTED = 535
|
11
|
+
ERROR_SUCCESS = 0
|
12
|
+
|
13
|
+
FILE_FLAG_OVERLAPPED = 0x40000000
|
14
|
+
|
15
|
+
INFINITE = 0xFFFFFFFF
|
16
|
+
INVALID_HANDLE_VALUE = FFI::Pointer.new(-1).address
|
17
|
+
|
18
|
+
PIPE_ACCESS_DUPLEX = 0x00000003
|
19
|
+
PIPE_READMODE_BYTE = 0x00000000
|
20
|
+
PIPE_READMODE_MESSAGE = 0x00000002
|
21
|
+
PIPE_TYPE_BYTE = 0x00000000
|
22
|
+
PIPE_TYPE_MESSAGE = 0x00000004
|
23
|
+
PIPE_WAIT = 0x00000000
|
24
|
+
|
25
|
+
QS_ALLINPUT = 0x04FF
|
26
|
+
|
27
|
+
typedef :uintptr_t, :handle
|
28
|
+
|
29
|
+
attach_function :ConnectNamedPipe, [:handle, :pointer], :ulong
|
30
|
+
attach_function :CreateEvent, :CreateEventA, [:pointer, :ulong, :ulong, :string], :handle
|
31
|
+
attach_function :CreateNamedPipe, :CreateNamedPipeA, [:string, :ulong, :ulong, :ulong, :ulong, :ulong, :ulong, :pointer], :handle
|
32
|
+
attach_function :DisconnectNamedPipe, [:handle], :bool
|
33
|
+
attach_function :FlushFileBuffers, [:handle], :bool
|
34
|
+
attach_function :GetLastError, [], :ulong
|
35
|
+
attach_function :GetOverlappedResult, [:handle, :pointer, :pointer, :bool], :bool
|
36
|
+
attach_function :MsgWaitForMultipleObjects, [:ulong, :pointer, :ulong, :ulong, :ulong], :ulong
|
37
|
+
attach_function :ReadFile, [:handle, :buffer_out, :ulong, :pointer, :pointer], :bool
|
38
|
+
attach_function :SetEvent, [:handle], :bool
|
39
|
+
attach_function :WaitForMultipleObjects, [:ulong, :pointer, :ulong, :ulong], :ulong
|
40
|
+
attach_function :WriteFile, [:handle, :buffer_in, :ulong, :pointer, :pointer], :bool
|
41
|
+
end
|
42
|
+
|
43
|
+
class AttachPipe
|
44
|
+
include Speednode::WindowsyThings
|
45
|
+
|
46
|
+
CONNECTING_STATE = 0
|
47
|
+
READING_STATE = 1
|
48
|
+
WRITING_STATE = 2
|
49
|
+
INSTANCES = 4
|
50
|
+
PIPE_TIMEOUT = 5000
|
51
|
+
BUFFER_SIZE = 65536
|
52
|
+
|
53
|
+
class Overlapped < FFI::Struct
|
54
|
+
layout(
|
55
|
+
:Internal, :uintptr_t,
|
56
|
+
:InternalHigh, :uintptr_t,
|
57
|
+
:Offset, :ulong,
|
58
|
+
:OffsetHigh, :ulong,
|
59
|
+
:hEvent, :uintptr_t
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(pipe_name, block)
|
64
|
+
@run_block = block
|
65
|
+
@full_pipe_name = "\\\\.\\pipe\\#{pipe_name}"
|
66
|
+
@instances = 1
|
67
|
+
@events = []
|
68
|
+
@events_pointer = FFI::MemoryPointer.new(:uintptr_t, @instances + 1)
|
69
|
+
@pipe = {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def run
|
73
|
+
@running = true
|
74
|
+
create_instance
|
75
|
+
while_loop
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop
|
79
|
+
@running = false
|
80
|
+
warn("DisconnectNamedPipe failed with #{GetLastError()}") if !DisconnectNamedPipe(@pipe[:instance])
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def create_instance
|
86
|
+
@events[0] = CreateEvent(nil, 1, 1, nil)
|
87
|
+
raise "CreateEvent failed with #{GetLastError()}" unless @events[0]
|
88
|
+
|
89
|
+
overlap = Overlapped.new
|
90
|
+
overlap[:hEvent] = @events[0]
|
91
|
+
|
92
|
+
@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 }
|
93
|
+
@pipe[:instance] = CreateNamedPipe(@full_pipe_name,
|
94
|
+
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
95
|
+
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
96
|
+
4,
|
97
|
+
BUFFER_SIZE,
|
98
|
+
BUFFER_SIZE,
|
99
|
+
PIPE_TIMEOUT,
|
100
|
+
nil)
|
101
|
+
raise "CreateNamedPipe failed with #{GetLastError()}" if @pipe[:instance] == INVALID_HANDLE_VALUE
|
102
|
+
@pipe[:pending_io] = connect_to_new_client
|
103
|
+
@pipe[:state] = @pipe[:pending_io] ? CONNECTING_STATE : READING_STATE
|
104
|
+
|
105
|
+
@events_pointer.write_array_of_ulong_long(@events)
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def while_loop
|
110
|
+
while @running
|
111
|
+
# this sleep gives other ruby threads a chance to run
|
112
|
+
# ~10ms is a ruby thread time slice, so we choose something a bit larger
|
113
|
+
# that ruby or the os is free to switch threads
|
114
|
+
sleep 0.010 if @pipe[:state] != WRITING_STATE && @pipe[:state] != READING_STATE
|
115
|
+
|
116
|
+
i = MsgWaitForMultipleObjects(@instances, @events_pointer, 0, 1, QS_ALLINPUT) if @pipe[:state] != WRITING_STATE
|
117
|
+
|
118
|
+
if i > 0
|
119
|
+
next
|
120
|
+
end
|
121
|
+
|
122
|
+
if @pipe[:pending_io]
|
123
|
+
bytes_transferred = FFI::MemoryPointer.new(:ulong)
|
124
|
+
success = GetOverlappedResult(@pipe[:instance], @pipe[:overlap], bytes_transferred, false)
|
125
|
+
|
126
|
+
case @pipe[:state]
|
127
|
+
when CONNECTING_STATE
|
128
|
+
raise "Error #{GetLastError()}" unless success
|
129
|
+
@pipe[:state] = READING_STATE
|
130
|
+
when READING_STATE
|
131
|
+
if !success || bytes_transferred.read_ulong == 0
|
132
|
+
disconnect_and_reconnect(i)
|
133
|
+
next
|
134
|
+
else
|
135
|
+
@pipe[:bytes_read] = bytes_transferred.read_ulong
|
136
|
+
@pipe[:state] = WRITING_STATE
|
137
|
+
end
|
138
|
+
when WRITING_STATE
|
139
|
+
if !success || bytes_transferred.read_ulong != @pipe[:bytes_to_write]
|
140
|
+
disconnect_and_reconnect(i)
|
141
|
+
next
|
142
|
+
else
|
143
|
+
@pipe[:state] = READING_STATE
|
144
|
+
end
|
145
|
+
else
|
146
|
+
raise "Invalid pipe state."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
case @pipe[:state]
|
151
|
+
when READING_STATE
|
152
|
+
bytes_read = FFI::MemoryPointer.new(:ulong)
|
153
|
+
success = ReadFile(@pipe[:instance], @pipe[:request], BUFFER_SIZE, bytes_read, @pipe[:overlap].to_ptr)
|
154
|
+
if success && bytes_read.read_ulong != 0
|
155
|
+
@pipe[:pending_io] = false
|
156
|
+
@pipe[:state] = WRITING_STATE
|
157
|
+
next
|
158
|
+
end
|
159
|
+
|
160
|
+
err = GetLastError()
|
161
|
+
if !success && err == ERROR_IO_PENDING
|
162
|
+
@pipe[:pending_io] = true
|
163
|
+
next
|
164
|
+
end
|
165
|
+
|
166
|
+
disconnect_and_reconnect
|
167
|
+
when WRITING_STATE
|
168
|
+
@pipe[:reply] = @run_block.call(@pipe[:request].get_string(0))
|
169
|
+
@pipe[:bytes_to_write] = @pipe[:reply].bytesize
|
170
|
+
bytes_written = FFI::MemoryPointer.new(:ulong)
|
171
|
+
success = WriteFile(@pipe[:instance], @pipe[:reply], @pipe[:bytes_to_write], bytes_written, @pipe[:overlap].to_ptr)
|
172
|
+
|
173
|
+
if success && bytes_written.read_ulong == @pipe[:bytes_to_write]
|
174
|
+
@pipe[:pending_io] = false
|
175
|
+
@pipe[:state] = READING_STATE
|
176
|
+
next
|
177
|
+
end
|
178
|
+
|
179
|
+
err = GetLastError()
|
180
|
+
|
181
|
+
if !success && err == ERROR_IO_PENDING
|
182
|
+
@pipe[:pending_io] = true
|
183
|
+
next
|
184
|
+
end
|
185
|
+
|
186
|
+
disconnect_and_reconnect
|
187
|
+
else
|
188
|
+
raise "Invalid pipe state."
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def disconnect_and_reconnect
|
194
|
+
FlushFileBuffers(@pipe[:instance])
|
195
|
+
warn("DisconnectNamedPipe failed with #{GetLastError()}") if !DisconnectNamedPipe(@pipe[:instance])
|
196
|
+
|
197
|
+
@pipe[:pending_io] = connect_to_new_client
|
198
|
+
|
199
|
+
@pipe[:state] = @pipe[:pending_io] ? CONNECTING_STATE : READING_STATE
|
200
|
+
end
|
201
|
+
|
202
|
+
def connect_to_new_client
|
203
|
+
pending_io = false
|
204
|
+
@pipe[:request].clear
|
205
|
+
@pipe[:reply].clear
|
206
|
+
connected = ConnectNamedPipe(@pipe[:instance], @pipe[:overlap].to_ptr)
|
207
|
+
last_error = GetLastError()
|
208
|
+
raise "ConnectNamedPipe failed with #{last_error} - #{connected}" if connected != 0
|
209
|
+
|
210
|
+
case last_error
|
211
|
+
when ERROR_IO_PENDING
|
212
|
+
pending_io = true
|
213
|
+
when ERROR_PIPE_CONNECTED
|
214
|
+
SetEvent(@pipe[:overlap][:hEvent])
|
215
|
+
when ERROR_SUCCESS
|
216
|
+
pending_io = true
|
217
|
+
else
|
218
|
+
raise "ConnectNamedPipe failed with error #{last_error}"
|
219
|
+
end
|
220
|
+
|
221
|
+
pending_io
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Speednode
|
4
|
+
class AttachSocket
|
5
|
+
|
6
|
+
attr_reader :socket
|
7
|
+
|
8
|
+
def initialize(socket_path, block)
|
9
|
+
@socket_path = socket_path
|
10
|
+
@run_block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
@running = true
|
15
|
+
client = nil
|
16
|
+
ret = nil
|
17
|
+
@socket = UNIXServer.new(@socket_path)
|
18
|
+
|
19
|
+
while @running do
|
20
|
+
if ret
|
21
|
+
begin
|
22
|
+
client = @socket.accept_nonblock
|
23
|
+
request = client.gets("\x04")
|
24
|
+
result = @run_block.call(request)
|
25
|
+
client.write result
|
26
|
+
client.flush
|
27
|
+
client.close
|
28
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
29
|
+
end
|
30
|
+
end
|
31
|
+
ret = begin
|
32
|
+
IO.select([@socket], nil, nil, nil) || next
|
33
|
+
rescue Errno::EBADF
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
@running = false
|
40
|
+
@socket.close
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Speednode
|
2
|
+
class << self
|
3
|
+
attr_accessor :node_paths
|
4
|
+
|
5
|
+
def set_node_paths
|
6
|
+
np_sep = Gem.win_platform? ? ';' : ':'
|
7
|
+
existing_node_path = ENV['NODE_PATH']
|
8
|
+
temp_node_path = ''
|
9
|
+
if existing_node_path.nil? || existing_node_path.empty?
|
10
|
+
temp_node_path = Speednode.node_paths.join(np_sep)
|
11
|
+
else
|
12
|
+
if existing_node_path.end_with?(np_sep)
|
13
|
+
temp_node_path = existing_node_path + Speednode.node_paths.join(np_sep)
|
14
|
+
else
|
15
|
+
temp_node_path = existing_node_path + np_sep + Speednode.node_paths.join(np_sep)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
ENV['NODE_PATH'] = temp_node_path.split(np_sep).uniq.join(np_sep)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self.node_paths = []
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ExecJS
|
2
|
+
class << self
|
3
|
+
def permissive_bench(source, options = {})
|
4
|
+
runtime.permissive_bench(source, options)
|
5
|
+
end
|
6
|
+
|
7
|
+
def permissive_exec(source, options = {})
|
8
|
+
runtime.permissive_exec(source, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def permissive_eval(source, options = {})
|
12
|
+
runtime.permissive_eval(source, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def permissive_compile(source, options = {})
|
16
|
+
runtime.permissive_compile(source, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ExecJS
|
2
|
+
# Abstract base class for runtimes
|
3
|
+
class Runtime
|
4
|
+
def permissive_bench(source, options = {})
|
5
|
+
context = permissive_compile("", options)
|
6
|
+
context.bench(source, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def permissive_exec(source, options = {})
|
10
|
+
context = permissive_compile("", options)
|
11
|
+
context.exec(source, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def permissive_eval(source, options = {})
|
15
|
+
context = permissive_compile("", options)
|
16
|
+
context.eval(source, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def permissive_compile(source, options = {})
|
20
|
+
context_class.new(self, source, options.merge({permissive: true}))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ExecJS
|
2
|
+
module Runtimes
|
3
|
+
Speednode = Speednode::Runtime.new(
|
4
|
+
name: 'Speednode Node.js (V8)',
|
5
|
+
command: %w[node nodejs],
|
6
|
+
runner_path: File.join(File.dirname(__FILE__), 'runner.js'),
|
7
|
+
encoding: 'UTF-8'
|
8
|
+
)
|
9
|
+
runtimes.unshift(Speednode)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Speednode
|
2
|
+
class NodeCommand
|
3
|
+
def self.which(command)
|
4
|
+
Array(command).find do |name|
|
5
|
+
name, args = name.split(/\s+/, 2)
|
6
|
+
path = locate_executable(name)
|
7
|
+
|
8
|
+
next unless path
|
9
|
+
|
10
|
+
args ? "#{path} #{args}" : path
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cached(command)
|
15
|
+
@cached_command ||= which(command)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.locate_executable(command)
|
21
|
+
commands = Array(command)
|
22
|
+
if ExecJS.windows? && File.extname(command) == ""
|
23
|
+
ENV['PATHEXT'].split(File::PATH_SEPARATOR).each { |p|
|
24
|
+
commands << (command + p)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
commands.find { |cmd|
|
29
|
+
if File.executable? cmd
|
30
|
+
cmd
|
31
|
+
else
|
32
|
+
path = ENV['PATH'].split(File::PATH_SEPARATOR).find { |p|
|
33
|
+
full_path = File.join(p, cmd)
|
34
|
+
File.executable?(full_path) && File.file?(full_path)
|
35
|
+
}
|
36
|
+
path && File.expand_path(cmd, path)
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|