isomorfeus-speednode 0.3.1 → 0.4.0
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/LICENSE +22 -22
- data/README.md +126 -98
- data/lib/isomorfeus/speednode/attach_pipe.rb +226 -0
- data/lib/isomorfeus/speednode/attach_socket.rb +46 -0
- data/lib/isomorfeus/speednode/config.rb +23 -0
- data/lib/isomorfeus/speednode/runner.js +324 -239
- data/lib/isomorfeus/speednode/runtime/context.rb +176 -0
- data/lib/isomorfeus/speednode/runtime/vm.rb +199 -0
- data/lib/isomorfeus/speednode/runtime/vm_command.rb +40 -0
- data/lib/isomorfeus/speednode/runtime.rb +45 -288
- data/lib/isomorfeus/speednode/version.rb +1 -1
- data/lib/isomorfeus-speednode.rb +21 -12
- metadata +26 -6
@@ -0,0 +1,176 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Speednode
|
3
|
+
class Runtime < ExecJS::Runtime
|
4
|
+
class Context < ::ExecJS::Runtime::Context
|
5
|
+
def self.finalize(runtime, uuid)
|
6
|
+
proc { runtime.vm.delete_context(uuid) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(runtime, source = "", options = {})
|
10
|
+
|
11
|
+
@runtime = runtime
|
12
|
+
@uuid = SecureRandom.uuid
|
13
|
+
@permissive = !!options.delete(:permissive)
|
14
|
+
@vm = @runtime.vm
|
15
|
+
@context = nil
|
16
|
+
@timeout = options[:timeout] ? options[:timeout]/1000 : 600
|
17
|
+
|
18
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@runtime, @uuid))
|
19
|
+
|
20
|
+
filename = options.delete(:filename)
|
21
|
+
source = File.read(filename) if filename
|
22
|
+
|
23
|
+
begin
|
24
|
+
source = encode(source)
|
25
|
+
rescue
|
26
|
+
source = source.force_encoding('UTF-8')
|
27
|
+
end
|
28
|
+
|
29
|
+
@permissive ? raw_createp(source, options) : raw_create(source, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# def options
|
33
|
+
# @vm.context_options(@uuid)
|
34
|
+
# end
|
35
|
+
|
36
|
+
def attach(func, procedure = nil, &block)
|
37
|
+
raise "#attach requires a permissive context." unless @permissive
|
38
|
+
run_block = block_given? ? block : procedure
|
39
|
+
Isomorfeus::Speednode::Runtime.attach_proc(@uuid, func, run_block)
|
40
|
+
@vm.attach(@uuid, func)
|
41
|
+
end
|
42
|
+
|
43
|
+
def await(source)
|
44
|
+
raw_eval <<~JAVASCRIPT
|
45
|
+
(async () => {
|
46
|
+
global.__LastExecutionFinished = false;
|
47
|
+
global.__LastResult = null;
|
48
|
+
global.__LastErr = null;
|
49
|
+
global.__LastResult = await #{source};
|
50
|
+
global.__LastExecutionFinished = true;
|
51
|
+
})().catch(function(err) {
|
52
|
+
global.__LastResult = null;
|
53
|
+
global.__LastErr = err;
|
54
|
+
global.__LastExecutionFinished = true;
|
55
|
+
})
|
56
|
+
JAVASCRIPT
|
57
|
+
await_result
|
58
|
+
end
|
59
|
+
|
60
|
+
def call(identifier, *args)
|
61
|
+
raw_eval("#{identifier}.apply(this, #{::Oj.dump(args, mode: :strict)})")
|
62
|
+
end
|
63
|
+
|
64
|
+
def eval(source, _options = nil)
|
65
|
+
raw_eval(source) if /\S/ =~ source
|
66
|
+
end
|
67
|
+
|
68
|
+
def exec(source, _options = nil)
|
69
|
+
raw_exec("(function(){#{source}})()")
|
70
|
+
end
|
71
|
+
|
72
|
+
def permissive?
|
73
|
+
@permissive
|
74
|
+
end
|
75
|
+
|
76
|
+
def permissive_eval(source, _options = nil)
|
77
|
+
raise "Context not permissive!" unless @permissive
|
78
|
+
raw_eval(source) if /\S/ =~ source
|
79
|
+
end
|
80
|
+
|
81
|
+
def permissive_exec(source, _options = nil)
|
82
|
+
raise "Context not permissive!" unless @permissive
|
83
|
+
raw_exec("(function(){#{source}})()")
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def raw_eval(source)
|
89
|
+
extract_result(@vm.eval(@uuid, encode(source)))
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def raw_exec(source)
|
94
|
+
extract_result(@vm.exec(@uuid, encode(source)))
|
95
|
+
end
|
96
|
+
|
97
|
+
def raw_create(source, options)
|
98
|
+
return if @context
|
99
|
+
source = encode(source)
|
100
|
+
result = @vm.create(@uuid, source, options)
|
101
|
+
@context = true
|
102
|
+
extract_result(result)
|
103
|
+
end
|
104
|
+
|
105
|
+
def raw_createp(source, options)
|
106
|
+
return if @context
|
107
|
+
source = encode(source)
|
108
|
+
result = @vm.createp(@uuid, source, options)
|
109
|
+
@context = true
|
110
|
+
extract_result(result)
|
111
|
+
end
|
112
|
+
|
113
|
+
def extract_result(output)
|
114
|
+
if output[0] == 'ok'
|
115
|
+
output[1]
|
116
|
+
else
|
117
|
+
_status, value, stack = output
|
118
|
+
stack ||= ""
|
119
|
+
stack = stack.split("\n").map do |line|
|
120
|
+
line.sub(" at ", "").strip
|
121
|
+
end
|
122
|
+
stack.reject! do |line|
|
123
|
+
line.include?('(node:') ||
|
124
|
+
line.include?('lib\isomorfeus\speednode\runner.js') ||
|
125
|
+
line.include?('lib/isomorfeus/speednode/runner.js')
|
126
|
+
end
|
127
|
+
stack.shift unless stack[0].to_s.include?("(execjs)")
|
128
|
+
error_class = value =~ /SyntaxError:/ ? ExecJS::RuntimeError : ExecJS::ProgramError
|
129
|
+
error = error_class.new(value)
|
130
|
+
error.set_backtrace(stack + caller)
|
131
|
+
raise error
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def await_result
|
136
|
+
start_time = Time.now
|
137
|
+
while !execution_finished? && !timed_out?(start_time)
|
138
|
+
sleep 0.005
|
139
|
+
end
|
140
|
+
result = exec <<~JAVASCRIPT
|
141
|
+
if (global.__LastExecutionFinished === true) {
|
142
|
+
var err = global.__LastErr;
|
143
|
+
var result = global.__LastResult;
|
144
|
+
|
145
|
+
global.__LastErr = null;
|
146
|
+
global.__LastResult = null;
|
147
|
+
global.__LastExecutionFinished = false;
|
148
|
+
|
149
|
+
if (err) { return ['err', ['', err].join(''), err.stack]; }
|
150
|
+
else if (typeof result === 'undefined' && result !== null) { return ['ok']; }
|
151
|
+
else {
|
152
|
+
try { return ['ok', result]; }
|
153
|
+
catch (err) { return ['err', ['', err].join(''), err.stack]; }
|
154
|
+
}
|
155
|
+
} else {
|
156
|
+
var new_err = new Error('Last command did not yet finish execution!');
|
157
|
+
return ['err', ['', new_err].join(''), new_err.stack];
|
158
|
+
}
|
159
|
+
JAVASCRIPT
|
160
|
+
extract_result(result)
|
161
|
+
end
|
162
|
+
|
163
|
+
def execution_finished?
|
164
|
+
eval 'global.__LastExecutionFinished'
|
165
|
+
end
|
166
|
+
|
167
|
+
def timed_out?(start_time)
|
168
|
+
if (Time.now - start_time) > @timeout
|
169
|
+
raise "Command Execution timed out!"
|
170
|
+
end
|
171
|
+
false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
if Gem.win_platform?
|
2
|
+
require 'securerandom'
|
3
|
+
require 'win32/pipe'
|
4
|
+
|
5
|
+
module Win32
|
6
|
+
class Pipe
|
7
|
+
def write(data)
|
8
|
+
bytes = FFI::MemoryPointer.new(:ulong)
|
9
|
+
|
10
|
+
raise Error, "no pipe created" unless @pipe
|
11
|
+
|
12
|
+
if @asynchronous
|
13
|
+
bool = WriteFile(@pipe, data, data.bytesize, bytes, @overlapped)
|
14
|
+
bytes_written = bytes.read_ulong
|
15
|
+
|
16
|
+
if bool && bytes_written > 0
|
17
|
+
@pending_io = false
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
|
21
|
+
error = GetLastError()
|
22
|
+
if !bool && error == ERROR_IO_PENDING
|
23
|
+
@pending_io = true
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
|
27
|
+
return false
|
28
|
+
else
|
29
|
+
unless WriteFile(@pipe, data, data.bytesize, bytes, nil)
|
30
|
+
raise SystemCallError.new("WriteFile", FFI.errno)
|
31
|
+
end
|
32
|
+
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Isomorfeus
|
41
|
+
module Speednode
|
42
|
+
class Runtime < ExecJS::Runtime
|
43
|
+
class VM
|
44
|
+
attr_reader :responder
|
45
|
+
|
46
|
+
def initialize(options)
|
47
|
+
@mutex = 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
|
+
def self.finalize(socket, socket_dir, socket_path, pid)
|
59
|
+
proc do
|
60
|
+
Isomorfeus::Speednode::Runtime.responders(socket_path).kill if Isomorfeus::Speednode::Runtime.responders(socket_path)
|
61
|
+
exit_node(socket, socket_dir, socket_path, pid)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.exit_node(socket, socket_dir, socket_path, pid)
|
66
|
+
VMCommand.new(socket, "exit", [0]).execute
|
67
|
+
socket.close
|
68
|
+
File.unlink(socket_path)
|
69
|
+
Dir.rmdir(socket_dir)
|
70
|
+
if Gem.win_platform?
|
71
|
+
# SIGINT or SIGKILL are unreliable on Windows, try native taskkill first
|
72
|
+
Process.kill('KILL', pid) unless system("taskkill /f /t /pid #{pid} >NUL 2>NUL")
|
73
|
+
else
|
74
|
+
Process.kill 'KILL', pid
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def eval(context, source)
|
79
|
+
command("eval", {'context' => context, 'source' => source})
|
80
|
+
end
|
81
|
+
|
82
|
+
def exec(context, source)
|
83
|
+
command("exec", {'context' => context, 'source' => source})
|
84
|
+
end
|
85
|
+
|
86
|
+
def create(context, source, options)
|
87
|
+
command("create", {'context' => context, 'source' => source, 'options' => options})
|
88
|
+
end
|
89
|
+
|
90
|
+
def createp(context, source, options)
|
91
|
+
command("createp", {'context' => context, 'source' => source, 'options' => options})
|
92
|
+
end
|
93
|
+
|
94
|
+
def attach(context, func)
|
95
|
+
create_responder(context) unless responder
|
96
|
+
command("attach", {'context' => context, 'func' => func })
|
97
|
+
end
|
98
|
+
|
99
|
+
def delete_context(context)
|
100
|
+
command("deleteContext", context)
|
101
|
+
end
|
102
|
+
|
103
|
+
# def context_options(context)
|
104
|
+
# command('ctxo', {'context' => context })
|
105
|
+
# end
|
106
|
+
|
107
|
+
def start
|
108
|
+
@mutex.synchronize do
|
109
|
+
start_without_synchronization
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def start_without_synchronization
|
116
|
+
return if @started
|
117
|
+
if ExecJS.windows?
|
118
|
+
@socket_dir = nil
|
119
|
+
@socket_path = SecureRandom.uuid
|
120
|
+
else
|
121
|
+
@socket_dir = Dir.mktmpdir("isomorfeus-speednode-")
|
122
|
+
@socket_path = File.join(@socket_dir, "socket")
|
123
|
+
end
|
124
|
+
@pid = Process.spawn({"SOCKET_PATH" => @socket_path}, @options[:binary], @options[:source_maps], @options[:runner_path])
|
125
|
+
|
126
|
+
retries = 20
|
127
|
+
|
128
|
+
if ExecJS.windows?
|
129
|
+
timeout_or_connected = false
|
130
|
+
begin
|
131
|
+
retries -= 1
|
132
|
+
begin
|
133
|
+
@socket = Win32::Pipe::Client.new(@socket_path, Win32::Pipe::ACCESS_DUPLEX)
|
134
|
+
rescue
|
135
|
+
sleep 0.1
|
136
|
+
raise "Unable to start nodejs process in time" if retries == 0
|
137
|
+
next
|
138
|
+
end
|
139
|
+
timeout_or_connected = true
|
140
|
+
end until timeout_or_connected
|
141
|
+
else
|
142
|
+
while !File.exist?(@socket_path)
|
143
|
+
sleep 0.1
|
144
|
+
retries -= 1
|
145
|
+
raise "Unable to start nodejs process in time" if retries == 0
|
146
|
+
end
|
147
|
+
|
148
|
+
@socket = UNIXSocket.new(@socket_path)
|
149
|
+
end
|
150
|
+
|
151
|
+
@started = true
|
152
|
+
|
153
|
+
at_exit do
|
154
|
+
begin
|
155
|
+
self.class.exit_node(@socket, @socket_dir, @socket_path, @pid) unless @socket.closed?
|
156
|
+
rescue
|
157
|
+
# do nothing
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@socket, @socket_dir, @socket_path, @pid))
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_responder(context)
|
165
|
+
start unless @started
|
166
|
+
run_block = Proc.new do |request|
|
167
|
+
args = ::Oj.load(request.chop!, mode: :strict)
|
168
|
+
req_context = args[0]
|
169
|
+
method = args[1]
|
170
|
+
method_args = args[2]
|
171
|
+
begin
|
172
|
+
result = Isomorfeus::Speednode::Runtime.attached_procs[req_context][method].call(*method_args)
|
173
|
+
::Oj.dump(['ok', result], mode: :strict)
|
174
|
+
rescue Exception => err
|
175
|
+
::Oj.dump(['err', err.class.to_s, [err.message].concat(err.backtrace)], mode: :strict)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
responder_path = @socket_path + '_responder'
|
179
|
+
@responder = Thread.new do
|
180
|
+
if ExecJS.windows?
|
181
|
+
Isomorfeus::Speednode::AttachPipe.new(responder_path, run_block).run
|
182
|
+
else
|
183
|
+
Isomorfeus::Speednode::AttachSocket.new(responder_path, run_block).run
|
184
|
+
end
|
185
|
+
end
|
186
|
+
Isomorfeus::Speednode::Runtime.responders[@socket_path] = @responder_thread
|
187
|
+
@responder.run
|
188
|
+
end
|
189
|
+
|
190
|
+
def command(cmd, *arguments)
|
191
|
+
@mutex.synchronize do
|
192
|
+
start_without_synchronization unless @started
|
193
|
+
VMCommand.new(@socket, cmd, arguments).execute
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Speednode
|
3
|
+
class Runtime < ExecJS::Runtime
|
4
|
+
class VMCommand
|
5
|
+
def initialize(socket, cmd, arguments)
|
6
|
+
@socket = socket
|
7
|
+
@cmd = cmd
|
8
|
+
@arguments = arguments
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
result = ''
|
13
|
+
message = ::Oj.dump({ 'cmd' => @cmd, 'args' => @arguments }, mode: :strict)
|
14
|
+
message = message + "\x04"
|
15
|
+
bytes_to_send = message.bytesize
|
16
|
+
sent_bytes = 0
|
17
|
+
|
18
|
+
if ExecJS.windows?
|
19
|
+
@socket.write(message)
|
20
|
+
begin
|
21
|
+
result << @socket.read
|
22
|
+
end until result.end_with?("\x04")
|
23
|
+
else
|
24
|
+
sent_bytes = @socket.sendmsg(message)
|
25
|
+
if sent_bytes < bytes_to_send
|
26
|
+
while sent_bytes < bytes_to_send
|
27
|
+
sent_bytes += @socket.sendmsg(message.byteslice((sent_bytes)..-1))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
result << @socket.recvmsg()[0]
|
33
|
+
end until result.end_with?("\x04")
|
34
|
+
end
|
35
|
+
::Oj.load(result.chop!, create_additions: false)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,288 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@cmd = cmd
|
47
|
-
@arguments = arguments
|
48
|
-
end
|
49
|
-
|
50
|
-
def execute
|
51
|
-
result = ''
|
52
|
-
message = ::Oj.dump({ 'cmd' => @cmd, 'args' => @arguments }, mode: :strict)
|
53
|
-
message = message + "\x04"
|
54
|
-
bytes_to_send = message.bytesize
|
55
|
-
sent_bytes = 0
|
56
|
-
|
57
|
-
if ExecJS.windows?
|
58
|
-
@socket.write(message)
|
59
|
-
begin
|
60
|
-
result << @socket.read
|
61
|
-
end until result.end_with?("\x04")
|
62
|
-
else
|
63
|
-
sent_bytes = @socket.sendmsg(message)
|
64
|
-
if sent_bytes < bytes_to_send
|
65
|
-
while sent_bytes < bytes_to_send
|
66
|
-
sent_bytes += @socket.sendmsg(message.byteslice((sent_bytes)..-1))
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
begin
|
71
|
-
result << @socket.recvmsg()[0]
|
72
|
-
end until result.end_with?("\x04")
|
73
|
-
end
|
74
|
-
::Oj.load(result.chop!, create_additions: false)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class VM
|
79
|
-
def initialize(options)
|
80
|
-
@mutex = Mutex.new
|
81
|
-
@socket_path = nil
|
82
|
-
@options = options
|
83
|
-
@started = false
|
84
|
-
@socket = nil
|
85
|
-
end
|
86
|
-
|
87
|
-
def started?
|
88
|
-
@started
|
89
|
-
end
|
90
|
-
|
91
|
-
def self.finalize(socket, socket_dir, socket_path, pid)
|
92
|
-
proc { exit_node(socket, socket_dir, socket_path, pid) }
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.exit_node(socket, socket_dir, socket_path, pid)
|
96
|
-
VMCommand.new(socket, "exit", [0]).execute
|
97
|
-
socket.close
|
98
|
-
File.unlink(socket_path)
|
99
|
-
Dir.rmdir(socket_dir)
|
100
|
-
Process.kill('KILL', pid)
|
101
|
-
end
|
102
|
-
|
103
|
-
def exec(context, source)
|
104
|
-
command("exec", {'context' => context, 'source' => source})
|
105
|
-
end
|
106
|
-
|
107
|
-
def execp(context, source)
|
108
|
-
command("execp", {'context' => context, 'source' => source})
|
109
|
-
end
|
110
|
-
|
111
|
-
def delete_context(context)
|
112
|
-
command("deleteContext", context)
|
113
|
-
end
|
114
|
-
|
115
|
-
def start
|
116
|
-
@mutex.synchronize do
|
117
|
-
start_without_synchronization
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
private
|
122
|
-
|
123
|
-
def start_without_synchronization
|
124
|
-
return if @started
|
125
|
-
if ExecJS.windows?
|
126
|
-
@socket_dir = nil
|
127
|
-
@socket_path = SecureRandom.uuid
|
128
|
-
else
|
129
|
-
@socket_dir = Dir.mktmpdir("isomorfeus-speednode-")
|
130
|
-
@socket_path = File.join(@socket_dir, "socket")
|
131
|
-
end
|
132
|
-
@pid = Process.spawn({"SOCKET_PATH" => @socket_path}, @options[:binary], @options[:runner_path])
|
133
|
-
|
134
|
-
retries = 20
|
135
|
-
|
136
|
-
if ExecJS.windows?
|
137
|
-
timeout_or_connected = false
|
138
|
-
begin
|
139
|
-
retries -= 1
|
140
|
-
begin
|
141
|
-
@socket = Win32::Pipe::Client.new(@socket_path, Win32::Pipe::ACCESS_DUPLEX)
|
142
|
-
rescue
|
143
|
-
sleep 0.1
|
144
|
-
raise "Unable to start nodejs process in time" if retries == 0
|
145
|
-
next
|
146
|
-
end
|
147
|
-
timeout_or_connected = true
|
148
|
-
end until timeout_or_connected
|
149
|
-
else
|
150
|
-
while !File.exist?(@socket_path)
|
151
|
-
sleep 0.1
|
152
|
-
retries -= 1
|
153
|
-
raise "Unable to start nodejs process in time" if retries == 0
|
154
|
-
end
|
155
|
-
|
156
|
-
@socket = UNIXSocket.new(@socket_path)
|
157
|
-
end
|
158
|
-
|
159
|
-
@started = true
|
160
|
-
|
161
|
-
at_exit do
|
162
|
-
begin
|
163
|
-
self.class.exit_node(@socket, @socket_dir, @socket_path, @pid) unless @socket.closed?
|
164
|
-
rescue
|
165
|
-
# do nothing
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
ObjectSpace.define_finalizer(self, self.class.finalize(@socket, @socket_dir, @socket_path, @pid))
|
170
|
-
end
|
171
|
-
|
172
|
-
def command(cmd, *arguments)
|
173
|
-
@mutex.synchronize do
|
174
|
-
start_without_synchronization
|
175
|
-
VMCommand.new(@socket, cmd, arguments).execute
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
class Context < ::ExecJS::Runtime::Context
|
181
|
-
def initialize(runtime, source = "", options = {})
|
182
|
-
@runtime = runtime
|
183
|
-
@uuid = SecureRandom.uuid
|
184
|
-
@permissive = !!options[:permissive]
|
185
|
-
|
186
|
-
ObjectSpace.define_finalizer(self, self.class.finalize(@runtime, @uuid))
|
187
|
-
|
188
|
-
begin
|
189
|
-
source = encode(source)
|
190
|
-
rescue
|
191
|
-
source = source.force_encoding('UTF-8')
|
192
|
-
end
|
193
|
-
|
194
|
-
@permissive ? raw_execp(source) : raw_exec(source)
|
195
|
-
end
|
196
|
-
|
197
|
-
def self.finalize(runtime, uuid)
|
198
|
-
proc { runtime.vm.delete_context(uuid) }
|
199
|
-
end
|
200
|
-
|
201
|
-
def call(identifier, *args)
|
202
|
-
eval "#{identifier}.apply(this, #{::Oj.dump(args, mode: :strict)})"
|
203
|
-
end
|
204
|
-
|
205
|
-
def eval(source, options = {})
|
206
|
-
raw_exec("(#{source})") if /\S/ =~ source
|
207
|
-
end
|
208
|
-
|
209
|
-
def exec(source, options = {})
|
210
|
-
raw_exec("(function(){#{source}})()")
|
211
|
-
end
|
212
|
-
|
213
|
-
def permissive?
|
214
|
-
@permissive
|
215
|
-
end
|
216
|
-
|
217
|
-
def permissive_eval(source, options = {})
|
218
|
-
raw_execp("(#{source})") if /\S/ =~ source
|
219
|
-
end
|
220
|
-
|
221
|
-
def permissive_exec(source, options = {})
|
222
|
-
raw_execp("(function(){#{source}})()")
|
223
|
-
end
|
224
|
-
|
225
|
-
def raw_exec(source)
|
226
|
-
source = encode(source)
|
227
|
-
|
228
|
-
result = @runtime.vm.exec(@uuid, source)
|
229
|
-
extract_result(result)
|
230
|
-
end
|
231
|
-
|
232
|
-
def raw_execp(source)
|
233
|
-
source = encode(source)
|
234
|
-
|
235
|
-
result = @runtime.vm.execp(@uuid, source)
|
236
|
-
extract_result(result)
|
237
|
-
end
|
238
|
-
|
239
|
-
protected
|
240
|
-
|
241
|
-
def extract_result(output)
|
242
|
-
status, value, stack = output
|
243
|
-
if status == "ok"
|
244
|
-
value
|
245
|
-
else
|
246
|
-
stack ||= ""
|
247
|
-
stack = stack.split("\n").map do |line|
|
248
|
-
line.sub(" at ", "").strip
|
249
|
-
end
|
250
|
-
stack.reject! { |line| ["eval code", "eval@[native code]"].include?(line) }
|
251
|
-
stack.shift unless stack[0].to_s.include?("(execjs)")
|
252
|
-
error_class = value =~ /SyntaxError:/ ? ExecJS::RuntimeError : ExecJS::ProgramError
|
253
|
-
error = error_class.new(value)
|
254
|
-
error.set_backtrace(stack + caller)
|
255
|
-
raise error
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
attr_reader :name, :vm
|
261
|
-
|
262
|
-
def initialize(options)
|
263
|
-
@name = options[:name]
|
264
|
-
@binary = Isomorfeus::Speednode::NodeCommand.cached(options[:command])
|
265
|
-
@runner_path = options[:runner_path]
|
266
|
-
@encoding = options[:encoding]
|
267
|
-
@deprecated = !!options[:deprecated]
|
268
|
-
|
269
|
-
@vm = VM.new(
|
270
|
-
binary: @binary,
|
271
|
-
runner_path: @runner_path
|
272
|
-
)
|
273
|
-
|
274
|
-
@popen_options = {}
|
275
|
-
@popen_options[:external_encoding] = @encoding if @encoding
|
276
|
-
@popen_options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8'
|
277
|
-
end
|
278
|
-
|
279
|
-
def available?
|
280
|
-
@binary ? true : false
|
281
|
-
end
|
282
|
-
|
283
|
-
def deprecated?
|
284
|
-
@deprecated
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
1
|
+
module Isomorfeus
|
2
|
+
module Speednode
|
3
|
+
class Runtime < ExecJS::Runtime
|
4
|
+
def self.attach_proc(context_id, func, run_block)
|
5
|
+
attached_procs[context_id] = { func => run_block }
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.attached_procs
|
9
|
+
@attached_procs ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.responders
|
13
|
+
@responders ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :name, :vm
|
17
|
+
|
18
|
+
def initialize(options)
|
19
|
+
@name = options[:name]
|
20
|
+
@binary = Isomorfeus::Speednode::NodeCommand.cached(options[:command])
|
21
|
+
@runner_path = options[:runner_path]
|
22
|
+
@encoding = options[:encoding]
|
23
|
+
@deprecated = !!options[:deprecated]
|
24
|
+
|
25
|
+
@vm = VM.new(
|
26
|
+
binary: @binary,
|
27
|
+
source_maps: '--enable-source-maps',
|
28
|
+
runner_path: @runner_path
|
29
|
+
)
|
30
|
+
|
31
|
+
@popen_options = {}
|
32
|
+
@popen_options[:external_encoding] = @encoding if @encoding
|
33
|
+
@popen_options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8'
|
34
|
+
end
|
35
|
+
|
36
|
+
def available?
|
37
|
+
@binary ? true : false
|
38
|
+
end
|
39
|
+
|
40
|
+
def deprecated?
|
41
|
+
@deprecated
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|