Class: Knj::Process_meta

Inherits:
Object show all
Defined in:
lib/knj/process_meta.rb

Overview

This class can spawn another Ruby-process and manipulate it to create objects, evaluate code, create proxy-objects and other stuff in that process.

Examples

This will create another Ruby-process, spawn an integer with the value of 5, run upto(10) and return each block to the current block. In the end the subprocess is terminated. Knj::Process_meta.new do |subproc|

proxy_int = subproc.new(:Integer, 5)
proxy_int.upto(10) do |i|
  print "Number: #{i}\n"
end

end

Defined Under Namespace

Classes: Proxy_obj

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Process_meta

Examples

Knj::Process_meta.new(“id” => “my_subproc”) #Will make this ID be shown in the command, so you can recocknize it from “ps aux”. Knj::Process_meta.new(“exec_path” => “ruby1.9.1”) #If you want a certain Ruby-command to be used when starting the subprocess, instead of detecting the current one. Knj::Process_meta.new(“debug” => true, “debug_err” => true) #Enables various debug-messages to be printed.



20
21
22
23
24
25
26
27
28
29
30
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/knj/process_meta.rb', line 20

def initialize(args = {})
  @args = args
  @objects = {}
  
  #These variables are used to free memory in the subprocess, by using ObjectSpace#define_finalizer. The Mutex is the avoid problems when writing to the finalize-array multithreadded.
  @finalize = []
  @finalize_mutex = Mutex.new
  
  if @args["exec_path"]
    exec_path = @args["exec_path"]
  else
    exec_path = Knj::Os.executed_executable
  end
  
  exec_file = "#{File.dirname(__FILE__)}/scripts/process_meta_exec.rb"
  
  if args["id"]
    id = args["id"]
  else
    id = caller[0].to_s.strip
  end
  
  cmd = "#{exec_path} \"#{exec_file}\" #{Knj::Strings.unixsafe(id)}"
  
  if RUBY_ENGINE == "jruby"
    pid, @stdin, @stdout, @stderr = IO.popen4("#{exec_path} --#{RUBY_VERSION[0, 3]} \"#{exec_file}\" \"#{id}\"")
  else
    require "open3"
    @stdin, @stdout, @stderr = Open3.popen3(cmd)
  end
  
  @stdout.sync = true
  @stdin.sync = true
  
  args = {
    :out => @stdin,
    :in => @stdout,
    :listen => true,
    :debug => @args["debug"]
  }
  
  if @args["debug"] or @args["debug_err"]
    args[:err] = @stderr
    args[:on_err] = proc{|line|
      $stderr.print "stderr: #{line}"
    }
  end
  
  #Wait for process to start and check that it is returning the expected output.
  start_line = @stdout.gets
  raise "Expected startline from process to be 'process_meta_started' but got: '#{start_line}'." if start_line != "process_meta_started\n"
  
  @process = Knj::Process.new(args)
  
  res = @process.send("obj" => {"type" => "process_data"})
  raise "Unexpected process-data: '#{res}'." if !res.is_a?(Hash) or res["type"] != "process_data_success"
  @pid = res["pid"]
  
  #If block is given then run block and destroy self.
  if block_given?
    begin
      yield(self)
    ensure
      self.destroy
    end
  end
end

Instance Attribute Details

#pidObject (readonly)

Returns the value of attribute pid.



14
15
16
# File 'lib/knj/process_meta.rb', line 14

def pid
  @pid
end

#processObject (readonly)

Returns the value of attribute process.



14
15
16
# File 'lib/knj/process_meta.rb', line 14

def process
  @process
end

Class Method Details

.args_parse(args) ⇒ Object

Parses the arguments given. Proxy-object-arguments will be their natural objects in the subprocess.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/knj/process_meta.rb', line 120

def self.args_parse(args)
  if args.is_a?(Array)
    newargs = []
    args.each do |val|
      if val.is_a?(Knj::Process_meta::Proxy_obj)
        newargs << {"type" => "proxy_obj", "var_name" => val._process_meta_args[:name]}
      else
        newargs << Knj::Process_meta.args_parse(val)
      end
    end
    
    return newargs
  elsif args.is_a?(Hash)
    newargs = {}
    args.each do |key, val|
      if key.is_a?(Knj::Process_meta::Proxy_obj)
        key = {"type" => "proxy_obj", "var_name" => key._process_meta_args[:name]}
      else
        key = Knj::Process_meta.args_parse(key)
      end
      
      if val.is_a?(Knj::Process_meta::Proxy_obj)
        val = {"type" => "proxy_obj", "var_name" => val._process_meta_args[:name]}
      else
        val = Knj::Process_meta.args_parse(val)
      end
      
      newargs[key] = val
    end
    
    return newargs
  else
    return args
  end
end

.args_parse_back(args, objects) ⇒ Object

Parses the special hashes to reflect the natural objects instead of proxy-objects.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/knj/process_meta.rb', line 157

def self.args_parse_back(args, objects)
  if args.is_a?(Array)
    newargs = []
    args.each do |val|
      newargs << Knj::Process_meta.args_parse_back(val, objects)
    end
    
    return newargs
  elsif args.is_a?(Hash) and args["type"] == "proxy_obj" and args.key?("var_name")
    raise "No object by that var-name: '#{args["var_name"]}' in '#{objects}'." if !objects.key?(args["var_name"])
    return objects[args["var_name"]]
  elsif args.is_a?(Hash)
    newargs = {}
    args.each do |key, val|
      newargs[Knj::Process_meta.args_parse_back(key, objects)] = Knj::Process_meta.args_parse_back(val, objects)
    end
    
    return newargs
  else
    return args
  end
end

Instance Method Details

#call_object(args, &block) ⇒ Object

Calls a method on an object and returns the result.



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/knj/process_meta.rb', line 259

def call_object(args, &block)
  if args.key?("capture_return")
    capture_return = args["capture_return"]
  else
    capture_return = true
  end
  
  if args["buffered"]
    type = "call_object_buffered"
  else
    type = "call_object_block"
  end
  
  res = @process.send(
    {
      "buffer_use" => args["buffer_use"],
      "obj" => {
        "type" => type,
        "var_name" => args["var_name"],
        "method_name" => args["method_name"],
        "capture_return" => capture_return,
        "args" => Knj::Process_meta.args_parse(args["args"])
      }
    },
    &block
  )
  
  return res["result"] if res.is_a?(Hash) and res["type"] == "call_object_success"
  raise "Unknown result: '#{res}'."
end

#check_finalizersObject

Flushes all finalized objects on the process-side.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/knj/process_meta.rb', line 96

def check_finalizers
  return nil if @finalize.empty?
  
  finalize = nil
  @finalize_mutex.synchronize do
    finalize = @finalize
    @finalize = []
    
    begin
      @process.send("obj" => {
        "type" => "unset_multiple",
        "var_names" => finalize
      })
    rescue => e
      if e.message.to_s.index("Var-name didnt exist when trying to unset:")
        #ignore.
      else
        raise e
      end
    end
  end
end

#destroyObject

Destroyes the project and unsets all variables on the Process_meta-object.



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/knj/process_meta.rb', line 365

def destroy
  @process.send("obj" => {"type" => "exit"})
  @err_thread.kill if @err_thread
  @process.destroy
  
  begin
    Process.kill("TERM", @pid)
  rescue Errno::ESRCH
    #Process is already dead - ignore.
  end
  
  begin
    sleep 0.1
    process_exists = Knj::Unix_proc.list("pids" => [@pid])
    raise "Process exists." if !process_exists.empty?
  rescue => e
    raise e if e.message != "Process exists."
    
    begin
      Process.kill(9, pid) if process_exists
    rescue Errno::ESRCH => e
      raise e if e.message.index("No such process") == nil
    end
    
    #$stderr.print "Try to kill again...\n"
    #retry
  end
  
  @process = nil
  @stdin = nil
  @stdout = nil
  @stderr = nil
  @objects = nil
  @args = nil
end

#new(class_name, *args, &block) ⇒ Object

Spawns a new object in the subprocess by that classname, with those arguments and with that block.



215
216
217
218
219
220
221
# File 'lib/knj/process_meta.rb', line 215

def new(class_name, *args, &block)
  #We need to check finalizers first, so we wont accidently reuse an ID, which will then be unset in the process.
  self.check_finalizers
  
  #Spawn and return the object.
  return self.spawn_object(class_name, nil, *args, &block)
end

#proxy_finalizer(id) ⇒ Object

Finalizer for proxy-objects. Used for unsetting objects on the process-side.



89
90
91
92
93
# File 'lib/knj/process_meta.rb', line 89

def proxy_finalizer(id)
  @finalize_mutex.synchronize do
    @finalize << id
  end
end

#proxy_from_call(proxy_obj_to_call, method_name, *args) ⇒ Object

Returns a proxy-object to a object given from a call.



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/knj/process_meta.rb', line 325

def proxy_from_call(proxy_obj_to_call, method_name, *args)
  proxy_obj = Knj::Process_meta::Proxy_obj.new(:process_meta => self)
  var_name = proxy_obj.__id__
  proxy_obj._process_meta_args[:name] = var_name
  
  res = @process.send(
    "obj" => {
      "type" => "proxy_from_call",
      "proxy_obj" => proxy_obj_to_call.__id__,
      "method_name" => method_name,
      "var_name" => var_name,
      "args" => Knj::Process_meta.args_parse(args)
    }
  )
  
  return proxy_obj
end

#proxy_from_eval(eval_str) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/knj/process_meta.rb', line 290

def proxy_from_eval(eval_str)
  proxy_obj = Knj::Process_meta::Proxy_obj.new(:process_meta => self)
  var_name = proxy_obj.__id__
  proxy_obj._process_meta_args[:name] = var_name
  
  res = @process.send(
    "obj" => {
      "type" => "proxy_from_eval",
      "str" => eval_str,
      "var_name" => var_name
    }
  )
  
  return proxy_obj
end

#proxy_from_static(class_name, method_name, *args) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/knj/process_meta.rb', line 306

def proxy_from_static(class_name, method_name, *args)
  proxy_obj = Knj::Process_meta::Proxy_obj.new(:process_meta => self)
  var_name = proxy_obj.__id__
  proxy_obj._process_meta_args[:name] = var_name
  
  res = @process.send(
    "obj" => {
      "type" => "proxy_from_static",
      "const" => class_name,
      "method_name" => method_name,
      "var_name" => var_name,
      "args" => Knj::Process_meta.args_parse(args)
    }
  )
  
  return proxy_obj
end

#proxy_has?(var_name) ⇒ Boolean

Returns true if the given name exists in the subprocess-objects-hash.

Returns:

  • (Boolean)


344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/knj/process_meta.rb', line 344

def proxy_has?(var_name)
  self.check_finalizers
  
  begin
    res = @process.send(
      "obj" => {
        "type" => "call_object_block",
        "var_name" => var_name,
        "method_name" => "__id__",
        "args" => []
      }
    )
  rescue => e
    return false if e.message.to_s.match(/^No object by that name/)
    raise e
  end
  
  return true
end

#spawn_object(class_name, var_name = nil, *args, &block) ⇒ Object

Spawns a new object in the subprocess and returns a proxy-variable for that subprocess-object.



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/knj/process_meta.rb', line 224

def spawn_object(class_name, var_name = nil, *args, &block)
  proxy_obj = Knj::Process_meta::Proxy_obj.new(:process_meta => self, :name => var_name)
  
  if var_name == nil
    var_name = proxy_obj.__id__
    proxy_obj._process_meta_args[:name] = var_name
  end
  
  res = @process.send(
    {
      "obj" => {
        "type" => "spawn_object",
        "class_name" => class_name,
        "var_name" => var_name,
        "args" => Knj::Process_meta.args_parse(args)
      }
    },
    &block
  )
  
  return proxy_obj
end

#static(const, method_name, *args, &block) ⇒ Object

Executes a static method on a class in the sub-process.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/knj/process_meta.rb', line 198

def static(const, method_name, *args, &block)
  res = @process.send(
    "obj" => {
      "type" => "static",
      "const" => const,
      "method_name" => method_name,
      "capture_return" => true,
      "args" => Knj::Process_meta.args_parse(args),
    },
    &block
  )
  
  return res["result"] if res["type"] == "call_const_success"
  raise "Unknown result: '#{res}'."
end

#static_noret(const, method_name, *args, &block) ⇒ Object

Executes a static call in the subprocess but does not capture or return the result. Useful if the static method returns an object that would load a library after being un-marshaled.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/knj/process_meta.rb', line 181

def static_noret(const, method_name, *args, &block)
  res = @process.send(
    "obj" => {
      "type" => "static",
      "const" => const,
      "method_name" => method_name,
      "capture_return" => false,
      "args" => Knj::Process_meta.args_parse(args),
    },
    &block
  )
  
  return res["result"] if res["type"] == "call_const_success"
  raise "Unknown result: '#{res}'."
end

#str_eval(str) ⇒ Object

Evaluates a string in the sub-process.



248
249
250
251
252
253
254
255
256
# File 'lib/knj/process_meta.rb', line 248

def str_eval(str)
  res = @process.send("obj" => {
    "type" => "str_eval",
    "str" => str
  })
  
  return res["result"] if res.is_a?(Hash) and res["type"] == "call_eval_success"
  return "Unknown result: '#{res}'."
end