virtual_module 0.1.0 → 0.2.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 +4 -4
- data/README.md +94 -11
- data/lib/virtual_module/bridge.jl +3 -0
- data/lib/virtual_module/version.rb +1 -1
- data/lib/virtual_module.rb +203 -144
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd5f0e87cb8b078374f942887d00b5d32e3f2245
|
4
|
+
data.tar.gz: f350813eb2781d0158ac0d1a931c8a9bd0643ccf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a05517f26f04375b0241986354bd9008960f1d57dc3146580165b17d608a4d4e47056605993b40d94f856d07729c7a4e9547fc1fce9a21214c578fa53fa6fe69
|
7
|
+
data.tar.gz: 7bdf2a597339e02a8fc40a919b79edbddd4de11aabe7a997979e07cb4e280f69632649b82ef1cd5009869a7cd252009683dbafb264a027d7abb01604bf8f86a5
|
data/README.md
CHANGED
@@ -1,28 +1,111 @@
|
|
1
|
-
|
1
|
+
# VirtualModule
|
2
2
|
|
3
|
-
If you
|
3
|
+
If you have ever felt at all disappointed seeing Ruby's computational performance particularly for large-scale scientific computation, VirtualModule may save you a little. It offers you a way to run your arbitrary Ruby code on the other language VM process (currently only Julia is supported). What make VirtualModule possible to do this is [ruby2julia transpiler](https://github.com/remore/julializer) (which is at very early stage of development) and IPC using [msgpack](http://msgpack.org/).
|
4
4
|
|
5
|
-
|
5
|
+
Let's take a look at results of [benchmark program](https://github.com/remore/virtual_module/blob/master/doc/benchmark_loop_performance/add_two_floating_point_numbers_cumulatively.rb) of which scores were taken on my MacBook Pro (OSX10.11, Core i5 2.9GHz, 8GB). Essentially this benchmarking program just repeats adding an integer value to floating point value cumulatively, which is represented as `x=2.5; 1.upto(N){|i| x=x+i}` in Ruby. If you run this program with setting of smaller number of loops (upto 10,000,000 times), VirtualModule doesn't make sense at all as you can see:
|
6
|
+
|
7
|
+
")
|
8
|
+
|
9
|
+
However as the number of loops gets bigger(upto 1,000,000,000 times), VirtualModule significantly reduces total execution time.
|
10
|
+
|
11
|
+
")
|
12
|
+
|
13
|
+
(For the sake of honor of python and cython, here is [another benchmarking results](https://raw.githubusercontent.com/remore/virtual_module/master/doc/assets/benchmark-result-from-1e1-to-1e9-with-ubuntu.png) measured on Ubuntu 16 on DigitalOcean (8CPUs, 16GB Mem). It records better score for both python2.7 and Cython than the one with my MacBook Pro, although I'm not sure why this happens. Does anyone can guess why this happens?)
|
14
|
+
|
15
|
+
#### An experiment with word2vec
|
16
|
+
|
17
|
+
The other example here is a [prototype word2vec implementation in Ruby](https://github.com/remore/virtual_module/blob/master/example/word2vec.rb), which is just partially ported (CBOW model only) from original C implementation ([the one created by Tomas Mikolov at Google](https://code.google.com/archive/p/word2vec/)). Since it's not optimized very well yet due to my limited time to work on this example, it doesn't record decent score in terms of speed yet, but it reveals at least the fact that VirtualModule will reduces total execution time considerably even in real world example.
|
18
|
+
|
19
|
+
")
|
20
|
+
|
21
|
+
(A few record values resulted in 0 because it took too much time, actually more than three days to complete due to exponential explosion happened.)
|
22
|
+
|
23
|
+
Of course the vector binary file format generated through VirtualModule is compatible with original C implementation. Here is [sample generated vector files](https://github.com/remore/remore.github.com/tree/master/virtual_module/sample_output) saved onto GitHUb, and command logs are as shown below:
|
6
24
|
|
7
25
|
```
|
26
|
+
$ cd example
|
27
|
+
$ ruby word2vec.rb --output /tmp/vectors.bin --train ../doc/benchmark_word2vec/training_data/10mb.txt --size 20 --window 10 --negative 5 --sample 1e-4 --binary 1 --iter 3 --debug 0 > /dev/null 2>&1
|
28
|
+
$ python
|
29
|
+
Python 2.7.12 (default, Jul 1 2016, 15:12:24)
|
30
|
+
[GCC 5.4.0 20160609] on linux2
|
31
|
+
Type "help", "copyright", "credits" or "license" for more information.
|
32
|
+
>>> import gensim
|
33
|
+
>>> model = gensim.models.Word2Vec.load_word2vec_format('/tmp/vectors.bin', binary=True)
|
34
|
+
>>> model.most_similar("japan")
|
35
|
+
[(u'netherlands', 0.9741939902305603), (u'china', 0.9712631702423096), (u'county', 0.9686408042907715), (u'spaniards', 0.9669440388679504), (u'vienna', 0.9614173769950867), (u'abu', 0.9587018489837646), (u'korea', 0.9565504789352417), (u'canberra', 0.954473614692688), (u'erupts', 0.9540712833404541), (u'prefecture', 0.9534248113632202)]
|
36
|
+
```
|
37
|
+
|
38
|
+
## Usage and How It Works
|
39
|
+
|
40
|
+
To write Ruby code using VirtualModule is something like to write Python code using Cython. Basic concept is to separate large-scale computation algorithm, and isolate them as a module accessible from the base program. Like Cython, VirtualModule is NOT comprehensive approach too but gives you an opportunity to reduce execution time in exchange for [the hard limitation of Ruby syntax due to ruby2julia transipiler](https://github.com/remore/julializer#supported-classes-and-syntax).
|
41
|
+
|
42
|
+
```
|
43
|
+
require 'virtual_module'
|
8
44
|
vm = VirtualModule.new(<<EOS)
|
9
45
|
def hi
|
10
|
-
"
|
46
|
+
"yo"
|
11
47
|
end
|
12
48
|
|
13
|
-
|
14
|
-
|
15
|
-
|
49
|
+
def init_table(list)
|
50
|
+
for i in 0..list.size-1
|
51
|
+
list[i]+=Random.rand
|
52
|
+
end
|
53
|
+
list
|
16
54
|
end
|
17
55
|
EOS
|
18
|
-
p vm.hi #"
|
56
|
+
p vm.hi # "yo"
|
19
57
|
include vm
|
20
|
-
p
|
58
|
+
p init_table([1,20]) # [1.3066601775641218, 20.17001189249985]
|
21
59
|
```
|
22
60
|
|
23
|
-
|
61
|
+
As this sample snippet shows, VirtualModule is a Module generator which has only one public api named `VirtualModule#new`. Since `VirtualModule#new` returns a instance of Module class, user can simply include `vm` object that's why `#init_table` method is called in the context of `self`.
|
62
|
+
|
63
|
+
In detail, when you run VirtualModule, following processing are happening internally. It's just a typical procedures of an RPC call.
|
24
64
|
|
25
|
-
|
65
|
+
1. VirtualModule calls `Julializer#ruby2julia` to transpile `#hi` and `#init_table` methods into raw Julia code
|
66
|
+
2. Prepare glue code to send and receive parameters from/to Julia and add this to (1)
|
67
|
+
3. Run (2) through Julia VM process and pass the parameters using msgpack-ruby and MsgPack.jl
|
68
|
+
4. Receive returned value passed from Julia
|
69
|
+
|
70
|
+
What makes VirtualModule different from normal RPC call is `#virtual_module_eval` method. It offers a convenient way to eval arbitary code as if it's executed on the context of `self`. Most important note here is by this way, you can even pass type information about arguments like below.
|
71
|
+
|
72
|
+
```
|
73
|
+
# An example to pass Array{Float64,1} type array to Julia
|
74
|
+
class FloatArray < Array
|
75
|
+
end
|
76
|
+
float_list = FloatArray.new([1.0234, 9.9999])
|
77
|
+
vm.virtual_module_eval("float_list = init_table(float_list)")
|
78
|
+
puts float_list # [1.5765191145261321, 10.270808112990153] is calculated as Array{Float64,1} in Julia, which means much faster computation than Array{Any,1}!
|
79
|
+
```
|
80
|
+
|
81
|
+
## Requirement
|
82
|
+
|
83
|
+
- Mac OSX, Linux and Ruby (MRI) 2.1 and higher are supported.
|
84
|
+
- Either Julia or Docker installed on your machine is a must, although installing Julia is highly recommended for the performance reason.
|
85
|
+
|
86
|
+
## Installation
|
87
|
+
|
88
|
+
Simply run the following command.
|
89
|
+
|
90
|
+
```
|
91
|
+
$ gem install virtual_module
|
92
|
+
$ julia -e 'Pkg.add("MsgPack"); Pkg.add("MsgPackRpcServer")'
|
93
|
+
```
|
94
|
+
|
95
|
+
## Features
|
96
|
+
|
97
|
+
Following features are implemented already:
|
98
|
+
|
99
|
+
- File-based IPC using [msgpack-ruby](https://github.com/msgpack/msgpack-ruby) and [MsgPack.jl](https://github.com/kmsquire/MsgPack.jl/)
|
100
|
+
- RPC-based IPC using [msgpack-ruby](https://github.com/msgpack/msgpack-ruby) and [MsgPackRpcServer.jl](https://github.com/remore/MsgPackRpcServer.jl)
|
101
|
+
|
102
|
+
features to be implemented in the future (if needed/requested) are listed as follows:
|
103
|
+
|
104
|
+
- A bug fix for `#virtual_module_eval`: return value has a bug whenever argument has more than two lines.
|
105
|
+
- Improve julializer (transpiler) much better
|
106
|
+
- word2vec implementation is another interesting option to improve
|
107
|
+
- Other programming language support such as golang, ruby itself etc.
|
26
108
|
|
27
109
|
## License
|
110
|
+
|
28
111
|
MIT
|
data/lib/virtual_module.rb
CHANGED
@@ -10,41 +10,58 @@ module VirtualModule
|
|
10
10
|
require 'virtual_module/version'
|
11
11
|
|
12
12
|
class << self
|
13
|
-
def new(methods,
|
14
|
-
|
15
|
-
|
16
|
-
vm_builder = instance_eval("#{config[:lang].to_s.capitalize}Builder").new(config)
|
17
|
-
else
|
18
|
-
vm_builder = config
|
19
|
-
end
|
13
|
+
def new(methods, **args)
|
14
|
+
option = {:lang=>:julia, :pkgs=>[], :ipc=>:file}.merge(args)
|
15
|
+
vm_builder = Builder.new(option[:lang], option[:pkgs], option[:ipc])
|
20
16
|
vm_builder.add(methods)
|
21
17
|
vm_builder.build
|
22
18
|
end
|
23
19
|
end
|
24
20
|
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
module SexpParser
|
22
|
+
def extract_defs(s)
|
23
|
+
if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
|
24
|
+
if [:def].include?(s[0])
|
25
|
+
"#{s[1][1]},"
|
26
|
+
else
|
27
|
+
s.map{|e| extract_defs(e)}.join
|
32
28
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
30
|
+
s.map{|e| extract_defs(e)}.join
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_args(s)
|
35
|
+
if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
|
36
|
+
if [:vcall, :var_field].include?(s[0])
|
37
|
+
"#{s[1][1]},"
|
38
|
+
else
|
39
|
+
s.map{|e| extract_args(e)}.join
|
40
|
+
end
|
41
|
+
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
42
|
+
s.map{|e| extract_args(e)}.join
|
36
43
|
end
|
37
44
|
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Builder
|
48
|
+
include SexpParser
|
38
49
|
|
39
|
-
def
|
40
|
-
@
|
41
|
-
@ipc.
|
50
|
+
def initialize(lang=:julia, pkgs=[], ipc=:file)
|
51
|
+
@provider = instance_eval("#{lang.capitalize}SourceProvider").new(pkgs)
|
52
|
+
@ipc = instance_eval("#{ipc.to_s.capitalize}IpcInterface").new(@provider)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add(methods="")
|
56
|
+
@provider.source << methods
|
57
|
+
@provider.compile
|
58
|
+
@ipc.reset @provider
|
42
59
|
end
|
43
60
|
|
44
61
|
def build
|
45
62
|
vm_builder = self
|
46
63
|
ipc = @ipc
|
47
|
-
vm = Module.new{
|
64
|
+
@vm = Module.new{
|
48
65
|
@vm_builder = vm_builder
|
49
66
|
@ipc = ipc
|
50
67
|
def self.virtual_module_eval(*args)
|
@@ -53,56 +70,50 @@ module VirtualModule
|
|
53
70
|
def self.method_missing(key, *args, &block)
|
54
71
|
@vm_builder.send(:call, key, *args)
|
55
72
|
end
|
73
|
+
def self.___get_serialized___
|
74
|
+
@ipc.serialized
|
75
|
+
end
|
56
76
|
}
|
57
|
-
extract_defs(Ripper.sexp(@source.join(";"))).split(",").each{|e|
|
58
|
-
vm.class_eval {
|
77
|
+
extract_defs(Ripper.sexp(@provider.source.join(";"))).split(",").each{|e|
|
78
|
+
@vm.class_eval {
|
59
79
|
define_method e.to_sym, Proc.new { |*args|
|
60
80
|
vm_builder.call(e.to_sym, *args)
|
61
81
|
}
|
62
82
|
}
|
63
83
|
}
|
64
|
-
vm
|
84
|
+
@vm
|
65
85
|
end
|
66
86
|
|
67
87
|
def call(name, *args)
|
68
|
-
|
69
|
-
@work_dir = @ipc.work_dir = tempdir
|
88
|
+
begin
|
70
89
|
@ipc.call(name, *args)
|
90
|
+
rescue StandardError => e
|
91
|
+
@ipc.serialized = e.message
|
92
|
+
@vm
|
71
93
|
end
|
72
94
|
end
|
73
95
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
83
|
-
s.map{|e| extract_defs(e)}.join
|
84
|
-
end
|
96
|
+
def virtual_module_eval(script, auto_binding=true)
|
97
|
+
vars, type_info, params = inspect_local_vars(binding.of_caller(2), script)
|
98
|
+
@provider.compile(vars, type_info, params, script, auto_binding)
|
99
|
+
@ipc.reset @provider
|
100
|
+
evaluated = self.call(:___main, params, type_info)
|
101
|
+
if auto_binding
|
102
|
+
binding.of_caller(2).eval(evaluated[1].map{|k,v| "#{k}=#{v};" if !v.nil? }.join)
|
85
103
|
end
|
86
104
|
|
87
|
-
|
88
|
-
|
89
|
-
if [:vcall, :var_field].include?(s[0])
|
90
|
-
"#{s[1][1]},"
|
91
|
-
else
|
92
|
-
s.map{|e| extract_args(e)}.join
|
93
|
-
end
|
94
|
-
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
95
|
-
s.map{|e| extract_args(e)}.join
|
96
|
-
end
|
97
|
-
end
|
105
|
+
return evaluated[0]
|
106
|
+
end
|
98
107
|
|
108
|
+
alias_method :virtual_instance_eval, :virtual_module_eval
|
109
|
+
alias_method :virtual_eval, :virtual_module_eval
|
110
|
+
|
111
|
+
private
|
99
112
|
def inspect_local_vars(context, script)
|
100
113
|
vars = extract_args(Ripper.sexp(script)).split(",").uniq.map{|e| e.to_sym} & context.eval("local_variables")
|
101
|
-
#p "vars=#{vars}"
|
102
114
|
|
103
115
|
type_info = {}
|
104
116
|
type_info[:params] = context.eval("Hash[ *#{vars}.collect { |e| [ e, eval(e.to_s).class.to_s ] }.flatten ]").select{|k,v| ["FloatArray", "IntArray"].include?(v)}
|
105
|
-
#p "type_info=#{type_info}"
|
106
117
|
|
107
118
|
params = context.eval(<<EOS)
|
108
119
|
require 'msgpack'
|
@@ -110,16 +121,67 @@ ___params = {}
|
|
110
121
|
___params = local_variables.map{|e| [e, eval(e.to_s)]}.each{|e| ___params[e[0]]=e[1] if #{vars}.include?(e[0])}[0][1]
|
111
122
|
___params
|
112
123
|
EOS
|
113
|
-
#File.write("#{@work_dir}.polykit.params", params)
|
114
124
|
[vars, type_info, params]
|
115
125
|
end
|
116
126
|
end
|
117
127
|
|
128
|
+
class BaseSourceProvider
|
129
|
+
attr_accessor :source
|
130
|
+
def initialize(pkgs)
|
131
|
+
@source = load_packages(pkgs)
|
132
|
+
@compiled_lib = ""
|
133
|
+
end
|
118
134
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
135
|
+
def load_packages(pkgs)
|
136
|
+
[] #to be overrieded
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class JuliaSourceProvider < BaseSourceProvider
|
141
|
+
def load_packages(pkgs)
|
142
|
+
pkgs.map{|e| "import #{e}"}
|
143
|
+
end
|
144
|
+
|
145
|
+
def main_loop(input_queue_path, output_queue_path)
|
146
|
+
<<EOS
|
147
|
+
using MsgPack
|
148
|
+
while true
|
149
|
+
source = open( "#{input_queue_path}", "r" ) do fp
|
150
|
+
readall(fp)
|
151
|
+
end
|
152
|
+
result = eval(parse(source))
|
153
|
+
open( "#{output_queue_path}", "w" ) do fp
|
154
|
+
try
|
155
|
+
write(fp,pack(result))
|
156
|
+
catch
|
157
|
+
serialize(fp,result)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
EOS
|
162
|
+
end
|
163
|
+
|
164
|
+
def lib_script(ipc=nil)
|
165
|
+
if ipc!=:rpc
|
166
|
+
@compiled_lib
|
167
|
+
else
|
168
|
+
<<EOS
|
169
|
+
import MsgPackRpcServer
|
170
|
+
module RemoteFunctions
|
171
|
+
#{@compiled_lib}
|
172
|
+
end
|
173
|
+
MsgPackRpcServer.run(parse(ARGS[1]), RemoteFunctions)
|
174
|
+
EOS
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def compile(vars=nil, type_info=nil, params=nil, script=nil, auto_binding=nil)
|
179
|
+
@compiled_lib = File.read(
|
180
|
+
File.dirname(__FILE__)+"/virtual_module/bridge.jl") + ";" +
|
181
|
+
Julializer.ruby2julia(@source.join(";\n")
|
182
|
+
)
|
183
|
+
if !vars.nil? && !type_info.nil? && !params.nil? && !script.nil? && !auto_binding.nil?
|
184
|
+
@compiled_lib += <<EOS
|
123
185
|
function ___convert_type(name, typename, params)
|
124
186
|
if length(findin(#{type_info[:params].keys.map{|e| e.to_s}},[name]))>0
|
125
187
|
if typename=="FloatArray"
|
@@ -135,125 +197,124 @@ EOS
|
|
135
197
|
function ___main(params, type_info)
|
136
198
|
#{vars.map{|e| e.to_s + '=___convert_type("'+e.to_s+'","'+(type_info[:params][e]||"")+'", params)'}.join(";")}
|
137
199
|
##{vars.map{|e| 'println("'+e.to_s+'=", typeof('+e.to_s+'))' }.join(";")}
|
138
|
-
___evaluated = #{Julializer.ruby2julia(script)}
|
200
|
+
___evaluated = (#{Julializer.ruby2julia(script)})
|
139
201
|
|
140
202
|
#{vars.map{|e| 'params["'+e.to_s+'"]='+e.to_s }.join(";") if auto_binding}
|
141
203
|
|
142
204
|
(___evaluated,#{auto_binding ? "params" : "-1" })
|
143
205
|
end
|
144
206
|
EOS
|
145
|
-
|
146
|
-
# File.write("#{@work_dir}.polykit.rb", <<EOS)
|
147
|
-
# require 'msgpack-rpc'
|
148
|
-
# client = MessagePack::RPC::Client.new('127.0.0.1', #{@port})
|
149
|
-
# client.timeout = #{@timeout}
|
150
|
-
# p client.call(:___main, #{params}, #{type_info})
|
151
|
-
#EOS
|
152
|
-
|
153
|
-
@ipc.reset get_compiled_code + ";#{boot_script}"
|
154
|
-
evaluated = self.call(:___main, params, type_info)
|
155
|
-
if auto_binding
|
156
|
-
binding.of_caller(2).eval(evaluated[1].map{|k,v| "#{k}=#{v};" if !v.nil? }.join)
|
157
207
|
end
|
158
|
-
|
159
|
-
return evaluated[0]
|
160
208
|
end
|
161
209
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
File.
|
210
|
+
def generate_message(input_queue_path, name, *args)
|
211
|
+
script = ""
|
212
|
+
params = []
|
213
|
+
args.each_with_index do |arg, i|
|
214
|
+
type = arg.class == Module ? "serialized" : "msgpack"
|
215
|
+
File.write("#{input_queue_path}.#{i}.#{type}", arg.class == Module ? arg.___get_serialized___ : MessagePack.pack(arg))
|
216
|
+
params << "params_#{i}"
|
217
|
+
val = arg.class == Module ? "deserialize(fp)" : "unpack(readall(fp))"
|
218
|
+
script += "#{params.last} =open( \"#{input_queue_path}.#{i}.#{type}\", \"r\" ) do fp; #{val}; end;"
|
168
219
|
end
|
220
|
+
script += "#{name}(#{params.join(',')});"
|
221
|
+
end
|
169
222
|
|
170
223
|
end
|
171
224
|
|
172
225
|
class BaseIpcInterface
|
173
|
-
|
174
|
-
OUTPUT_ARGS = "virtualmodule-output.msgpack"
|
175
|
-
ENTRYPOINT_SCRIPT = "virtualmodule-entrypoint.jl"
|
176
|
-
LIB_SCRIPT = "virtualmodule-lib.jl"
|
226
|
+
LIB_SCRIPT = "vm-lib"
|
177
227
|
|
178
228
|
attr_accessor :work_dir
|
229
|
+
attr_accessor :serialized
|
179
230
|
|
180
|
-
def initialize(
|
181
|
-
|
231
|
+
def initialize(provider)
|
232
|
+
@provider = provider
|
233
|
+
@work_dir = Dir.mktmpdir(nil, Dir.home)
|
182
234
|
end
|
183
235
|
def call(name, *args)
|
184
236
|
#do nothing
|
185
237
|
end
|
186
|
-
def reset(
|
187
|
-
|
238
|
+
def reset(provider)
|
239
|
+
@provider = provider
|
188
240
|
end
|
189
241
|
end
|
190
242
|
|
191
243
|
class FileIpcInterface < BaseIpcInterface
|
244
|
+
INPUT = "vm-input"
|
245
|
+
OUTPUT = "vm-output"
|
246
|
+
MAIN_LOOP = "vm-main"
|
247
|
+
|
248
|
+
def initialize(provider)
|
249
|
+
super
|
250
|
+
File.mkfifo("#{@work_dir}/#{INPUT}")
|
251
|
+
File.mkfifo("#{@work_dir}/#{OUTPUT}")
|
252
|
+
at_exit do
|
253
|
+
Process.kill(:INT, @pid) if !@pid.nil?
|
254
|
+
FileUtils.remove_entry @work_dir if File.directory?(@work_dir)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
192
258
|
def call(name, *args)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
elsif is_installed?(:docker)
|
200
|
-
File.write("#{@work_dir}/#{ENTRYPOINT_SCRIPT}", generate_entrypoint("/opt/", "/opt/#{OUTPUT_ARGS}", name, *args))
|
201
|
-
command = "docker run -v #{@work_dir}/:/opt/ remore/virtual_module julia --depwarn=no -L /opt/virtualmodule-lib.jl /opt/virtualmodule-entrypoint.jl"
|
259
|
+
#require 'byebug'
|
260
|
+
#byebug
|
261
|
+
if Helper.is_installed?(:julia)
|
262
|
+
enqueue @provider.generate_message("#{@work_dir}/#{INPUT}", name, *args)
|
263
|
+
elsif Helper.is_installed?(:docker)
|
264
|
+
enqueue @provider.generate_message("/opt/#{INPUT}", name, *args)
|
202
265
|
else
|
203
266
|
raise Exception.new("Either julia or docker command is required to run virtual_module")
|
204
267
|
end
|
205
|
-
out, err, status = Open3.capture3(command)
|
206
268
|
#byebug
|
207
|
-
|
208
|
-
|
209
|
-
|
269
|
+
response = dequeue
|
270
|
+
begin
|
271
|
+
MessagePack.unpack(response)
|
272
|
+
rescue
|
273
|
+
raise StandardError.new(response)
|
274
|
+
end
|
210
275
|
end
|
211
276
|
|
212
277
|
def reset(source)
|
213
|
-
|
278
|
+
super
|
279
|
+
restart_server_process
|
214
280
|
end
|
215
281
|
|
216
282
|
private
|
217
|
-
def
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
params = open( "#{basedir}/#{INPUT_ARGS}", "r" ) do fp
|
226
|
-
readall( fp )
|
227
|
-
end
|
228
|
-
result = #{name}(unpack(params)...)
|
229
|
-
EOS
|
283
|
+
def restart_server_process
|
284
|
+
Process.kill(:KILL, @pid) if !@pid.nil?
|
285
|
+
File.write("#{@work_dir}/#{LIB_SCRIPT}", @provider.lib_script)
|
286
|
+
File.write("#{@work_dir}/#{MAIN_LOOP}", @provider.main_loop("#{@work_dir}/#{INPUT}", "#{@work_dir}/#{OUTPUT}"))
|
287
|
+
if Helper.is_installed?(:julia)
|
288
|
+
command = "julia --depwarn=no -L #{@work_dir}/#{LIB_SCRIPT} #{@work_dir}/#{MAIN_LOOP}"
|
289
|
+
elsif Helper.is_installed?(:docker)
|
290
|
+
command = "docker run -v #{@work_dir}/:/opt/ remore/virtual_module julia --depwarn=no -L /opt/#{LIB_SCRIPT} /opt/#{MAIN_LOOP}"
|
230
291
|
else
|
231
|
-
|
232
|
-
using MsgPack
|
233
|
-
result = #{name}()
|
234
|
-
EOS
|
292
|
+
raise Exception.new("Either julia or docker command is required to run virtual_module")
|
235
293
|
end
|
294
|
+
@pid = Process.spawn(command, :err => :out,:out => "/dev/null") # , :pgroup => Process.pid)
|
295
|
+
Process.detach @pid
|
296
|
+
end
|
236
297
|
|
237
|
-
|
238
|
-
|
239
|
-
write( fp, pack(result) )
|
240
|
-
end
|
241
|
-
EOS
|
298
|
+
def enqueue(message)
|
299
|
+
File.write("#{@work_dir}/#{INPUT}", message)
|
242
300
|
end
|
243
301
|
|
302
|
+
def dequeue
|
303
|
+
File.open("#{@work_dir}/#{OUTPUT}", 'r'){|f| f.read}
|
304
|
+
end
|
244
305
|
end
|
245
306
|
|
246
307
|
class RpcIpcInterface < BaseIpcInterface
|
247
|
-
def initialize(
|
308
|
+
def initialize(provider)
|
309
|
+
super
|
248
310
|
init_connection
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
end
|
311
|
+
@server = "127.0.0.1"
|
312
|
+
@port = 8746
|
313
|
+
@timeout = 10
|
253
314
|
end
|
254
315
|
|
255
316
|
def call(name, *args)
|
256
|
-
restart_server_process
|
317
|
+
restart_server_process
|
257
318
|
while `echo exit | telnet #{@server} #{@port} 2>&1`.chomp[-5,5]!="host." do
|
258
319
|
sleep(0.05)
|
259
320
|
end
|
@@ -262,36 +323,34 @@ EOS
|
|
262
323
|
args.count>0 ? @client.call(name, *args) : @client.call(name)
|
263
324
|
end
|
264
325
|
|
265
|
-
def reset(source)
|
266
|
-
@lib_source = source
|
267
|
-
end
|
268
|
-
|
269
326
|
private
|
270
327
|
def init_connection
|
271
328
|
@pid = nil
|
272
329
|
@client.close if !@client.nil?
|
273
330
|
@client = nil
|
331
|
+
at_exit do
|
332
|
+
@client.close if !@client.nil?
|
333
|
+
Process.kill(:INT, @pid) if !@pid.nil?
|
334
|
+
FileUtils.remove_entry @work_dir if File.directory?(@work_dir)
|
335
|
+
end
|
274
336
|
end
|
275
337
|
|
276
|
-
def restart_server_process
|
277
|
-
Process.kill(:
|
338
|
+
def restart_server_process
|
339
|
+
Process.kill(:KILL, @pid) if !@pid.nil?
|
278
340
|
`lsof -wni tcp:#{@port} | cut -f 4 -d ' ' | sed -ne '2,$p' | xargs kill -9`
|
279
341
|
init_connection
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
#{source}
|
284
|
-
end
|
285
|
-
MsgPackRpcServer.run(#{@port}, RemoteFunctions)
|
286
|
-
EOS
|
287
|
-
File.write("#{@work_dir}/#{LIB_SCRIPT}", compiled)
|
288
|
-
@pid = Process.spawn("julia --depwarn=no #{@work_dir}/#{LIB_SCRIPT}")
|
342
|
+
File.write("#{@work_dir}/#{LIB_SCRIPT}", @provider.lib_script(:rpc))
|
343
|
+
@pid = Process.spawn("julia --depwarn=no #{@work_dir}/#{LIB_SCRIPT} #{@port}", :err => :out,:out => "/dev/null") #, :pgroup=>Process.pid)
|
344
|
+
Process.detach @pid
|
289
345
|
end
|
346
|
+
end
|
290
347
|
|
291
|
-
|
292
|
-
|
293
|
-
|
348
|
+
module Helper
|
349
|
+
class << self
|
350
|
+
def is_installed?(command)
|
351
|
+
Open3.capture3("which #{command.to_s}")[0].size > 0
|
294
352
|
end
|
353
|
+
end
|
295
354
|
end
|
296
355
|
|
297
356
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtual_module
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kei Sawada(@remore)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|