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 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: