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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4bfab3d164f02ef899d40766d55420467ece2e23
4
- data.tar.gz: 8f125f80cc967b5e40fa59879a53dcb97bbca308
3
+ metadata.gz: bd5f0e87cb8b078374f942887d00b5d32e3f2245
4
+ data.tar.gz: f350813eb2781d0158ac0d1a931c8a9bd0643ccf
5
5
  SHA512:
6
- metadata.gz: ec550f409511efd94ecaf381a5f5e1e73ba6c1000a0d663f142dc7582d487db8ee2cb04c51c7b709c81dd3b3416f5a1a8c1cd206a740fad4f9787e5d0b80c364
7
- data.tar.gz: 40a0f820f59e823896b9b20b4b1c4c85f1f4c05902b2692e37c2c6821004831298834a4d1712b209cff6afba8c73da08f174d38ba5fbad18fd83f4230c9b8532
6
+ metadata.gz: a05517f26f04375b0241986354bd9008960f1d57dc3146580165b17d608a4d4e47056605993b40d94f856d07729c7a4e9547fc1fce9a21214c578fa53fa6fe69
7
+ data.tar.gz: 7bdf2a597339e02a8fc40a919b79edbddd4de11aabe7a997979e07cb4e280f69632649b82ef1cd5009869a7cd252009683dbafb264a027d7abb01604bf8f86a5
data/README.md CHANGED
@@ -1,28 +1,111 @@
1
- ## VirtualModule
1
+ # VirtualModule
2
2
 
3
- If you feel good with Ruby's language design but *totally* disappointed with Ruby's computational performance such as terribly slow speed of loop or Math functions, then VirtualModule may save you a little.
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
- With VirtualModule, you can run your Ruby code on the other language VM process(currently only Julia is supported).
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
+ ![Benchmarking results](https://raw.githubusercontent.com/remore/virtual_module/master/doc/assets/benchmark-result-from-1e1-to-1e7.png "Graphs of average execution time vs number of loops(MacBook Pro)")
8
+
9
+ However as the number of loops gets bigger(upto 1,000,000,000 times), VirtualModule significantly reduces total execution time.
10
+
11
+ ![Benchmarking results](https://raw.githubusercontent.com/remore/virtual_module/master/doc/assets/benchmark-result-from-1e1-to-1e9.png "Graphs of average execution time vs number of loops(MacBook Pro)")
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
+ ![Benchmarking results](https://raw.githubusercontent.com/remore/virtual_module/master/doc/assets/benchmark-result-of-word2vec-performance.png "Graphs of average execution time vs filesize of training data(Ubuntu16)")
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
- "ho"
46
+ "yo"
11
47
  end
12
48
 
13
- # @param [Fixnum] num
14
- def hello(num)
15
- num*num
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 #"ho"
56
+ p vm.hi # "yo"
19
57
  include vm
20
- p hello(33) #1089
58
+ p init_table([1,20]) # [1.3066601775641218, 20.17001189249985]
21
59
  ```
22
60
 
23
- ## Other explanation
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
- TBC
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
@@ -1,3 +1,6 @@
1
+ import Base.+
2
+ import Base.*
3
+
1
4
  function +(x::ASCIIString, y::ASCIIString)
2
5
  string(x, y)
3
6
  end
@@ -1,3 +1,3 @@
1
1
  module VirtualModule
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -10,41 +10,58 @@ module VirtualModule
10
10
  require 'virtual_module/version'
11
11
 
12
12
  class << self
13
- def new(methods, config={})
14
- if !config.is_a?(BaseBuilder)
15
- config[:lang] = :julia if config[:lang].nil?
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
- class BaseBuilder
26
- def initialize(config={})
27
- @source = []
28
- if !config.is_a?(BaseIpcInterface)
29
- props = {:ipc=>:file, :work_dir=>nil}
30
- props.each do |k,v|
31
- instance_variable_set("@#{k}", config[k] || v)
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
- @ipc = instance_eval("#{@ipc.to_s.capitalize}IpcInterface").new(props.merge(config))
34
- else
35
- @ipc = config
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 add(methods)
40
- @source << methods
41
- @ipc.reset get_compiled_code
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
- Dir.mktmpdir(nil, Dir.home) do |tempdir|
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
- private
75
- def extract_defs(s)
76
- if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
77
- if [:def].include?(s[0])
78
- "#{s[1][1]},"
79
- else
80
- s.map{|e| extract_defs(e)}.join
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
- def extract_args(s)
88
- if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
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
- class JuliaBuilder < BaseBuilder
120
- def virtual_module_eval(script, auto_binding=true)
121
- vars, type_info, params = inspect_local_vars(binding.of_caller(2), script)
122
- boot_script = <<EOS
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
- alias_method :virtual_instance_eval, :virtual_module_eval
163
- alias_method :virtual_eval, :virtual_module_eval
164
-
165
- private
166
- def get_compiled_code
167
- File.read(File.dirname(__FILE__)+"/virtual_module/bridge.jl") + ";" + Julializer.ruby2julia(@source.join(";\n"))
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
- INPUT_ARGS = "virtualmodule-input.msgpack"
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(config)
181
- #do nothing
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(source)
187
- #do nothing
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
- File.write("#{@work_dir}/#{LIB_SCRIPT}", @lib_source)
194
- File.write("#{@work_dir}/#{INPUT_ARGS}", MessagePack.pack(args))
195
-
196
- if is_installed?(:julia)
197
- File.write("#{@work_dir}/#{ENTRYPOINT_SCRIPT}", generate_entrypoint(@work_dir, "#{@work_dir}/#{OUTPUT_ARGS}", name, *args))
198
- command = "julia --depwarn=no -L #{@work_dir}/#{LIB_SCRIPT} #{@work_dir}/#{ENTRYPOINT_SCRIPT}"
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
- # FIXME: Only For Debug
208
- # printf out, err
209
- MessagePack.unpack(File.read("#{@work_dir}/#{OUTPUT_ARGS}"))
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
- @lib_source = source
278
+ super
279
+ restart_server_process
214
280
  end
215
281
 
216
282
  private
217
- def is_installed?(command)
218
- Open3.capture3("which #{command.to_s}")[0].size > 0
219
- end
220
-
221
- def generate_entrypoint(basedir, target, name, *args)
222
- if args.count>0
223
- script =<<EOS
224
- using MsgPack
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
- script =<<EOS
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
- script + ";" + <<EOS
238
- open( "#{target}", "w" ) do fp
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(config={})
308
+ def initialize(provider)
309
+ super
248
310
  init_connection
249
- props = {:server=>"127.0.0.1", :port=>8746, :timeout=>10}
250
- props.each do |k,v|
251
- instance_variable_set("@#{k}", config[k] || v)
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 @lib_source
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(source)
277
- Process.kill(:TERM, @pid) if !@pid.nil?
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
- compiled = <<EOS
281
- import MsgPackRpcServer
282
- module RemoteFunctions
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
- at_exit do
292
- @client.close if !@client.nil?
293
- Process.kill(:TERM, @pid) if !@pid.nil?
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.0
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-08-24 00:00:00.000000000 Z
11
+ date: 2016-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack