speednode 0.8.1
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 +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
@@ -0,0 +1,223 @@
|
|
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 Speednode
|
41
|
+
class Runtime < ExecJS::Runtime
|
42
|
+
class VM
|
43
|
+
def self.finalize(socket, socket_dir, socket_path, pid)
|
44
|
+
proc do
|
45
|
+
::Speednode::Runtime.responders[socket_path].kill if ::Speednode::Runtime.responders[socket_path]
|
46
|
+
exit_node(socket, socket_dir, socket_path, pid)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.exit_node(socket, socket_dir, socket_path, pid)
|
51
|
+
VMCommand.new(socket, "exit", 0).execute rescue nil
|
52
|
+
socket.close
|
53
|
+
File.unlink(socket_path) if File.exist?(socket_path)
|
54
|
+
Dir.rmdir(socket_dir) if socket_dir && Dir.exist?(socket_dir)
|
55
|
+
if Gem.win_platform?
|
56
|
+
# SIGINT or SIGKILL are unreliable on Windows, try native taskkill first
|
57
|
+
unless system("taskkill /f /t /pid #{pid} >NUL 2>NUL")
|
58
|
+
Process.kill('KILL', pid) rescue nil
|
59
|
+
end
|
60
|
+
else
|
61
|
+
Process.kill('KILL', pid) rescue nil
|
62
|
+
end
|
63
|
+
rescue
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
attr_reader :responder
|
68
|
+
|
69
|
+
def initialize(options)
|
70
|
+
@mutex = ::Thread::Mutex.new
|
71
|
+
@socket_path = nil
|
72
|
+
@options = options
|
73
|
+
@started = false
|
74
|
+
@socket = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def started?
|
78
|
+
@started
|
79
|
+
end
|
80
|
+
|
81
|
+
def evsc(context, key)
|
82
|
+
command('evsc', { 'context' => context, 'key' => key })
|
83
|
+
end
|
84
|
+
|
85
|
+
def scsc(context, key, source)
|
86
|
+
command('scsc', { 'context' => context, 'key' => key, 'source' => source })
|
87
|
+
end
|
88
|
+
|
89
|
+
def eval(context, source)
|
90
|
+
command('eval', {'context' => context, 'source' => source})
|
91
|
+
end
|
92
|
+
|
93
|
+
def exec(context, source)
|
94
|
+
command('exec', {'context' => context, 'source' => source})
|
95
|
+
end
|
96
|
+
|
97
|
+
def bench(context, source)
|
98
|
+
command('bench', {'context' => context, 'source' => source})
|
99
|
+
end
|
100
|
+
|
101
|
+
def create(context, source, options)
|
102
|
+
command('create', {'context' => context, 'source' => source, 'options' => options})
|
103
|
+
end
|
104
|
+
|
105
|
+
def created(context, source, options)
|
106
|
+
command('created', {'context' => context, 'source' => source, 'options' => options})
|
107
|
+
end
|
108
|
+
|
109
|
+
def createp(context, source, options)
|
110
|
+
command('createp', {'context' => context, 'source' => source, 'options' => options})
|
111
|
+
end
|
112
|
+
|
113
|
+
def attach(context, func)
|
114
|
+
create_responder(context) unless responder
|
115
|
+
command('attach', {'context' => context, 'func' => func })
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete_context(context)
|
119
|
+
@mutex.synchronize do
|
120
|
+
VMCommand.new(@socket, "deleteContext", context).execute rescue nil
|
121
|
+
end
|
122
|
+
rescue ThreadError
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# def context_options(context)
|
127
|
+
# command('ctxo', {'context' => context })
|
128
|
+
# end
|
129
|
+
|
130
|
+
def start
|
131
|
+
@mutex.synchronize do
|
132
|
+
start_without_synchronization
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def stop
|
137
|
+
return unless @started
|
138
|
+
@mutex.synchronize do
|
139
|
+
self.class.exit_node(@socket, @socket_dir, @socket_path, @pid)
|
140
|
+
@socket_path = nil
|
141
|
+
@started = false
|
142
|
+
@socket = nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def start_without_synchronization
|
149
|
+
return if @started
|
150
|
+
if ExecJS.windows?
|
151
|
+
@socket_dir = nil
|
152
|
+
@socket_path = SecureRandom.uuid
|
153
|
+
else
|
154
|
+
@socket_dir = Dir.mktmpdir("iso-speednode-")
|
155
|
+
@socket_path = File.join(@socket_dir, "socket")
|
156
|
+
end
|
157
|
+
@pid = Process.spawn({"SOCKET_PATH" => @socket_path}, @options[:binary], '--expose-gc', @options[:source_maps], @options[:runner_path])
|
158
|
+
|
159
|
+
retries = 500
|
160
|
+
|
161
|
+
if ExecJS.windows?
|
162
|
+
timeout_or_connected = false
|
163
|
+
begin
|
164
|
+
retries -= 1
|
165
|
+
begin
|
166
|
+
@socket = Win32::Pipe::Client.new(@socket_path, Win32::Pipe::ACCESS_DUPLEX)
|
167
|
+
rescue
|
168
|
+
sleep 0.1
|
169
|
+
raise "Unable to start nodejs process in time" if retries == 0
|
170
|
+
next
|
171
|
+
end
|
172
|
+
timeout_or_connected = true
|
173
|
+
end until timeout_or_connected
|
174
|
+
else
|
175
|
+
while !File.exist?(@socket_path)
|
176
|
+
sleep 0.1
|
177
|
+
retries -= 1
|
178
|
+
raise "Unable to start nodejs process in time" if retries == 0
|
179
|
+
end
|
180
|
+
|
181
|
+
@socket = UNIXSocket.new(@socket_path)
|
182
|
+
end
|
183
|
+
|
184
|
+
@started = true
|
185
|
+
|
186
|
+
Kernel.at_exit { self.class.finalize(@socket, @socket_dir, @socket_path, @pid).call }
|
187
|
+
end
|
188
|
+
|
189
|
+
def create_responder(context)
|
190
|
+
start unless @started
|
191
|
+
run_block = Proc.new do |request|
|
192
|
+
args = ::Oj.load(request.chop!, mode: :strict)
|
193
|
+
req_context = args[0]
|
194
|
+
method = args[1]
|
195
|
+
method_args = args[2]
|
196
|
+
begin
|
197
|
+
result = ::Speednode::Runtime.attached_procs[req_context][method].call(*method_args)
|
198
|
+
::Oj.dump(['ok', result], mode: :strict)
|
199
|
+
rescue Exception => err
|
200
|
+
::Oj.dump(['err', err.class.to_s, [err.message].concat(err.backtrace)], mode: :strict)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
responder_path = @socket_path + '_responder'
|
204
|
+
@responder = Thread.new do
|
205
|
+
if ExecJS.windows?
|
206
|
+
::Speednode::AttachPipe.new(responder_path, run_block).run
|
207
|
+
else
|
208
|
+
::Speednode::AttachSocket.new(responder_path, run_block).run
|
209
|
+
end
|
210
|
+
end
|
211
|
+
::Speednode::Runtime.responders[@socket_path] = @responder_thread
|
212
|
+
@responder.run
|
213
|
+
end
|
214
|
+
|
215
|
+
def command(cmd, argument)
|
216
|
+
@mutex.synchronize do
|
217
|
+
start_without_synchronization unless @started
|
218
|
+
VMCommand.new(@socket, cmd, argument).execute
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Speednode
|
2
|
+
class Runtime < ExecJS::Runtime
|
3
|
+
class VMCommand
|
4
|
+
def initialize(socket, cmd, arguments)
|
5
|
+
@socket = socket
|
6
|
+
@cmd = cmd
|
7
|
+
@arguments = arguments
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
result = ''
|
12
|
+
message = ::Oj.dump({ 'cmd' => @cmd, 'args' => @arguments }, mode: :strict)
|
13
|
+
message = message + "\x04"
|
14
|
+
bytes_to_send = message.bytesize
|
15
|
+
sent_bytes = 0
|
16
|
+
|
17
|
+
if ::ExecJS.windows?
|
18
|
+
@socket.write(message)
|
19
|
+
begin
|
20
|
+
result << @socket.read
|
21
|
+
end until result.end_with?("\x04")
|
22
|
+
else
|
23
|
+
sent_bytes = @socket.sendmsg(message)
|
24
|
+
if sent_bytes < bytes_to_send
|
25
|
+
while sent_bytes < bytes_to_send
|
26
|
+
sent_bytes += @socket.sendmsg(message.byteslice((sent_bytes)..-1))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
result << @socket.recvmsg()[0]
|
32
|
+
end until result.end_with?("\x04")
|
33
|
+
end
|
34
|
+
::Oj.load(result.chop!, mode: :strict)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Speednode
|
2
|
+
class Runtime < ExecJS::Runtime
|
3
|
+
def self.attach_proc(context_id, func, run_block)
|
4
|
+
attached_procs[context_id] = { func => run_block }
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.attached_procs
|
8
|
+
@attached_procs ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.responders
|
12
|
+
@responders ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :name, :vm
|
16
|
+
|
17
|
+
def initialize(options)
|
18
|
+
@name = options[:name]
|
19
|
+
@binary = ::Speednode::NodeCommand.cached(options[:command])
|
20
|
+
@runner_path = options[:runner_path]
|
21
|
+
@vm = VM.new(binary: @binary, source_maps: '--enable-source-maps', runner_path: @runner_path)
|
22
|
+
@encoding = options[:encoding]
|
23
|
+
@deprecated = !!options[:deprecated]
|
24
|
+
@popen_options = {}
|
25
|
+
@popen_options[:external_encoding] = @encoding if @encoding
|
26
|
+
@popen_options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8'
|
27
|
+
@contexts = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def register_context(uuid, context)
|
31
|
+
@contexts[uuid] = context
|
32
|
+
end
|
33
|
+
|
34
|
+
def unregister_context(uuid)
|
35
|
+
context = @contexts.delete(uuid)
|
36
|
+
if context && @vm
|
37
|
+
ObjectSpace.undefine_finalizer(context)
|
38
|
+
@vm.delete_context(uuid) rescue nil # if delete_context fails, the vm exited before probably
|
39
|
+
end
|
40
|
+
@vm.stop if @contexts.size == 0 && @vm.started?
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop_context(context)
|
44
|
+
unregister_context(context.instance_variable_get(:@uuid))
|
45
|
+
end
|
46
|
+
|
47
|
+
def available?
|
48
|
+
@binary ? true : false
|
49
|
+
end
|
50
|
+
|
51
|
+
def deprecated?
|
52
|
+
@deprecated
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/speednode.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'socket'
|
3
|
+
require 'oj'
|
4
|
+
require 'execjs/module'
|
5
|
+
require 'speednode/config'
|
6
|
+
require 'speednode/node_command'
|
7
|
+
require 'execjs/runtime'
|
8
|
+
if Gem.win_platform?
|
9
|
+
require 'speednode/attach_pipe'
|
10
|
+
else
|
11
|
+
require 'speednode/attach_socket'
|
12
|
+
end
|
13
|
+
require 'speednode/runtime/vm'
|
14
|
+
require 'speednode/runtime/vm_command'
|
15
|
+
require 'speednode/runtime/context'
|
16
|
+
require 'speednode/runtime'
|
17
|
+
require 'execjs/runtimes'
|
18
|
+
require 'speednode/execjs_runtime'
|
19
|
+
require 'speednode/execjs_module'
|
20
|
+
require 'speednode/execjs_runtimes'
|
21
|
+
require 'execjs'
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: speednode
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jan Biedermann
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: execjs
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.9.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.9.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: oj
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.13.23
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 3.17.0
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.13.23
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.17.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: win32-pipe
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.4.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.4.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: minitest
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 5.20.0
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 5.20.0
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 13.1.0
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 13.1.0
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: uglifier
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
description: A fast ExecJS runtime based on nodejs. As fast as mini_racer, but without
|
118
|
+
the need to compile the js engine, because it uses the system provided nodejs.
|
119
|
+
email: jan@kursator.de
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- LICENSE
|
125
|
+
- README.md
|
126
|
+
- lib/speednode.rb
|
127
|
+
- lib/speednode/attach_pipe.rb
|
128
|
+
- lib/speednode/attach_socket.rb
|
129
|
+
- lib/speednode/config.rb
|
130
|
+
- lib/speednode/execjs_module.rb
|
131
|
+
- lib/speednode/execjs_runtime.rb
|
132
|
+
- lib/speednode/execjs_runtimes.rb
|
133
|
+
- lib/speednode/node_command.rb
|
134
|
+
- lib/speednode/runner.js
|
135
|
+
- lib/speednode/runtime.rb
|
136
|
+
- lib/speednode/runtime/context.rb
|
137
|
+
- lib/speednode/runtime/vm.rb
|
138
|
+
- lib/speednode/runtime/vm_command.rb
|
139
|
+
- lib/speednode/version.rb
|
140
|
+
homepage: https://github.com/janbiedermann/speednode
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 3.1.0
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubygems_version: 3.5.3
|
160
|
+
signing_key:
|
161
|
+
specification_version: 4
|
162
|
+
summary: A fast ExecJS runtime based on nodejs.
|
163
|
+
test_files: []
|