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 CHANGED
@@ -1 +1 @@
1
- 0.0.7
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 = @objects[obj[:block_id]]
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
- block_ele.call(*read_args(obj[:args]))
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
@@ -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.args[:pid]}).\n" if @debug
35
- return {:type => :proxy_obj, :id => obj.args[:id], :pid => obj.args[:pid]}
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
- begin
143
- send(:cmd => :exit) if alive?
144
- rescue => e
145
- raise e if e.message != "Process is dead." and e.message != "Not listening."
146
- end
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
- Process.kill("TERM", @pid)
152
- Process.kill(9, @pid)
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
- raise "No block ID was returned?" if !block_proxy_res[:id]
178
- raise "Invalid block-ID: '#{block_proxy_res[:id]}'." if block_proxy_res[:id].to_i <= 0
179
- @proxy_objs[block_proxy_res[:id]] = block
180
- @proxy_objs_ids[block.__id__] = block_proxy_res[:id]
181
- @objects[block_proxy_res[:id]] = block
182
- ObjectSpace.define_finalizer(block_proxy_res, self.method(:proxyobj_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 => block_proxy_res[: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
- return answer_read(id)
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
- self.alive_check!
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(:rp => self, :id => id, :pid => pid)
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
- raise "No object by that ID: '#{id}' (#{@objects})." if !obj
264
- return obj
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
- debug "Waiting for answer #{id}\n" if @debug
285
- answer = @answers[id].pop
286
-
287
- debug "Returning answer #{id}\n" if @debug
288
- @answers.delete(id)
289
-
290
- if answer.is_a?(Hash) and answer[:type] == :error and answer.key?(:class) and answer.key?(:msg) and answer.key?(:bt)
291
- begin
292
- raise "#{answer[:class]}: #{answer[:msg]}"
293
- rescue => e
294
- bt = []
295
- answer[:bt].each do |btline|
296
- bt << "(#{@pid}): #{btline}"
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
- bt += e.backtrace
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
- return answer
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.length <= 0
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
- if obj[:type] == :send
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 !obj.key?(:id)
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 => obj[:id], :answer => res))
406
+ data = Base64.strict_encode64(Marshal.dump(:type => :answer, :id => id, :answer => res))
342
407
  @io_out.puts(data)
343
408
  end
344
- elsif obj[:type] == :answer and id = obj[:id].to_i
345
- debug "Answer #{id} saved.\n" if @debug
346
- @answers[id] << obj[:answer]
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
@@ -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 = Monitor.new
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
- @@subproc = Ruby_process.new(:title => "cproxy", :debug => false)
39
- @@subproc.spawn_process
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
- @@subproc.destroy
51
- @@subproc = nil
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 :args
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(args)
11
- @args = args
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(@args[:rp].send(:cmd => :obj_marshal, :id => @args[:id]))
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 @args[:rp].send(:cmd => :obj_method, :id => @args[:id], :method => method_name, :args => args, &blk).__rp_marshal
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} (#{@my_pid})\n" if @debug
45
- real_args = @args[:rp].parse_args(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 @args[:rp].send(:cmd => :obj_method, :id => @args[:id], :method => method, :args => real_args, &block)
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.7"
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-16}
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
@@ -26,7 +26,7 @@ describe "RubyProcess" do
26
26
  raise "Expected res2 to be true but it wasnt: '#{res2}'." if res2 != true
27
27
  raise "Expected res3 to be false but it wasnt: '#{res3}'." if res3 != false
28
28
 
29
- print "."
29
+ #print "."
30
30
  end
31
31
  end
32
32
  end
@@ -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
@@ -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 "Unexpected" if File.read(str.__rp_marshal) != "Test!"
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.7
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-16 00:00:00.000000000 +02:00
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: &19833520 !ruby/object:Gem::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: *19833520
25
+ version_requirements: *12893660
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: tsafe
28
- requirement: &19832660 !ruby/object:Gem::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: *19832660
36
+ version_requirements: *12892340
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: rspec
39
- requirement: &19831800 !ruby/object:Gem::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: *19831800
47
+ version_requirements: *12890800
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: rdoc
50
- requirement: &19830660 !ruby/object:Gem::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: *19830660
58
+ version_requirements: *12889640
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: bundler
61
- requirement: &19828180 !ruby/object:Gem::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: *19828180
69
+ version_requirements: *12888460
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: jeweler
72
- requirement: &19827260 !ruby/object:Gem::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: *19827260
80
+ version_requirements: *12887260
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: rcov
83
- requirement: &19826400 !ruby/object:Gem::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: *19826400
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: 1714435591026867569
144
+ hash: -2651984765676101078
144
145
  required_rubygems_version: !ruby/object:Gem::Requirement
145
146
  none: false
146
147
  requirements: