ruby_process 0.0.7 → 0.0.8
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.
- 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:
|