ruby_process 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/cmds/blocks.rb +16 -3
- data/cmds/new.rb +1 -1
- data/cmds/static.rb +1 -1
- data/cmds/system.rb +15 -0
- data/include/args_handeling.rb +2 -2
- data/lib/ruby_process.rb +145 -71
- data/lib/ruby_process_cproxy.rb +24 -19
- data/lib/ruby_process_proxyobj.rb +13 -8
- data/ruby_process.gemspec +3 -2
- data/spec/cproxy_spec.rb +80 -0
- data/spec/hard_load_spec.rb +1 -1
- data/spec/leaks_spec.rb +24 -0
- data/spec/ruby_process_spec.rb +29 -1
- metadata +18 -17
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.8
|
data/cmds/blocks.rb
CHANGED
@@ -2,18 +2,31 @@ class Ruby_process
|
|
2
2
|
#Calls a block by its block-ID with given arguments.
|
3
3
|
def cmd_block_call(obj)
|
4
4
|
raise "Invalid block-ID: '#{obj}'." if obj[:block_id].to_i <= 0
|
5
|
-
block_ele = @
|
5
|
+
block_ele = @proxy_objs[obj[:block_id]]
|
6
6
|
raise "No block by that ID: '#{obj[:block_id]}'." if !block_ele
|
7
7
|
raise "Not a block? '#{block_ele.class.name}'." if !block_ele.respond_to?(:call)
|
8
8
|
debug "Calling block #{obj[:block_id]}: #{obj}\n" if @debug
|
9
|
-
|
9
|
+
|
10
|
+
answer_id = obj[:answer_id]
|
11
|
+
raise "No ':answer_id' was given (#{obj})." if !answer_id
|
12
|
+
|
13
|
+
if answer = @answers[answer_id]
|
14
|
+
#Use a queue to sleep thread until the block has been executed.
|
15
|
+
queue = Queue.new
|
16
|
+
answer.push(:type => :proxy_block_call, :block => block_ele, :args => read_args(obj[:args]), :queue => queue)
|
17
|
+
res = queue.pop
|
18
|
+
raise "Expected true but didnt get that: '#{res}'." if res != true
|
19
|
+
else
|
20
|
+
block_ele.call(*read_args(obj[:args]))
|
21
|
+
end
|
22
|
+
|
10
23
|
return nil
|
11
24
|
end
|
12
25
|
|
13
26
|
#Spawns a block and returns its ID.
|
14
27
|
def cmd_spawn_proxy_block(obj)
|
15
28
|
block = proc{
|
16
|
-
send(:cmd => :block_call, :block_id => obj[:id])
|
29
|
+
send(:cmd => :block_call, :block_id => obj[:id], :answer_id => obj[:answer_id])
|
17
30
|
}
|
18
31
|
|
19
32
|
id = block.__id__
|
data/cmds/new.rb
CHANGED
@@ -24,7 +24,7 @@ class Ruby_process
|
|
24
24
|
if obj.key?(:block)
|
25
25
|
real_block = proc{|*args|
|
26
26
|
debug "Block called! #{args}\n" if @debug
|
27
|
-
send(:cmd => :block_call, :block_id => obj[:block][:id], :args => handle_return_args(args))
|
27
|
+
send(:cmd => :block_call, :block_id => obj[:block][:id], :answer_id => obj[:send_id], :args => handle_return_args(args))
|
28
28
|
}
|
29
29
|
|
30
30
|
block = block_with_arity(:arity => obj[:block][:arity], &real_block)
|
data/cmds/static.rb
CHANGED
@@ -17,7 +17,7 @@ class Ruby_process
|
|
17
17
|
if obj.key?(:block)
|
18
18
|
real_block = proc{|*args|
|
19
19
|
debug "Block called! #{args}\n" if @debug
|
20
|
-
send(:cmd => :block_call, :block_id => obj[:block][:id], :args => handle_return_args(args))
|
20
|
+
send(:cmd => :block_call, :block_id => obj[:block][:id], :answer_id => obj[:send_id], :args => handle_return_args(args))
|
21
21
|
}
|
22
22
|
|
23
23
|
block = block_with_arity(:arity => obj[:block][:arity], &real_block)
|
data/cmds/system.rb
CHANGED
@@ -29,4 +29,19 @@ class Ruby_process
|
|
29
29
|
|
30
30
|
return nil
|
31
31
|
end
|
32
|
+
|
33
|
+
#Starts garbage-collecting and then flushes the finalized objects to the sub-process. Does the same thing in the sub-process.
|
34
|
+
def garbage_collect
|
35
|
+
GC.start
|
36
|
+
self.flush_finalized
|
37
|
+
send(:cmd => :garbage_collect)
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
#The sub-process-side execution of 'garbage_collect'.
|
42
|
+
def cmd_garbage_collect(obj)
|
43
|
+
GC.start
|
44
|
+
self.flush_finalized
|
45
|
+
return nil
|
46
|
+
end
|
32
47
|
end
|
data/include/args_handeling.rb
CHANGED
@@ -31,8 +31,8 @@ class Ruby_process
|
|
31
31
|
|
32
32
|
#The object is a proxy-obj - just return its arguments that contains the true 'my_pid'.
|
33
33
|
if obj.is_a?(Ruby_process::Proxyobj)
|
34
|
-
debug "Returning from proxy-obj: (ID: #{obj.args[:id]}, PID: #{obj.
|
35
|
-
return {:type => :proxy_obj, :id => obj.
|
34
|
+
debug "Returning from proxy-obj: (ID: #{obj.args[:id]}, PID: #{obj.__rp_pid}).\n" if @debug
|
35
|
+
return {:type => :proxy_obj, :id => obj.__rp_id, :pid => obj.__rp_pid}
|
36
36
|
end
|
37
37
|
|
38
38
|
#Check if object has already been spawned. If not: spawn id. Then returns hash for it.
|
data/lib/ruby_process.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
require "base64"
|
3
2
|
require "wref" if !Kernel.const_defined?(:Wref)
|
4
3
|
require "tsafe" if !Kernel.const_defined?(:Tsafe)
|
4
|
+
require "base64"
|
5
|
+
require "thread"
|
6
|
+
require "timeout"
|
5
7
|
|
6
8
|
#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.
|
7
9
|
class Ruby_process
|
8
|
-
attr_reader :finalize_count
|
10
|
+
attr_reader :finalize_count, :pid
|
9
11
|
|
10
12
|
#Require all the different commands.
|
11
13
|
dir = "#{File.dirname(__FILE__)}/../cmds"
|
@@ -139,21 +141,56 @@ class Ruby_process
|
|
139
141
|
|
140
142
|
#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.
|
141
143
|
def destroy
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
return nil if self.destroyed?
|
145
|
+
|
146
|
+
debug "Destroying Ruby-process (#{caller}).\n" if @debug
|
147
|
+
pid = @pid
|
148
|
+
tries = 0
|
147
149
|
|
148
150
|
#Make main kill it and make sure its dead...
|
149
151
|
begin
|
150
152
|
if @main and @pid
|
151
|
-
|
152
|
-
Process.kill(
|
153
|
+
tries += 1
|
154
|
+
Process.kill("TERM", pid) rescue Errno::ESRCH
|
155
|
+
|
156
|
+
#Ensure subprocess is dead.
|
157
|
+
begin
|
158
|
+
Timeout.timeout(1) do
|
159
|
+
sleep 0.01
|
160
|
+
|
161
|
+
loop do
|
162
|
+
begin
|
163
|
+
Process.getpgid(pid)
|
164
|
+
alive = true
|
165
|
+
rescue Errno::ESRCH
|
166
|
+
alive = false
|
167
|
+
end
|
168
|
+
|
169
|
+
break if !alive
|
170
|
+
end
|
171
|
+
end
|
172
|
+
rescue Timeout::Error
|
173
|
+
Process.kill(9, pid) rescue Errno::ESRCH
|
174
|
+
retry
|
175
|
+
end
|
153
176
|
end
|
154
177
|
rescue Errno::ESRCH
|
155
178
|
#Process is already dead - ignore.
|
179
|
+
ensure
|
180
|
+
@pid = nil
|
181
|
+
@io_out = nil
|
182
|
+
@io_in = nil
|
183
|
+
@io_err = nil
|
184
|
+
@main = nil
|
156
185
|
end
|
186
|
+
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
|
190
|
+
#Returns true if the Ruby process has been destroyed.
|
191
|
+
def destroyed?
|
192
|
+
return true if !@pid and !@io_out and !@io_in and !@io_err and @main == nil
|
193
|
+
return false
|
157
194
|
end
|
158
195
|
|
159
196
|
#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.
|
@@ -171,46 +208,51 @@ class Ruby_process
|
|
171
208
|
def send(obj, &block)
|
172
209
|
alive_check!
|
173
210
|
|
211
|
+
#Sync ID stuff so they dont get mixed up.
|
212
|
+
id = nil
|
213
|
+
@send_mutex.synchronize do
|
214
|
+
id = @send_count
|
215
|
+
@send_count += 1
|
216
|
+
end
|
217
|
+
|
174
218
|
#Parse block.
|
175
219
|
if block
|
176
|
-
block_proxy_res = self.send(:cmd => :spawn_proxy_block, :id => block.__id__)
|
177
|
-
|
178
|
-
raise "
|
179
|
-
|
180
|
-
@
|
181
|
-
@
|
182
|
-
ObjectSpace.define_finalizer(
|
220
|
+
block_proxy_res = self.send(:cmd => :spawn_proxy_block, :id => block.__id__, :answer_id => id)
|
221
|
+
block_proxy_res_id = block_proxy_res[:id]
|
222
|
+
raise "No block ID was returned?" if !block_proxy_res_id
|
223
|
+
raise "Invalid block-ID: '#{block_proxy_res_id}'." if block_proxy_res_id.to_i <= 0
|
224
|
+
@proxy_objs[block_proxy_res_id] = block
|
225
|
+
@proxy_objs_ids[block.__id__] = block_proxy_res_id
|
226
|
+
ObjectSpace.define_finalizer(block, self.method(:proxyobj_finalizer))
|
183
227
|
obj[:block] = {
|
184
|
-
:id =>
|
228
|
+
:id => block_proxy_res_id,
|
185
229
|
:arity => block.arity
|
186
230
|
}
|
187
231
|
end
|
188
232
|
|
189
233
|
flush_finalized if obj[:cmd] != :flush_finalized
|
190
234
|
|
191
|
-
#Sync ID stuff so they dont get mixed up.
|
192
|
-
id = nil
|
193
|
-
@send_mutex.synchronize do
|
194
|
-
id = @send_count
|
195
|
-
@send_count += 1
|
196
|
-
end
|
197
|
-
|
198
235
|
debug "Sending(#{id}): #{obj}\n" if @debug
|
199
236
|
line = Base64.strict_encode64(Marshal.dump(
|
200
237
|
:id => id,
|
201
238
|
:type => :send,
|
202
239
|
:obj => obj
|
203
240
|
))
|
204
|
-
@answers[id] = Queue.new
|
205
|
-
@io_out.puts(line)
|
206
241
|
|
207
|
-
|
242
|
+
begin
|
243
|
+
@answers[id] = Queue.new
|
244
|
+
@io_out.puts(line)
|
245
|
+
return answer_read(id)
|
246
|
+
ensure
|
247
|
+
#Be sure that the answer is actually deleted to avoid memory-leaking.
|
248
|
+
@answers.delete(id)
|
249
|
+
end
|
208
250
|
end
|
209
251
|
|
210
252
|
#Returns true if the child process is still running. Otherwise false.
|
211
253
|
def alive?
|
212
254
|
begin
|
213
|
-
|
255
|
+
alive_check!
|
214
256
|
return true
|
215
257
|
rescue
|
216
258
|
return false
|
@@ -219,6 +261,19 @@ class Ruby_process
|
|
219
261
|
|
220
262
|
private
|
221
263
|
|
264
|
+
#Raises an error if the subprocess is no longer alive.
|
265
|
+
def alive_check!
|
266
|
+
raise "Has been destroyed." if self.destroyed?
|
267
|
+
raise "No 'io_out'." if !@io_out
|
268
|
+
raise "No 'io_in'." if !@io_in
|
269
|
+
raise "'io_in' was closed." if @io_in.closed?
|
270
|
+
raise "No listen thread." if !@thr_listen
|
271
|
+
#raise "Listen thread wasnt alive?" if !@thr_listen.alive?
|
272
|
+
|
273
|
+
return nil
|
274
|
+
end
|
275
|
+
|
276
|
+
#Prints the given string to stderr. Raises error if debugging is not enabled.
|
222
277
|
def debug(str_full)
|
223
278
|
raise "Debug not enabled?" if !@debug
|
224
279
|
|
@@ -231,17 +286,6 @@ class Ruby_process
|
|
231
286
|
end
|
232
287
|
end
|
233
288
|
|
234
|
-
#Raises an error if the subprocess is no longer alive.
|
235
|
-
def alive_check!
|
236
|
-
raise "No 'io_out'." if !@io_out
|
237
|
-
raise "No 'io_in'." if !@io_in
|
238
|
-
raise "'io_in' was closed." if @io_in.closed?
|
239
|
-
raise "No listen thread." if !@thr_listen
|
240
|
-
raise "Listen thread wasnt alive?" if !@thr_listen.alive?
|
241
|
-
|
242
|
-
return nil
|
243
|
-
end
|
244
|
-
|
245
289
|
#Registers an object ID as a proxy-object on the host-side.
|
246
290
|
def proxyobj_get(id, pid = @my_pid)
|
247
291
|
if proxy_obj = @proxy_objs.get!(id)
|
@@ -250,7 +294,7 @@ class Ruby_process
|
|
250
294
|
end
|
251
295
|
|
252
296
|
@proxy_objs_unsets.delete(id)
|
253
|
-
proxy_obj = Ruby_process::Proxyobj.new(
|
297
|
+
proxy_obj = Ruby_process::Proxyobj.new(self, id, pid)
|
254
298
|
@proxy_objs[id] = proxy_obj
|
255
299
|
@proxy_objs_ids[proxy_obj.__id__] = id
|
256
300
|
ObjectSpace.define_finalizer(proxy_obj, self.method(:proxyobj_finalizer))
|
@@ -258,10 +302,13 @@ class Ruby_process
|
|
258
302
|
return proxy_obj
|
259
303
|
end
|
260
304
|
|
305
|
+
#Returns the saved proxy-object by the given ID. Raises error if it doesnt exist.
|
261
306
|
def proxyobj_object(id)
|
262
|
-
obj = @objects[id]
|
263
|
-
|
264
|
-
|
307
|
+
if obj = @objects[id]
|
308
|
+
return obj
|
309
|
+
end
|
310
|
+
|
311
|
+
raise "No object by that ID: '#{id}' (#{@objects})."
|
265
312
|
end
|
266
313
|
|
267
314
|
#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.
|
@@ -281,38 +328,50 @@ class Ruby_process
|
|
281
328
|
|
282
329
|
#Waits for an answer to appear in the answers-hash. Then deletes it from hash and returns it.
|
283
330
|
def answer_read(id)
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
331
|
+
loop do
|
332
|
+
debug "Waiting for answer #{id}\n" if @debug
|
333
|
+
answer = @answers[id].pop
|
334
|
+
debug "Returning answer #{id}\n" if @debug
|
335
|
+
|
336
|
+
if answer.is_a?(Hash) and type = answer[:type]
|
337
|
+
if type == :error and class_str = answer[:class] and msg = answer[:msg] and bt_orig = answer[:bt]
|
338
|
+
begin
|
339
|
+
raise "#{class_str}: #{msg}"
|
340
|
+
rescue => e
|
341
|
+
bt = []
|
342
|
+
bt_orig.each do |btline|
|
343
|
+
bt << "(#{@pid}): #{btline}"
|
344
|
+
end
|
345
|
+
|
346
|
+
bt += e.backtrace
|
347
|
+
e.set_backtrace(bt)
|
348
|
+
raise e
|
349
|
+
end
|
350
|
+
elsif type == :proxy_obj and id = answer[:id] and pid = answer[:pid]
|
351
|
+
return proxyobj_get(id, pid)
|
352
|
+
elsif type == :proxy_block_call and block = answer[:block] and args = answer[:args] and queue = answer[:queue]
|
353
|
+
#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.
|
354
|
+
block.call(*args)
|
355
|
+
|
356
|
+
#Tells the parent thread that the block has been executed and it should continue.
|
357
|
+
queue << true
|
358
|
+
else
|
359
|
+
return answer
|
297
360
|
end
|
298
|
-
|
299
|
-
|
300
|
-
e.set_backtrace(bt)
|
301
|
-
raise e
|
361
|
+
else
|
362
|
+
return answer
|
302
363
|
end
|
303
|
-
elsif answer.is_a?(Hash) and answer[:type] == :proxy_obj and answer.key?(:id)
|
304
|
-
return proxyobj_get(answer[:id], answer[:pid])
|
305
364
|
end
|
306
365
|
|
307
|
-
|
366
|
+
raise "This should never be reached."
|
308
367
|
end
|
309
368
|
|
310
|
-
#Starts the listen-thread that listens for, and executes, commands.
|
369
|
+
#Starts the listen-thread that listens for, and executes, commands. This is normally automatically called and should not be called manually.
|
311
370
|
def start_listen
|
312
371
|
@thr_listen = Thread.new do
|
313
372
|
begin
|
314
373
|
@io_in.each_line do |line|
|
315
|
-
raise "No line?" if !line or line.to_s.strip.
|
374
|
+
raise "No line?" if !line or line.to_s.strip.empty?
|
316
375
|
alive_check!
|
317
376
|
debug "Received: #{line}" if @debug
|
318
377
|
|
@@ -327,28 +386,43 @@ class Ruby_process
|
|
327
386
|
raise e
|
328
387
|
end
|
329
388
|
|
330
|
-
|
389
|
+
id = obj[:id]
|
390
|
+
obj_type = obj[:type]
|
391
|
+
|
392
|
+
if obj_type == :send
|
331
393
|
Thread.new do
|
394
|
+
#Hack to be able to do callbacks from same thread when using blocks. This should properly be cleaned a bit up in the future.
|
395
|
+
obj[:obj][:send_id] = id
|
396
|
+
|
332
397
|
begin
|
333
398
|
raise "Object was not a hash." if !obj.is_a?(Hash)
|
334
|
-
raise "No ID was given?" if !
|
399
|
+
raise "No ID was given?" if !id
|
335
400
|
res = self.__send__("cmd_#{obj[:obj][:cmd]}", obj[:obj])
|
336
401
|
rescue Exception => e
|
337
402
|
raise e if e.is_a?(SystemExit) or e.is_a?(Interrupt)
|
338
403
|
res = {:type => :error, :class => e.class.name, :msg => e.message, :bt => e.backtrace}
|
339
404
|
end
|
340
405
|
|
341
|
-
data = Base64.strict_encode64(Marshal.dump(:type => :answer, :id =>
|
406
|
+
data = Base64.strict_encode64(Marshal.dump(:type => :answer, :id => id, :answer => res))
|
342
407
|
@io_out.puts(data)
|
343
408
|
end
|
344
|
-
elsif
|
345
|
-
|
346
|
-
|
409
|
+
elsif obj_type == :answer
|
410
|
+
if answer_queue = @answers[id]
|
411
|
+
debug "Answer #{id} saved.\n" if @debug
|
412
|
+
answer_queue << obj[:answer]
|
413
|
+
elsif @debug
|
414
|
+
debug "No answer-queue could be found for ID #{id}."
|
415
|
+
end
|
347
416
|
else
|
348
417
|
raise "Unknown object: '#{obj}'."
|
349
418
|
end
|
350
419
|
end
|
351
420
|
rescue => e
|
421
|
+
if @debug
|
422
|
+
debug "Error while listening: #{e.inspect}"
|
423
|
+
debug e.backtrace.join("\n") + "\n"
|
424
|
+
end
|
425
|
+
|
352
426
|
@listen_err = e
|
353
427
|
end
|
354
428
|
end
|
data/lib/ruby_process_cproxy.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require "monitor"
|
2
|
-
|
3
1
|
#This class is used to seamlessly use leaky classes without working through 'Ruby_process'.
|
4
2
|
#===Examples
|
5
3
|
# Ruby_process::Cproxy.run do |data|
|
@@ -13,7 +11,7 @@ require "monitor"
|
|
13
11
|
# raise "REXML shouldnt be defined?" if Kernel.const_defined?(:REXML)
|
14
12
|
class Ruby_process::Cproxy
|
15
13
|
#Lock is used to to create new Ruby-process-instances and not doing double-counts.
|
16
|
-
@@lock =
|
14
|
+
@@lock = Mutex.new
|
17
15
|
|
18
16
|
#Counts how many instances are using the Cproxy-module. This way it can be safely unset once no-body is using it again.
|
19
17
|
@@instances = 0
|
@@ -25,30 +23,37 @@ class Ruby_process::Cproxy
|
|
25
23
|
def self.run
|
26
24
|
#Increase count of instances that are using Cproxy and set the subproc-object if not already set.
|
27
25
|
@@lock.synchronize do
|
28
|
-
@@instances += 1
|
29
|
-
|
30
26
|
#Check if the sub-process is alive.
|
31
|
-
if @@subproc and !@@subproc.alive?
|
27
|
+
if @@subproc and (!@@subproc.alive? or @@subproc.destroyed?)
|
28
|
+
raise "Cant destroy sub-process because instances are running: '#{@@instances}'." if @@instances > 0
|
32
29
|
@@subproc.destroy
|
33
30
|
@@subproc = nil
|
34
31
|
end
|
35
32
|
|
36
33
|
#Start a new subprocess if none is defined and active.
|
37
34
|
if !@@subproc
|
38
|
-
|
39
|
-
|
35
|
+
subproc = Ruby_process.new(:title => "ruby_process_cproxy", :debug => false)
|
36
|
+
subproc.spawn_process
|
37
|
+
@@subproc = subproc
|
40
38
|
end
|
39
|
+
|
40
|
+
@@instances += 1
|
41
41
|
end
|
42
42
|
|
43
43
|
begin
|
44
44
|
yield(:subproc => @@subproc)
|
45
|
+
raise "'run'-caller destroyed sub-process. This shouldn't happen." if @@subproc.destroyed?
|
45
46
|
ensure
|
46
47
|
@@lock.synchronize do
|
47
48
|
@@instances -= 1
|
48
49
|
|
49
50
|
if @@instances <= 0
|
50
|
-
|
51
|
-
|
51
|
+
begin
|
52
|
+
@@subproc.destroy
|
53
|
+
ensure
|
54
|
+
@@subproc = nil
|
55
|
+
self.destroy_loaded_constants
|
56
|
+
end
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
@@ -60,6 +65,13 @@ class Ruby_process::Cproxy
|
|
60
65
|
return @@subproc
|
61
66
|
end
|
62
67
|
|
68
|
+
#Destroy all loaded sub-process-constants.
|
69
|
+
def self.destroy_loaded_constants
|
70
|
+
self.constants.each do |constant|
|
71
|
+
self.__send__(:remove_const, constant)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
63
75
|
#Creates the new constant under the 'Ruby_process::Cproxy'-namespace.
|
64
76
|
def self.const_missing(name)
|
65
77
|
Ruby_process::Cproxy.load_class(self, name) if !self.const_defined?(name)
|
@@ -81,20 +93,13 @@ class Ruby_process::Cproxy
|
|
81
93
|
def self.new(*args, &blk)
|
82
94
|
name_match = self.name.to_s.match(/^Ruby_process::Cproxy::(.+)$/)
|
83
95
|
class_name = name_match[1]
|
84
|
-
|
85
|
-
subproc = Ruby_process::Cproxy.subproc
|
86
|
-
obj = subproc.new(class_name, *args, &blk)
|
87
|
-
|
88
|
-
return obj
|
96
|
+
return Ruby_process::Cproxy.subproc.new(class_name, *args, &blk)
|
89
97
|
end
|
90
98
|
|
91
99
|
def self.method_missing(method_name, *args, &blk)
|
92
100
|
name_match = self.name.to_s.match(/^Ruby_process::Cproxy::(.+)$/)
|
93
101
|
class_name = name_match[1]
|
94
|
-
|
95
|
-
subproc = Ruby_process::Cproxy.subproc
|
96
|
-
|
97
|
-
return subproc.static(class_name, method_name, *args, &blk)
|
102
|
+
return Ruby_process::Cproxy.subproc.static(class_name, method_name, *args, &blk)
|
98
103
|
end
|
99
104
|
})
|
100
105
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
#This class handels the calling of methods on objects in the other process seamlessly.
|
2
2
|
class Ruby_process::Proxyobj
|
3
3
|
#Hash that contains various information about the proxyobj.
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :__rp_rp, :__rp_id, :__rp_pid
|
5
5
|
|
6
6
|
#Constructor. This should not be called manually but through a running 'Ruby_process'.
|
7
7
|
#===Examples
|
8
8
|
# proxy_obj = rp.new(:String, "Kasper") #=> <Ruby_process::Proxyobj>
|
9
9
|
# proxy_obj = rp.static(:File, :open, "/tmp/somefile") #=> <Ruby_process::Proxyobj>
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(rp, id, pid)
|
11
|
+
@__rp_rp, @__rp_id, @__rp_pid = rp, id, pid
|
12
12
|
end
|
13
13
|
|
14
14
|
#Returns the object as the real object transfered by using the marshal-lib.
|
@@ -16,14 +16,19 @@ class Ruby_process::Proxyobj
|
|
16
16
|
# str = rp.new(:String, "Kasper") #=> <Ruby_process::Proxyobj>
|
17
17
|
# str.__rp_marshal #=> "Kasper"
|
18
18
|
def __rp_marshal
|
19
|
-
return Marshal.load(@
|
19
|
+
return Marshal.load(@__rp_rp.send(:cmd => :obj_marshal, :id => @__rp_id))
|
20
|
+
end
|
21
|
+
|
22
|
+
#Unsets all data on the object.
|
23
|
+
def __rp_destroy
|
24
|
+
@__rp_id = nil, @__rp_rp = nil, @__rp_pid = nil
|
20
25
|
end
|
21
26
|
|
22
27
|
#Overwrite certain convert methods.
|
23
28
|
RUBY_METHODS = [:to_i, :to_s, :to_str, :to_f]
|
24
29
|
RUBY_METHODS.each do |method_name|
|
25
30
|
define_method(method_name) do |*args, &blk|
|
26
|
-
return @
|
31
|
+
return @__rp_rp.send(:cmd => :obj_method, :id => @__rp_id, :method => method_name, :args => args, &blk).__rp_marshal
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
@@ -41,10 +46,10 @@ class Ruby_process::Proxyobj
|
|
41
46
|
# length_int = str.length #=> <Ruby_process::Proxyobj::2>
|
42
47
|
# length_int.__rp_marshal #=> 6
|
43
48
|
def method_missing(method, *args, &block)
|
44
|
-
debug "Method-missing-args-before: #{args} (#{@
|
45
|
-
real_args = @
|
49
|
+
debug "Method-missing-args-before: #{args} (#{@__rp_pid})\n" if @debug
|
50
|
+
real_args = @__rp_rp.parse_args(args)
|
46
51
|
debug "Method-missing-args-after: #{real_args}\n" if @debug
|
47
52
|
|
48
|
-
return @
|
53
|
+
return @__rp_rp.send(:cmd => :obj_method, :id => @__rp_id, :method => method, :args => real_args, &block)
|
49
54
|
end
|
50
55
|
end
|
data/ruby_process.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruby_process}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.8"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kasper Johansen"]
|
12
|
-
s.date = %q{2012-10-
|
12
|
+
s.date = %q{2012-10-24}
|
13
13
|
s.description = %q{A framework for spawning and communicating with other Ruby-processes}
|
14
14
|
s.email = %q{k@spernj.org}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -42,6 +42,7 @@ Gem::Specification.new do |s|
|
|
42
42
|
"scripts/ruby_process_script.rb",
|
43
43
|
"spec/cproxy_spec.rb",
|
44
44
|
"spec/hard_load_spec.rb",
|
45
|
+
"spec/leaks_spec.rb",
|
45
46
|
"spec/ruby_process_spec.rb",
|
46
47
|
"spec/spec_helper.rb"
|
47
48
|
]
|
data/spec/cproxy_spec.rb
CHANGED
@@ -1,6 +1,26 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe "RubyProcess" do
|
4
|
+
it "should be able to do quick in-and-outs without leaking" do
|
5
|
+
ts = []
|
6
|
+
|
7
|
+
1.upto(2) do |tcount|
|
8
|
+
ts << Thread.new do
|
9
|
+
1.upto(10) do
|
10
|
+
Ruby_process::Cproxy.run do |data|
|
11
|
+
sp = data[:subproc]
|
12
|
+
str = sp.new(:String, "Wee")
|
13
|
+
res1 = str.include?("Kasper")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ts.each do |thread|
|
20
|
+
thread.join
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
4
24
|
it "should be able to do basic stuff" do
|
5
25
|
require "stringio"
|
6
26
|
|
@@ -18,4 +38,64 @@ describe "RubyProcess" do
|
|
18
38
|
raise "Expected strio to contain '<test/>' but it didnt: '#{strio.string}'." if strio.string != "<test/>"
|
19
39
|
end
|
20
40
|
end
|
41
|
+
|
42
|
+
it "should be able to do multiple calls at once" do
|
43
|
+
ts = []
|
44
|
+
|
45
|
+
0.upto(9) do |tcount|
|
46
|
+
ts << Thread.new do
|
47
|
+
Ruby_process::Cproxy.run do |data|
|
48
|
+
sp = data[:subproc]
|
49
|
+
sp.new(:String, "Wee")
|
50
|
+
|
51
|
+
1.upto(250) do
|
52
|
+
str = sp.new(:String, "Kasper Johansen")
|
53
|
+
|
54
|
+
res1 = str.include?("Kasper")
|
55
|
+
str << " More"
|
56
|
+
|
57
|
+
res2 = str.include?("Johansen")
|
58
|
+
str << " Even more"
|
59
|
+
|
60
|
+
res3 = str.include?("Christina")
|
61
|
+
str << " Much more"
|
62
|
+
|
63
|
+
raise "Expected res1 to be true but it wasnt: '#{res1}'." if res1 != true
|
64
|
+
raise "Expected res2 to be true but it wasnt: '#{res2}'." if res2 != true
|
65
|
+
raise "Expected res3 to be false but it wasnt: '#{res3}'." if res3 != false
|
66
|
+
|
67
|
+
#print tcount
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
count = 0
|
74
|
+
ts.each do |t|
|
75
|
+
count += 1
|
76
|
+
#puts "Thread #{count}"
|
77
|
+
t.join
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not leak" do
|
82
|
+
str = "kasper"
|
83
|
+
str = nil
|
84
|
+
sleep 0.1
|
85
|
+
GC.start
|
86
|
+
sleep 0.1
|
87
|
+
|
88
|
+
count_objs = 0
|
89
|
+
ObjectSpace.each_object(Ruby_process) do |obj|
|
90
|
+
count_objs += 1
|
91
|
+
end
|
92
|
+
|
93
|
+
count_proxy_objs = 0
|
94
|
+
ObjectSpace.each_object(Ruby_process::Proxyobj) do |obj|
|
95
|
+
count_proxy_objs += 1
|
96
|
+
end
|
97
|
+
|
98
|
+
raise "Expected 1 or less 'Ruby_process' to be left but it wasnt like that: #{count_objs} (proxy objects: #{count_proxy_objs})" if count_objs > 1
|
99
|
+
raise "Expected 0 constants to be left on cproxy." if !Ruby_process::Cproxy.constants.empty?
|
100
|
+
end
|
21
101
|
end
|
data/spec/hard_load_spec.rb
CHANGED
data/spec/leaks_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "RubyProcess" do
|
4
|
+
it "should be able to clean up after itself when timeout" do
|
5
|
+
require "timeout"
|
6
|
+
|
7
|
+
Ruby_process::Cproxy.run do |data|
|
8
|
+
sp = data[:subproc]
|
9
|
+
|
10
|
+
begin
|
11
|
+
Timeout.timeout(1) do
|
12
|
+
sp.static(:Object, :sleep, 2)
|
13
|
+
end
|
14
|
+
|
15
|
+
raise "Expected timeout to be raised."
|
16
|
+
rescue Timeout::Error
|
17
|
+
#ignore.
|
18
|
+
end
|
19
|
+
|
20
|
+
answers = sp.instance_variable_get(:@answers)
|
21
|
+
raise "'answers'-variable should be empty but wasnt: '#{answers}'." if !answers.empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/ruby_process_spec.rb
CHANGED
@@ -20,11 +20,18 @@ describe "RubyProcess" do
|
|
20
20
|
|
21
21
|
it "should be able to pass proxy-objects as arguments." do
|
22
22
|
str = $rp.new(:String, "/tmp/somefile")
|
23
|
+
thread_id = Thread.current.__id__
|
24
|
+
write_called = false
|
25
|
+
|
23
26
|
$rp.static(:File, :open, str, "w") do |fp|
|
27
|
+
raise "Expected 'thread_id' to be the same but it wasnt: '#{thread_id}', '#{Thread.current.__id__}'." if thread_id != Thread.current.__id__
|
24
28
|
fp.write("Test!")
|
29
|
+
write_called = true
|
25
30
|
end
|
26
31
|
|
27
|
-
raise "
|
32
|
+
raise "Expected 'write' on file-pointer to be called, but it wasnt." if !write_called
|
33
|
+
read = File.read(str.__rp_marshal)
|
34
|
+
raise "Unexpected content of file: '#{read}'." if read != "Test!"
|
28
35
|
end
|
29
36
|
|
30
37
|
it "should be able to write files" do
|
@@ -142,6 +149,27 @@ describe "RubyProcess" do
|
|
142
149
|
raise "Unexpected: #{res}" if res != 10
|
143
150
|
end
|
144
151
|
|
152
|
+
it "should clean itself" do
|
153
|
+
$rp.garbage_collect
|
154
|
+
GC.start
|
155
|
+
$rp.flush_finalized
|
156
|
+
GC.start
|
157
|
+
$rp.flush_finalized
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should be clean and not leaking" do
|
161
|
+
GC.start
|
162
|
+
$rp.flush_finalized
|
163
|
+
GC.start
|
164
|
+
$rp.flush_finalized
|
165
|
+
|
166
|
+
answers = $rp.instance_variable_get(:@answers)
|
167
|
+
raise "Expected 0 answers to be present: #{answers}" if !answers.empty?
|
168
|
+
|
169
|
+
objects = $rp.instance_variable_get(:@objects)
|
170
|
+
raise "Expected 0 objects to be present: #{objects}" if !objects.empty?
|
171
|
+
end
|
172
|
+
|
145
173
|
it "should be able to destroy itself" do
|
146
174
|
$rp.destroy
|
147
175
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_process
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-24 00:00:00.000000000 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: wref
|
17
|
-
requirement: &
|
17
|
+
requirement: &12893660 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *12893660
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: tsafe
|
28
|
-
requirement: &
|
28
|
+
requirement: &12892340 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *12892340
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: rspec
|
39
|
-
requirement: &
|
39
|
+
requirement: &12890800 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 2.8.0
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *12890800
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: rdoc
|
50
|
-
requirement: &
|
50
|
+
requirement: &12889640 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ~>
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '3.12'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *12889640
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: bundler
|
61
|
-
requirement: &
|
61
|
+
requirement: &12888460 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: 1.0.0
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *12888460
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: jeweler
|
72
|
-
requirement: &
|
72
|
+
requirement: &12887260 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ~>
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: 1.8.3
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *12887260
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: rcov
|
83
|
-
requirement: &
|
83
|
+
requirement: &12886420 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ! '>='
|
@@ -88,7 +88,7 @@ dependencies:
|
|
88
88
|
version: '0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *12886420
|
92
92
|
description: A framework for spawning and communicating with other Ruby-processes
|
93
93
|
email: k@spernj.org
|
94
94
|
executables: []
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- scripts/ruby_process_script.rb
|
123
123
|
- spec/cproxy_spec.rb
|
124
124
|
- spec/hard_load_spec.rb
|
125
|
+
- spec/leaks_spec.rb
|
125
126
|
- spec/ruby_process_spec.rb
|
126
127
|
- spec/spec_helper.rb
|
127
128
|
has_rdoc: true
|
@@ -140,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
141
|
version: '0'
|
141
142
|
segments:
|
142
143
|
- 0
|
143
|
-
hash:
|
144
|
+
hash: -2651984765676101078
|
144
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
146
|
none: false
|
146
147
|
requirements:
|