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



31
32
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
# File 'lib/ruby_process.rb', line 31

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.



8
9
10
# File 'lib/ruby_process.rb', line 8

def finalize_count
  @finalize_count
end

Class Method Details

.const_missing(name) ⇒ Object

Autoloader for subclasses.



20
21
22
23
24
# File 'lib/ruby_process.rb', line 20

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

#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.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ruby_process.rb', line 136

def destroy
  begin
    send(:cmd => :exit) if alive?
  rescue => e
    raise e if e.message != "Process is dead." and e.message != "Not listening."
  end
  
  #Make main kill it and make sure its dead...
  begin
    if @main and @pid
      Process.kill("TERM", @pid)
      Process.kill(9, @pid)
    end
  rescue Errno::ESRCH
    #Process is already dead - ignore.
  end
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)


155
156
157
158
159
160
161
162
163
# File 'lib/ruby_process.rb', line 155

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.



127
128
129
130
131
132
133
# File 'lib/ruby_process.rb', line 127

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.



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
196
197
198
199
200
201
202
# File 'lib/ruby_process.rb', line 166

def send(obj, &block)
  raise "Ruby-process is dead." if !alive?
  
  #Parse block.
  if block
    block_proxy_res = self.send(:cmd => :spawn_proxy_block, :id => block.__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]
    @objects[block_proxy_res[:id]] = block
    ObjectSpace.define_finalizer(block_proxy_res, self.method(:proxyobj_finalizer))
    obj[:block] = {
      :id => block_proxy_res[:id],
      :arity => block.arity
    }
  end
  
  flush_finalized if obj[:cmd] != :flush_finalized
  
  #Sync ID stuff so they dont get mixed up.
  id = nil
  @send_mutex.synchronize do
    id = @send_count
    @send_count += 1
  end
  
  debug "Sending(#{id}): #{obj}\n" if @debug
  line = Base64.strict_encode64(Marshal.dump(
    :id => id,
    :type => :send,
    :obj => obj
  ))
  @io_out.puts(line)
  sleep 0.001
  return answer_read(id)
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



70
71
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
# File 'lib/ruby_process.rb', line 70

def spawn_process(args = nil)
  #Used for printing debug-stuff.
  @main = true
  
  if args and args[:exec]
    cmd = "#{args[:exec]}"
  else
    cmd = "ruby"
  end
  
  cmd << " \"#{File.realpath(File.dirname(__FILE__))}/../scripts/ruby_process_script.rb\" --pid=#{@my_pid}"
  cmd << " --debug" if @args[:debug]
  
  #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 nil
  else
    return self
  end
end