Class: Ruby_process

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_process.rb

Overview

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.

Defined Under Namespace

Classes: Cproxy, Proxyobj

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Ruby_process

Constructor.

Examples

Ruby_process.new.spawn_process do |rp|

str = rp.new(:String, "Kasper")

end



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ruby_process.rb', line 33

def initialize(args = {})
  @args = args
  @debug = @args[:debug]
  @pid = @args[:pid]
  
  #These classes are allowed in call-arguments. They can be marshalled without any errors.
  @args_allowed = [FalseClass, Fixnum, Integer, NilClass, String, Symbol, TrueClass]
  
  #Set IO variables if given.
  @io_out = Tsafe::Proxy.new(:obj => @args[:out]) if @args[:out]
  @io_in = @args[:in] if @args[:in]
  @io_err = @args[:err] if @args[:err]
  
  #This hash holds answers coming from the subprocess.
  @answers = Tsafe::MonHash.new
  
  #This hash holds objects that are referenced in the process.
  @objects = Tsafe::MonHash.new
  
  #This weak-map holds all proxy objects.
  @proxy_objs = Wref_map.new
  @proxy_objs_ids = Tsafe::MonHash.new
  @proxy_objs_unsets = Tsafe::MonArray.new
  @flush_mutex = Mutex.new
  @finalize_count = 0
  
  #Send ID is used to identify the correct answers.
  @send_mutex = Mutex.new
  @send_count = 0
  
  #The PID is used to know which process proxy-objects belongs to.
  @my_pid = Process.pid
end

Instance Attribute Details

#finalize_countObject (readonly)

Returns the value of attribute finalize_count.



10
11
12
# File 'lib/ruby_process.rb', line 10

def finalize_count
  @finalize_count
end

#pidObject (readonly)

Returns the value of attribute pid.



10
11
12
# File 'lib/ruby_process.rb', line 10

def pid
  @pid
end

Class Method Details

.const_missing(name) ⇒ Object

Autoloader for subclasses.



22
23
24
25
26
# File 'lib/ruby_process.rb', line 22

def self.const_missing(name)
  require "#{File.realpath(File.dirname(__FILE__))}/ruby_process_#{name.to_s.downcase}.rb"
  raise "Still not defined: '#{name}'." if !self.const_defined?(name)
  return self.const_get(name)
end

Instance Method Details

#alive?Boolean

Returns true if the child process is still running. Otherwise false.

Returns:

  • (Boolean)


260
261
262
263
264
265
266
267
# File 'lib/ruby_process.rb', line 260

def alive?
  begin
    alive_check!
    return true
  rescue
    return false
  end
end

#destroyObject

First tries to make the sub-process exit gently. Then kills it with “TERM” and 9 afterwards to make sure its dead. If ‘spawn_process’ is given a block, this method is automatically ensured after the block is run.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/ruby_process.rb', line 143

def destroy
  return nil if self.destroyed?
  
  debug "Destroying Ruby-process (#{caller}).\n" if @debug
  pid = @pid
  tries = 0
  
  #Make main kill it and make sure its dead...
  begin
    if @main and @pid
      tries += 1
      Process.kill("TERM", pid) rescue Errno::ESRCH
      
      #Ensure subprocess is dead.
      begin
        Timeout.timeout(1) do
          sleep 0.01
          
          loop do
            begin
              Process.getpgid(pid)
              alive = true
            rescue Errno::ESRCH
              alive = false
            end
            
            break if !alive
          end
        end
      rescue Timeout::Error
        Process.kill(9, pid) rescue Errno::ESRCH
        retry
      end
    end
  rescue Errno::ESRCH
    #Process is already dead - ignore.
  ensure
    @pid = nil
    
    @io_out.close if @io_out && !@io_out.closed?
    @io_out = nil
    
    @io_in.close if @io_in && !@io_in.closed?
    @io_in = nil
    
    @io_err if @io_err && !@io_err.closed?
    @io_err = nil
    
    @main = nil
  end
  
  return nil
end

#destroyed?Boolean

Returns true if the Ruby process has been destroyed.

Returns:

  • (Boolean)


198
199
200
201
# File 'lib/ruby_process.rb', line 198

def destroyed?
  return true if !@pid and !@io_out and !@io_in and !@io_err and @main == nil
  return false
end

#joinObject

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.

Raises:

  • (@listen_err)


204
205
206
207
208
209
210
211
212
# File 'lib/ruby_process.rb', line 204

def join
  debug "Joining listen-thread.\n" if @debug
  @thr_listen.join if @thr_listen
  raise @listen_err if @listen_err
  
  debug "Joining error-thread.\n" if @debug
  @thr_err.join if @thr_join
  raise @listen_err_err if @listen_err_err
end

#listenObject

Starts listening on the given IO’s. It is useally not needed to call this method manually.



134
135
136
137
138
139
140
# File 'lib/ruby_process.rb', line 134

def listen
  #Start listening for input.
  start_listen
  
  #Start listening for errors.
  start_listen_errors
end

#send(obj, &block) ⇒ Object

Sends a command to the other process. This should not be called manually, but is used by various other parts of the framework.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/ruby_process.rb', line 215

def send(obj, &block)
  alive_check!
  
  #Sync ID stuff so they dont get mixed up.
  id = nil
  @send_mutex.synchronize do
    id = @send_count
    @send_count += 1
  end
  
  #Parse block.
  if block
    block_proxy_res = self.send(:cmd => :spawn_proxy_block, :id => block.__id__, :answer_id => id)
    block_proxy_res_id = block_proxy_res[:id]
    raise "No block ID was returned?" if !block_proxy_res_id
    raise "Invalid block-ID: '#{block_proxy_res_id}'." if block_proxy_res_id.to_i <= 0
    @proxy_objs[block_proxy_res_id] = block
    @proxy_objs_ids[block.__id__] = block_proxy_res_id
    ObjectSpace.define_finalizer(block, self.method(:proxyobj_finalizer))
    obj[:block] = {
      :id => block_proxy_res_id,
      :arity => block.arity
    }
  end
  
  flush_finalized if obj[:cmd] != :flush_finalized
  
  debug "Sending(#{id}): #{obj}\n" if @debug
  line = Base64.strict_encode64(Marshal.dump(
    :id => id,
    :type => :send,
    :obj => obj
  ))
  
  begin
    @answers[id] = Queue.new
    @io_out.puts(line)
    return answer_read(id)
  ensure
    #Be sure that the answer is actually deleted to avoid memory-leaking.
    @answers.delete(id)
  end
end

#spawn_process(args = nil) ⇒ Object

Spawns a new process in the same Ruby-inteterpeter as the current one.

Examples

rp = Ruby_process.new.spawn_process rp.str_eval(“return 10”).__rp_marshal #=> 10 rp.destroy



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/ruby_process.rb', line 72

def spawn_process(args = nil)
  #Used for printing debug-stuff.
  @main = true
  
  if args and args[:exec]
    cmd = "#{args[:exec]}"
  else
    if !ENV["rvm_ruby_string"].to_s.empty?
      cmd = "#{ENV["rvm_ruby_string"]}"
    else
      cmd = "ruby"
    end
  end
  
  cmd << " \"#{File.realpath(File.dirname(__FILE__))}/../scripts/ruby_process_script.rb\" --pid=#{@my_pid}"
  cmd << " --debug" if @args[:debug]
  cmd << " \"--title=#{@args[:title]}\"" if !@args[:title].to_s.strip.empty?
  
  #Start process and set IO variables.
  require "open3"
  @io_out, @io_in, @io_err = Open3.popen3(cmd)
  @io_out = Tsafe::Proxy.new(:obj => @io_out)
  @io_out.sync = true
  @io_in.sync = true
  @io_err.sync = true
  
  started = false
  @io_in.each_line do |str|
    if str == "ruby_process_started\n"
      started = true
      break
    end
    
    debug "Ruby-process-debug from stdout before started: '#{str}'\n" if @debug
  end
  
  raise "Ruby-sub-process couldnt start: '#{@io_err.read}'." if !started
  self.listen
  
  #Start by getting the PID of the process.
  begin
    @pid = self.static(:Process, :pid).__rp_marshal
    raise "Unexpected PID: '#{@pid}'." if !@pid.is_a?(Fixnum) and !@pid.is_a?(Integer)
  rescue => e
    self.destroy
    raise e
  end
  
  if block_given?
    begin
      yield(self)
    ensure
      self.destroy
    end
    
    return self
  else
    return self
  end
end