ruby_process 0.0.9 → 0.0.13
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/Gemfile +3 -0
- data/README.md +98 -0
- data/Rakefile +2 -2
- data/RubyProcess.gemspec +88 -0
- data/VERSION +1 -1
- data/cmds/blocks.rb +18 -18
- data/cmds/marshal.rb +2 -2
- data/cmds/new.rb +13 -13
- data/cmds/numeric.rb +4 -4
- data/cmds/static.rb +10 -10
- data/cmds/str_eval.rb +4 -4
- data/cmds/system.rb +11 -11
- data/examples/example_csv.rb +13 -0
- data/examples/example_file_write.rb +2 -2
- data/examples/example_knj_db_dump.rb +7 -7
- data/examples/example_strscan.rb +3 -3
- data/include/args_handeling.rb +21 -21
- data/lib/ruby_process.rb +111 -105
- data/lib/{ruby_process_cproxy.rb → ruby_process/class_proxy.rb} +35 -35
- data/lib/{ruby_process_proxyobj.rb → ruby_process/proxy_object.rb} +18 -18
- data/ruby_process.gemspec +17 -10
- data/scripts/ruby_process_script.rb +7 -7
- data/shippable.yml +7 -0
- data/spec/class_proxy_spec.rb +95 -0
- data/spec/hard_load_spec.rb +13 -17
- data/spec/leaks_spec.rb +7 -7
- data/spec/ruby_process_spec.rb +75 -77
- data/spec/spec_helper.rb +3 -1
- metadata +60 -60
- data/README.rdoc +0 -19
- data/spec/cproxy_spec.rb +0 -101
data/cmds/str_eval.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
class
|
1
|
+
class RubyProcess
|
2
2
|
#Evalulates the given string in the process.
|
3
3
|
#===Examples
|
4
4
|
# rp.str_eval("return 10").__rp_marshal #=> 10
|
5
5
|
def str_eval(str)
|
6
|
-
send(:
|
6
|
+
send(cmd: :str_eval, str: str)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
#Process-method for 'str_eval'.
|
10
10
|
def cmd_str_eval(obj)
|
11
11
|
#Lamda is used here because 'return' might be used in evalled code and thereby return an unhandeled object.
|
@@ -13,4 +13,4 @@ class Ruby_process
|
|
13
13
|
eval(obj[:str])
|
14
14
|
}.call)
|
15
15
|
end
|
16
|
-
end
|
16
|
+
end
|
data/cmds/system.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
class
|
1
|
+
class RubyProcess
|
2
2
|
#Closes the process by executing exit.
|
3
3
|
def cmd_exit(obj)
|
4
4
|
exit
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
#Flushes various collected object-IDs to the subprocess, where they will be garbage-collected.
|
8
8
|
def flush_finalized
|
9
9
|
@flush_mutex.synchronize do
|
@@ -11,37 +11,37 @@ class Ruby_process
|
|
11
11
|
ids = @proxy_objs_unsets.shift(500)
|
12
12
|
debug "IDS: #{ids} #{@proxy_objs_unsets}\n" if @debug
|
13
13
|
return nil if ids.empty?
|
14
|
-
|
14
|
+
|
15
15
|
debug "Ruby-process-debug: Finalizing (#{ids}).\n" if @debug
|
16
|
-
send(:
|
16
|
+
send(cmd: :flush_finalized, ids: ids)
|
17
17
|
@finalize_count += ids.length
|
18
18
|
return nil
|
19
19
|
end
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
#Flushes references to the given object IDs.
|
23
23
|
def cmd_flush_finalized(obj)
|
24
24
|
debug "Command-flushing finalized: '#{obj[:ids]}'.\n" if @debug
|
25
25
|
obj[:ids].each do |id|
|
26
|
-
raise "Unknown ID: '#{id}' (#{id.class.name})."
|
26
|
+
raise "Unknown ID: '#{id}' (#{id.class.name})." unless @objects.key?(id)
|
27
27
|
@objects.delete(id)
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
return nil
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
#Starts garbage-collecting and then flushes the finalized objects to the sub-process. Does the same thing in the sub-process.
|
34
34
|
def garbage_collect
|
35
35
|
GC.start
|
36
36
|
self.flush_finalized
|
37
|
-
send(:
|
37
|
+
send(cmd: :garbage_collect)
|
38
38
|
return nil
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
#The sub-process-side execution of 'garbage_collect'.
|
42
42
|
def cmd_garbage_collect(obj)
|
43
43
|
GC.start
|
44
44
|
self.flush_finalized
|
45
45
|
return nil
|
46
46
|
end
|
47
|
-
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "ruby_process"
|
5
|
+
|
6
|
+
RubyProcess.new.spawn_process do |rp|
|
7
|
+
rp.static(:Object, :require, "csv")
|
8
|
+
|
9
|
+
rp.static(:CSV, :open, "test.csv", "w") do |csv|
|
10
|
+
csv << ["ID", "Name"]
|
11
|
+
csv << [1, "Kasper"]
|
12
|
+
end
|
13
|
+
end
|
@@ -4,7 +4,7 @@ require "rubygems"
|
|
4
4
|
require "ruby_process"
|
5
5
|
|
6
6
|
fpath = "/tmp/somefile"
|
7
|
-
|
7
|
+
RubyProcess.new.spawn_process do |rp|
|
8
8
|
#Opens file in subprocess.
|
9
9
|
rp.static(:File, :open, fpath, "w") do |fp|
|
10
10
|
#Writes to file in subprocess.
|
@@ -12,4 +12,4 @@ Ruby_process.new.spawn_process do |rp|
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
print "Content of '#{fpath}': #{File.read(fpath)}\n"
|
15
|
+
print "Content of '#{fpath}': #{File.read(fpath)}\n"
|
@@ -23,17 +23,17 @@ threads = []
|
|
23
23
|
threads << Thread.new do
|
24
24
|
begin
|
25
25
|
thread_tables = tables.shift(tables_per_thread)
|
26
|
-
|
27
|
-
|
26
|
+
|
27
|
+
RubyProcess.new(debug: true).spawn_process do |rp|
|
28
28
|
rp.static(:Object, :require, "rubygems")
|
29
29
|
rp.static(:Object, :require, "knjrbfw")
|
30
|
-
|
30
|
+
|
31
31
|
fpath = "/tmp/dbdump_#{i}.sql"
|
32
|
-
|
32
|
+
|
33
33
|
thread_tables.each do |thread_db|
|
34
34
|
rp_db = rp.new("Knj::Db", $db_settings)
|
35
|
-
rp_dump = rp.new("Knj::Db::Dump", :
|
36
|
-
|
35
|
+
rp_dump = rp.new("Knj::Db::Dump", db: rp_db, tables: thread_tables)
|
36
|
+
|
37
37
|
rp.static(:File, :open, fpath, "w") do |rp_fp|
|
38
38
|
print "#{i} dumping #{thread_db}\n"
|
39
39
|
rp_dump.dump(rp_fp)
|
@@ -49,4 +49,4 @@ end
|
|
49
49
|
|
50
50
|
threads.each do |thread|
|
51
51
|
thread.join
|
52
|
-
end
|
52
|
+
end
|
data/examples/example_strscan.rb
CHANGED
@@ -3,12 +3,12 @@
|
|
3
3
|
require "rubygems"
|
4
4
|
require "ruby_process"
|
5
5
|
|
6
|
-
|
6
|
+
RubyProcess.new.spawn_process do |rp|
|
7
7
|
#Spawns string in the subprocess.
|
8
8
|
str = rp.new(:String, "Kasper is 26 years old")
|
9
|
-
|
9
|
+
|
10
10
|
#Scans with regex in subprocess, but yields proxy-objects in the current process.
|
11
11
|
str.scan(/is (\d+) years old/) do |match|
|
12
12
|
puts match.__rp_marshal
|
13
13
|
end
|
14
|
-
end
|
14
|
+
end
|
data/include/args_handeling.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class RubyProcess
|
2
2
|
#Recursivly parses arrays and hashes into proxy-object-hashes.
|
3
3
|
def parse_args(args)
|
4
4
|
if args.is_a?(Array)
|
@@ -6,14 +6,14 @@ class Ruby_process
|
|
6
6
|
args.each do |val|
|
7
7
|
newarr << parse_args(val)
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
return newarr
|
11
11
|
elsif args.is_a?(Hash)
|
12
12
|
newh = {}
|
13
13
|
args.each do |key, val|
|
14
14
|
newh[parse_args(key)] = parse_args(val)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
return newh
|
18
18
|
elsif @args_allowed.index(args.class) != nil
|
19
19
|
debug "Allowing type '#{args.class}' as an argument: '#{args}'.\n" if @debug
|
@@ -23,38 +23,38 @@ class Ruby_process
|
|
23
23
|
return handle_return_object(args)
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
29
|
#Returns a special hash instead of an actual object. Some objects will be returned in their normal form (true, false and nil).
|
30
30
|
def handle_return_object(obj, pid = @my_pid)
|
31
31
|
#Dont proxy these objects.
|
32
32
|
return obj if obj.is_a?(TrueClass) or obj.is_a?(FalseClass) or obj.is_a?(NilClass)
|
33
|
-
|
33
|
+
|
34
34
|
#The object is a proxy-obj - just return its arguments that contains the true 'my_pid'.
|
35
|
-
if obj.is_a?(
|
35
|
+
if obj.is_a?(RubyProcess::ProxyObject)
|
36
36
|
debug "Returning from proxy-obj: (ID: #{obj.args[:id]}, PID: #{obj.__rp_pid}).\n" if @debug
|
37
|
-
return {:
|
37
|
+
return {type: :proxy_obj, id: obj.__rp_id, pid: obj.__rp_pid}
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
#Check if object has already been spawned. If not: spawn id. Then returns hash for it.
|
41
41
|
id = obj.__id__
|
42
42
|
@objects[id] = obj if !@objects.key?(id)
|
43
|
-
|
43
|
+
|
44
44
|
debug "Proxy-object spawned (ID: #{id}, PID: #{pid}).\n" if @debug
|
45
|
-
return {:
|
45
|
+
return {type: :proxy_obj, id: id, pid: pid}
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
#Parses an argument array to proxy-object-hashes.
|
49
49
|
def handle_return_args(arr)
|
50
50
|
newa = []
|
51
51
|
arr.each do |obj|
|
52
52
|
newa << handle_return_object(obj)
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
return newa
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
#Recursivly scans arrays and hashes for proxy-object-hashes and replaces them with actual proxy-objects.
|
59
59
|
def read_args(args)
|
60
60
|
if args.is_a?(Array)
|
@@ -62,11 +62,11 @@ class Ruby_process
|
|
62
62
|
args.each do |val|
|
63
63
|
newarr << read_args(val)
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
return newarr
|
67
|
-
elsif args.is_a?(Hash)
|
67
|
+
elsif args.is_a?(Hash) && args.length == 3 && args[:type] == :proxy_obj && args.key?(:id) && args.key?(:pid)
|
68
68
|
debug "Comparing PID (#{args[:pid]}, #{@my_pid}).\n" if @debug
|
69
|
-
|
69
|
+
|
70
70
|
if args[:pid] == @my_pid
|
71
71
|
debug "Same!\n" if @debug
|
72
72
|
return proxyobj_object(args[:id])
|
@@ -79,10 +79,10 @@ class Ruby_process
|
|
79
79
|
args.each do |key, val|
|
80
80
|
newh[read_args(key)] = read_args(val)
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
return newh
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
return args
|
87
87
|
end
|
88
|
-
end
|
88
|
+
end
|
data/lib/ruby_process.rb
CHANGED
@@ -1,79 +1,85 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
require "wref"
|
3
|
-
require "tsafe"
|
2
|
+
require "wref" unless Kernel.const_defined?(:Wref)
|
3
|
+
require "tsafe" unless Kernel.const_defined?(:Tsafe)
|
4
4
|
require "base64"
|
5
|
+
require "string-cases"
|
5
6
|
require "thread"
|
6
7
|
require "timeout"
|
7
8
|
|
8
9
|
#This class can communicate with another Ruby-process. It tries to integrate the work in the other process as seamless as possible by using proxy-objects.
|
9
|
-
class
|
10
|
+
class RubyProcess
|
10
11
|
attr_reader :finalize_count, :pid
|
11
|
-
|
12
|
+
|
12
13
|
#Require all the different commands.
|
13
14
|
dir = "#{File.dirname(__FILE__)}/../cmds"
|
14
15
|
Dir.foreach(dir) do |file|
|
15
16
|
require "#{dir}/#{file}" if file =~ /\.rb$/
|
16
17
|
end
|
17
|
-
|
18
|
+
|
18
19
|
#Methods for handeling arguments and proxy-objects in arguments.
|
19
20
|
require "#{File.dirname(__FILE__)}/../include/args_handeling.rb"
|
20
|
-
|
21
|
+
|
21
22
|
#Autoloader for subclasses.
|
22
23
|
def self.const_missing(name)
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
file_path = "#{::File.realpath(::File.dirname(__FILE__))}/ruby_process/#{::StringCases.camel_to_snake(name)}.rb"
|
25
|
+
|
26
|
+
if File.exists?(file_path)
|
27
|
+
require file_path
|
28
|
+
return const_get(name) if const_defined?(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
super
|
26
32
|
end
|
27
|
-
|
33
|
+
|
28
34
|
#Constructor.
|
29
35
|
#===Examples
|
30
|
-
#
|
36
|
+
# RubyProcess.new.spawn_process do |rp|
|
31
37
|
# str = rp.new(:String, "Kasper")
|
32
38
|
# end
|
33
39
|
def initialize(args = {})
|
34
40
|
@args = args
|
35
41
|
@debug = @args[:debug]
|
36
42
|
@pid = @args[:pid]
|
37
|
-
|
43
|
+
|
38
44
|
#These classes are allowed in call-arguments. They can be marshalled without any errors.
|
39
45
|
@args_allowed = [FalseClass, Fixnum, Integer, NilClass, String, Symbol, TrueClass]
|
40
|
-
|
46
|
+
|
41
47
|
#Set IO variables if given.
|
42
|
-
@io_out = Tsafe::Proxy.new(:
|
48
|
+
@io_out = Tsafe::Proxy.new(obj: @args[:out]) if @args[:out]
|
43
49
|
@io_in = @args[:in] if @args[:in]
|
44
50
|
@io_err = @args[:err] if @args[:err]
|
45
|
-
|
51
|
+
|
46
52
|
#This hash holds answers coming from the subprocess.
|
47
53
|
@answers = Tsafe::MonHash.new
|
48
|
-
|
54
|
+
|
49
55
|
#This hash holds objects that are referenced in the process.
|
50
56
|
@objects = Tsafe::MonHash.new
|
51
|
-
|
57
|
+
|
52
58
|
#This weak-map holds all proxy objects.
|
53
59
|
@proxy_objs = Wref_map.new
|
54
60
|
@proxy_objs_ids = Tsafe::MonHash.new
|
55
61
|
@proxy_objs_unsets = Tsafe::MonArray.new
|
56
62
|
@flush_mutex = Mutex.new
|
57
63
|
@finalize_count = 0
|
58
|
-
|
64
|
+
|
59
65
|
#Send ID is used to identify the correct answers.
|
60
66
|
@send_mutex = Mutex.new
|
61
67
|
@send_count = 0
|
62
|
-
|
68
|
+
|
63
69
|
#The PID is used to know which process proxy-objects belongs to.
|
64
70
|
@my_pid = Process.pid
|
65
71
|
end
|
66
|
-
|
72
|
+
|
67
73
|
#Spawns a new process in the same Ruby-inteterpeter as the current one.
|
68
74
|
#===Examples
|
69
|
-
# rp =
|
75
|
+
# rp = RubyProcess.new.spawn_process
|
70
76
|
# rp.str_eval("return 10").__rp_marshal #=> 10
|
71
77
|
# rp.destroy
|
72
78
|
def spawn_process(args = nil)
|
73
79
|
#Used for printing debug-stuff.
|
74
80
|
@main = true
|
75
|
-
|
76
|
-
if args
|
81
|
+
|
82
|
+
if args && args[:exec]
|
77
83
|
cmd = "#{args[:exec]}"
|
78
84
|
else
|
79
85
|
if !ENV["rvm_ruby_string"].to_s.empty?
|
@@ -82,82 +88,82 @@ class Ruby_process
|
|
82
88
|
cmd = "ruby"
|
83
89
|
end
|
84
90
|
end
|
85
|
-
|
91
|
+
|
86
92
|
cmd << " \"#{File.realpath(File.dirname(__FILE__))}/../scripts/ruby_process_script.rb\" --pid=#{@my_pid}"
|
87
93
|
cmd << " --debug" if @args[:debug]
|
88
94
|
cmd << " \"--title=#{@args[:title]}\"" if !@args[:title].to_s.strip.empty?
|
89
|
-
|
95
|
+
|
90
96
|
#Start process and set IO variables.
|
91
97
|
require "open3"
|
92
98
|
@io_out, @io_in, @io_err = Open3.popen3(cmd)
|
93
|
-
@io_out = Tsafe::Proxy.new(:
|
99
|
+
@io_out = Tsafe::Proxy.new(obj: @io_out)
|
94
100
|
@io_out.sync = true
|
95
101
|
@io_in.sync = true
|
96
102
|
@io_err.sync = true
|
97
|
-
|
103
|
+
|
98
104
|
started = false
|
99
105
|
@io_in.each_line do |str|
|
100
106
|
if str == "ruby_process_started\n"
|
101
107
|
started = true
|
102
108
|
break
|
103
109
|
end
|
104
|
-
|
110
|
+
|
105
111
|
debug "Ruby-process-debug from stdout before started: '#{str}'\n" if @debug
|
106
112
|
end
|
107
|
-
|
108
|
-
raise "Ruby-sub-process couldnt start: '#{@io_err.read}'."
|
113
|
+
|
114
|
+
raise "Ruby-sub-process couldnt start: '#{@io_err.read}'." unless started
|
109
115
|
self.listen
|
110
|
-
|
116
|
+
|
111
117
|
#Start by getting the PID of the process.
|
112
118
|
begin
|
113
119
|
@pid = self.static(:Process, :pid).__rp_marshal
|
114
|
-
raise "Unexpected PID: '#{@pid}'." if !@pid.is_a?(Fixnum)
|
120
|
+
raise "Unexpected PID: '#{@pid}'." if !@pid.is_a?(Fixnum) && !@pid.is_a?(Integer)
|
115
121
|
rescue => e
|
116
122
|
self.destroy
|
117
123
|
raise e
|
118
124
|
end
|
119
|
-
|
125
|
+
|
120
126
|
if block_given?
|
121
127
|
begin
|
122
128
|
yield(self)
|
123
129
|
ensure
|
124
130
|
self.destroy
|
125
131
|
end
|
126
|
-
|
132
|
+
|
127
133
|
return self
|
128
134
|
else
|
129
135
|
return self
|
130
136
|
end
|
131
137
|
end
|
132
|
-
|
138
|
+
|
133
139
|
#Starts listening on the given IO's. It is useally not needed to call this method manually.
|
134
140
|
def listen
|
135
141
|
#Start listening for input.
|
136
142
|
start_listen
|
137
|
-
|
143
|
+
|
138
144
|
#Start listening for errors.
|
139
145
|
start_listen_errors
|
140
146
|
end
|
141
|
-
|
147
|
+
|
142
148
|
#First tries to make the sub-process exit gently. Then kills it with "TERM" and 9 afterwards to make sure its dead. If 'spawn_process' is given a block, this method is automatically ensured after the block is run.
|
143
149
|
def destroy
|
144
150
|
return nil if self.destroyed?
|
145
|
-
|
151
|
+
|
146
152
|
debug "Destroying Ruby-process (#{caller}).\n" if @debug
|
147
153
|
pid = @pid
|
148
154
|
tries = 0
|
149
|
-
|
155
|
+
|
150
156
|
#Make main kill it and make sure its dead...
|
151
157
|
begin
|
152
|
-
if @main
|
158
|
+
if @main && @pid
|
153
159
|
tries += 1
|
154
160
|
Process.kill("TERM", pid) rescue Errno::ESRCH
|
155
|
-
|
161
|
+
|
156
162
|
#Ensure subprocess is dead.
|
157
163
|
begin
|
158
164
|
Timeout.timeout(1) do
|
159
165
|
sleep 0.01
|
160
|
-
|
166
|
+
|
161
167
|
loop do
|
162
168
|
begin
|
163
169
|
Process.getpgid(pid)
|
@@ -165,7 +171,7 @@ class Ruby_process
|
|
165
171
|
rescue Errno::ESRCH
|
166
172
|
alive = false
|
167
173
|
end
|
168
|
-
|
174
|
+
|
169
175
|
break if !alive
|
170
176
|
end
|
171
177
|
end
|
@@ -178,74 +184,74 @@ class Ruby_process
|
|
178
184
|
#Process is already dead - ignore.
|
179
185
|
ensure
|
180
186
|
@pid = nil
|
181
|
-
|
187
|
+
|
182
188
|
@io_out.close if @io_out && !@io_out.closed?
|
183
189
|
@io_out = nil
|
184
|
-
|
190
|
+
|
185
191
|
@io_in.close if @io_in && !@io_in.closed?
|
186
192
|
@io_in = nil
|
187
|
-
|
193
|
+
|
188
194
|
@io_err if @io_err && !@io_err.closed?
|
189
195
|
@io_err = nil
|
190
|
-
|
196
|
+
|
191
197
|
@main = nil
|
192
198
|
end
|
193
|
-
|
199
|
+
|
194
200
|
return nil
|
195
201
|
end
|
196
|
-
|
202
|
+
|
197
203
|
#Returns true if the Ruby process has been destroyed.
|
198
204
|
def destroyed?
|
199
|
-
return true if !@pid
|
205
|
+
return true if !@pid && !@io_out && !@io_in && !@io_err && @main == nil
|
200
206
|
return false
|
201
207
|
end
|
202
|
-
|
208
|
+
|
203
209
|
#Joins the listen thread and error-thread. This is useually only called on the sub-process side, but can also be useful, if you are waiting for a delayed callback from the subprocess.
|
204
210
|
def join
|
205
211
|
debug "Joining listen-thread.\n" if @debug
|
206
212
|
@thr_listen.join if @thr_listen
|
207
213
|
raise @listen_err if @listen_err
|
208
|
-
|
214
|
+
|
209
215
|
debug "Joining error-thread.\n" if @debug
|
210
216
|
@thr_err.join if @thr_join
|
211
217
|
raise @listen_err_err if @listen_err_err
|
212
218
|
end
|
213
|
-
|
219
|
+
|
214
220
|
#Sends a command to the other process. This should not be called manually, but is used by various other parts of the framework.
|
215
221
|
def send(obj, &block)
|
216
222
|
alive_check!
|
217
|
-
|
223
|
+
|
218
224
|
#Sync ID stuff so they dont get mixed up.
|
219
225
|
id = nil
|
220
226
|
@send_mutex.synchronize do
|
221
227
|
id = @send_count
|
222
228
|
@send_count += 1
|
223
229
|
end
|
224
|
-
|
230
|
+
|
225
231
|
#Parse block.
|
226
232
|
if block
|
227
|
-
block_proxy_res = self.send(:
|
233
|
+
block_proxy_res = self.send(cmd: :spawn_proxy_block, id: block.__id__, answer_id: id)
|
228
234
|
block_proxy_res_id = block_proxy_res[:id]
|
229
|
-
raise "No block ID was returned?"
|
235
|
+
raise "No block ID was returned?" unless block_proxy_res_id
|
230
236
|
raise "Invalid block-ID: '#{block_proxy_res_id}'." if block_proxy_res_id.to_i <= 0
|
231
237
|
@proxy_objs[block_proxy_res_id] = block
|
232
238
|
@proxy_objs_ids[block.__id__] = block_proxy_res_id
|
233
239
|
ObjectSpace.define_finalizer(block, self.method(:proxyobj_finalizer))
|
234
240
|
obj[:block] = {
|
235
|
-
:
|
236
|
-
:
|
241
|
+
id: block_proxy_res_id,
|
242
|
+
arity: block.arity
|
237
243
|
}
|
238
244
|
end
|
239
|
-
|
240
|
-
flush_finalized
|
241
|
-
|
245
|
+
|
246
|
+
flush_finalized unless obj[:cmd] == :flush_finalized
|
247
|
+
|
242
248
|
debug "Sending(#{id}): #{obj}\n" if @debug
|
243
249
|
line = Base64.strict_encode64(Marshal.dump(
|
244
|
-
:
|
245
|
-
:
|
246
|
-
:
|
250
|
+
id: id,
|
251
|
+
type: :send,
|
252
|
+
obj: obj
|
247
253
|
))
|
248
|
-
|
254
|
+
|
249
255
|
begin
|
250
256
|
@answers[id] = Queue.new
|
251
257
|
@io_out.puts(line)
|
@@ -255,7 +261,7 @@ class Ruby_process
|
|
255
261
|
@answers.delete(id)
|
256
262
|
end
|
257
263
|
end
|
258
|
-
|
264
|
+
|
259
265
|
#Returns true if the child process is still running. Otherwise false.
|
260
266
|
def alive?
|
261
267
|
begin
|
@@ -265,9 +271,9 @@ class Ruby_process
|
|
265
271
|
return false
|
266
272
|
end
|
267
273
|
end
|
268
|
-
|
274
|
+
|
269
275
|
private
|
270
|
-
|
276
|
+
|
271
277
|
#Raises an error if the subprocess is no longer alive.
|
272
278
|
def alive_check!
|
273
279
|
raise "Has been destroyed." if self.destroyed?
|
@@ -276,14 +282,14 @@ class Ruby_process
|
|
276
282
|
raise "'io_in' was closed." if @io_in.closed?
|
277
283
|
raise "No listen thread." if !@thr_listen
|
278
284
|
#raise "Listen thread wasnt alive?" if !@thr_listen.alive?
|
279
|
-
|
285
|
+
|
280
286
|
return nil
|
281
287
|
end
|
282
|
-
|
288
|
+
|
283
289
|
#Prints the given string to stderr. Raises error if debugging is not enabled.
|
284
290
|
def debug(str_full)
|
285
|
-
raise "Debug not enabled?"
|
286
|
-
|
291
|
+
raise "Debug not enabled?" unless @debug
|
292
|
+
|
287
293
|
str_full.each_line do |str|
|
288
294
|
if @main
|
289
295
|
$stderr.print "(M#{@my_pid}) #{str}"
|
@@ -292,54 +298,54 @@ class Ruby_process
|
|
292
298
|
end
|
293
299
|
end
|
294
300
|
end
|
295
|
-
|
301
|
+
|
296
302
|
#Registers an object ID as a proxy-object on the host-side.
|
297
303
|
def proxyobj_get(id, pid = @my_pid)
|
298
304
|
if proxy_obj = @proxy_objs.get!(id)
|
299
305
|
debug "Reuse proxy-obj (ID: #{id}, PID: #{pid}, fID: #{proxy_obj.args[:id]}, fPID: #{proxy_obj.args[:pid]})\n" if @debug
|
300
306
|
return proxy_obj
|
301
307
|
end
|
302
|
-
|
308
|
+
|
303
309
|
@proxy_objs_unsets.delete(id)
|
304
|
-
proxy_obj =
|
310
|
+
proxy_obj = RubyProcess::ProxyObject.new(self, id, pid)
|
305
311
|
@proxy_objs[id] = proxy_obj
|
306
312
|
@proxy_objs_ids[proxy_obj.__id__] = id
|
307
313
|
ObjectSpace.define_finalizer(proxy_obj, self.method(:proxyobj_finalizer))
|
308
|
-
|
314
|
+
|
309
315
|
return proxy_obj
|
310
316
|
end
|
311
|
-
|
317
|
+
|
312
318
|
#Returns the saved proxy-object by the given ID. Raises error if it doesnt exist.
|
313
319
|
def proxyobj_object(id)
|
314
320
|
if obj = @objects[id]
|
315
321
|
return obj
|
316
322
|
end
|
317
|
-
|
323
|
+
|
318
324
|
raise "No object by that ID: '#{id}' (#{@objects})."
|
319
325
|
end
|
320
|
-
|
326
|
+
|
321
327
|
#Method used for detecting garbage-collected proxy-objects. This way we can also free references to them in the other process, so it doesnt run out of memory.
|
322
328
|
def proxyobj_finalizer(id)
|
323
329
|
debug "Finalized #{id}\n" if @debug
|
324
330
|
proxy_id = @proxy_objs_ids[id]
|
325
|
-
|
331
|
+
|
326
332
|
if !proxy_id
|
327
333
|
debug "No such ID in proxy objects IDs hash: '#{id}'.\n" if @debug
|
328
334
|
else
|
329
335
|
@proxy_objs_unsets << proxy_id
|
330
336
|
debug "Done finalizing #{id}\n" if @debug
|
331
337
|
end
|
332
|
-
|
338
|
+
|
333
339
|
return nil
|
334
340
|
end
|
335
|
-
|
341
|
+
|
336
342
|
#Waits for an answer to appear in the answers-hash. Then deletes it from hash and returns it.
|
337
343
|
def answer_read(id)
|
338
344
|
loop do
|
339
345
|
debug "Waiting for answer #{id}\n" if @debug
|
340
346
|
answer = @answers[id].pop
|
341
347
|
debug "Returning answer #{id}\n" if @debug
|
342
|
-
|
348
|
+
|
343
349
|
if answer.is_a?(Hash) and type = answer[:type]
|
344
350
|
if type == :error and class_str = answer[:class] and msg = answer[:msg] and bt_orig = answer[:bt]
|
345
351
|
begin
|
@@ -349,17 +355,17 @@ class Ruby_process
|
|
349
355
|
bt_orig.each do |btline|
|
350
356
|
bt << "(#{@pid}): #{btline}"
|
351
357
|
end
|
352
|
-
|
358
|
+
|
353
359
|
bt += e.backtrace
|
354
360
|
e.set_backtrace(bt)
|
355
361
|
raise e
|
356
362
|
end
|
357
|
-
elsif type == :proxy_obj
|
363
|
+
elsif type == :proxy_obj && id = answer[:id] and pid = answer[:pid]
|
358
364
|
return proxyobj_get(id, pid)
|
359
365
|
elsif type == :proxy_block_call and block = answer[:block] and args = answer[:args] and queue = answer[:queue]
|
360
366
|
#Calls the block. This is used to call the block from the same thread that the answer is being read from. This can cause problems in Hayabusa, that uses thread-variables to determine output and such.
|
361
367
|
block.call(*args)
|
362
|
-
|
368
|
+
|
363
369
|
#Tells the parent thread that the block has been executed and it should continue.
|
364
370
|
queue << true
|
365
371
|
else
|
@@ -369,19 +375,19 @@ class Ruby_process
|
|
369
375
|
return answer
|
370
376
|
end
|
371
377
|
end
|
372
|
-
|
378
|
+
|
373
379
|
raise "This should never be reached."
|
374
380
|
end
|
375
|
-
|
381
|
+
|
376
382
|
#Starts the listen-thread that listens for, and executes, commands. This is normally automatically called and should not be called manually.
|
377
383
|
def start_listen
|
378
384
|
@thr_listen = Thread.new do
|
379
385
|
begin
|
380
386
|
@io_in.each_line do |line|
|
381
|
-
raise "No line?" if !line
|
387
|
+
raise "No line?" if !line || line.to_s.strip.empty?
|
382
388
|
alive_check!
|
383
389
|
debug "Received: #{line}" if @debug
|
384
|
-
|
390
|
+
|
385
391
|
begin
|
386
392
|
obj = Marshal.load(Base64.strict_decode64(line.strip))
|
387
393
|
debug "Object received: #{obj}\n" if @debug
|
@@ -389,28 +395,28 @@ class Ruby_process
|
|
389
395
|
$stderr.puts "Base64Str: #{line}" if @debug
|
390
396
|
$stderr.puts e.inspect if @debug
|
391
397
|
$stderr.puts e.backtrace if @debug
|
392
|
-
|
398
|
+
|
393
399
|
raise e
|
394
400
|
end
|
395
|
-
|
401
|
+
|
396
402
|
id = obj[:id]
|
397
403
|
obj_type = obj[:type]
|
398
|
-
|
404
|
+
|
399
405
|
if obj_type == :send
|
400
406
|
Thread.new do
|
401
407
|
#Hack to be able to do callbacks from same thread when using blocks. This should properly be cleaned a bit up in the future.
|
402
408
|
obj[:obj][:send_id] = id
|
403
|
-
|
409
|
+
|
404
410
|
begin
|
405
|
-
raise "Object was not a hash."
|
406
|
-
raise "No ID was given?"
|
411
|
+
raise "Object was not a hash." unless obj.is_a?(Hash)
|
412
|
+
raise "No ID was given?" unless id
|
407
413
|
res = self.__send__("cmd_#{obj[:obj][:cmd]}", obj[:obj])
|
408
414
|
rescue Exception => e
|
409
|
-
raise e if e.is_a?(SystemExit)
|
410
|
-
res = {:
|
415
|
+
raise e if e.is_a?(SystemExit) || e.is_a?(Interrupt)
|
416
|
+
res = {type: :error, class: e.class.name, msg: e.message, bt: e.backtrace}
|
411
417
|
end
|
412
|
-
|
413
|
-
data = Base64.strict_encode64(Marshal.dump(:
|
418
|
+
|
419
|
+
data = Base64.strict_encode64(Marshal.dump(type: :answer, id: id, answer: res))
|
414
420
|
@io_out.puts(data)
|
415
421
|
end
|
416
422
|
elsif obj_type == :answer
|
@@ -429,16 +435,16 @@ class Ruby_process
|
|
429
435
|
debug "Error while listening: #{e.inspect}"
|
430
436
|
debug e.backtrace.join("\n") + "\n"
|
431
437
|
end
|
432
|
-
|
438
|
+
|
433
439
|
@listen_err = e
|
434
440
|
end
|
435
441
|
end
|
436
442
|
end
|
437
|
-
|
443
|
+
|
438
444
|
#Starts the listen thread that outputs the 'stderr' for the other process on this process's 'stderr'.
|
439
445
|
def start_listen_errors
|
440
446
|
return nil if !@io_err
|
441
|
-
|
447
|
+
|
442
448
|
@thr_err = Thread.new do
|
443
449
|
begin
|
444
450
|
@io_err.each_line do |str|
|
@@ -449,4 +455,4 @@ class Ruby_process
|
|
449
455
|
end
|
450
456
|
end
|
451
457
|
end
|
452
|
-
end
|
458
|
+
end
|