ruby_process 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "wref"
7
+ gem "tsafe"
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "rspec", "~> 2.8.0"
13
+ gem "rdoc", "~> 3.12"
14
+ gem "bundler", ">= 1.0.0"
15
+ gem "jeweler", "~> 1.8.3"
16
+ gem "rcov", ">= 0"
17
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Kasper Johansen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ = ruby_process
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to ruby_process
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2012 Kasper Johansen. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "ruby_process"
18
+ gem.homepage = "http://github.com/kaspernj/ruby_process"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A framework for spawning and communicating with other Ruby-processes}
21
+ gem.description = %Q{A framework for spawning and communicating with other Ruby-processes}
22
+ gem.email = "k@spernj.org"
23
+ gem.authors = ["Kasper Johansen"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "ruby_process #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,55 @@
1
+ class Ruby_process
2
+ #Calls a block by its block-ID with given arguments.
3
+ def cmd_block_call(obj)
4
+ raise "Invalid block-ID: '#{obj}'." if obj[:block_id].to_i <= 0
5
+ block_ele = @objects[obj[:block_id]]
6
+ raise "No block by that ID: '#{obj[:block_id]}'." if !block_ele
7
+ raise "Not a block? '#{block_ele.class.name}'." if !block_ele.respond_to?(:call)
8
+ debug "Calling block #{obj[:block_id]}: #{obj}\n" if @debug
9
+ block_ele.call(*read_args(obj[:args]))
10
+ return nil
11
+ end
12
+
13
+ #Spawns a block and returns its ID.
14
+ def cmd_spawn_proxy_block(obj)
15
+ block = proc{
16
+ send(:cmd => :block_call, :block_id => obj[:id])
17
+ }
18
+
19
+ id = block.__id__
20
+ raise "ID already exists: '#{id}'." if @objects.key?(id)
21
+ @objects[id] = block
22
+
23
+ return {:id => id}
24
+ end
25
+
26
+ #Dynamically creates a block with a certain arity. If sub-methods measure arity, they will get the correct one from the other process.
27
+ def block_with_arity(args, &block)
28
+ eval_str = "proc{"
29
+ eval_argsarr = "\t\tblock.call("
30
+
31
+ if args[:arity] > 0
32
+ eval_str << "|"
33
+ 1.upto(args[:arity]) do |i|
34
+ if i > 1
35
+ eval_str << ","
36
+ eval_argsarr << ","
37
+ end
38
+
39
+ eval_str << "arg#{i}"
40
+ eval_argsarr << "arg#{i}"
41
+ end
42
+
43
+ eval_str << "|\n"
44
+ eval_argsarr << ")\n"
45
+ end
46
+
47
+ eval_full = eval_str + eval_argsarr
48
+ eval_full << "}"
49
+
50
+ debug "Block eval: #{eval_full}\n" if @debug
51
+ dynamic_proc = eval(eval_full)
52
+
53
+ return dynamic_proc
54
+ end
55
+ end
@@ -0,0 +1,8 @@
1
+ class Ruby_process
2
+ #This command returns an object as a marshalled string, so it can be re-created on the other side.
3
+ def cmd_obj_marshal(obj)
4
+ myobj = @objects[obj[:id]]
5
+ raise "Object by that ID does not exist: '#{obj[:id]}'." if !myobj
6
+ return Marshal.dump(myobj)
7
+ end
8
+ end
@@ -0,0 +1,44 @@
1
+ class Ruby_process
2
+ #Spawns a new object in the process and returns a proxy-object for it.
3
+ def new(classname, *args, &block)
4
+ return send(:cmd => :new, :classname => classname, :args => parse_args(args), &block)
5
+ end
6
+
7
+ #This command spawns a new object of a given class and returns its hash-handle, so a proxy-object can be spawned on the other side.
8
+ def cmd_new(obj)
9
+ const = obj[:classname].to_s.split("::").inject(Object, :const_get)
10
+
11
+ debug "New-args-before: #{obj[:args]}\n" if @debug
12
+ real_args = read_args(obj[:args])
13
+ debug "New-args-after: #{real_args}\n" if @debug
14
+
15
+ retobj = const.new(*real_args)
16
+ return handle_return_object(retobj)
17
+ end
18
+
19
+ #Calls a method on an object.
20
+ def cmd_obj_method(obj)
21
+ myobj = @objects[obj[:id]]
22
+ raise "Object by that ID does not exist: '#{obj[:id]}' '(#{@objects.keys.sort.join(",")})." if !myobj
23
+
24
+ if obj.key?(:block)
25
+ real_block = proc{|*args|
26
+ debug "Block called! #{args}\n" if @debug
27
+ send(:cmd => :block_call, :block_id => obj[:block][:id], :args => handle_return_args(args))
28
+ }
29
+
30
+ block = block_with_arity(:arity => obj[:block][:arity], &real_block)
31
+ debug "Spawned fake block with arity: #{block.arity}\n" if @debug
32
+ else
33
+ block = nil
34
+ end
35
+
36
+ debug "Obj-method-args-before: #{obj[:args]}\n" if @debug
37
+ real_args = read_args(obj[:args])
38
+ debug "Obj-methods-args-after: #{real_args}\n" if @debug
39
+
40
+ debug "Calling #{myobj.class.name}.#{obj[:method]}(*#{obj[:args]}, &#{block})\n" if @debug
41
+ res = myobj.__send__(obj[:method], *real_args, &block)
42
+ return handle_return_object(res)
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ class Ruby_process
2
+ #Returns a numeric value like a integer. This methods exists because it isnt possible to do: "Integer.new(5)".
3
+ #===Examples
4
+ # proxy_int = rp.numeric(5)
5
+ # proxy_int.__rp_marshal #=> 5
6
+ def numeric(val)
7
+ return send(:cmd => :numeric, :val => val)
8
+ end
9
+
10
+ #Process-method for the 'numeric'-method.
11
+ def cmd_numeric(obj)
12
+ return handle_return_object(obj[:val].to_i)
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ class Ruby_process
2
+ #Calls a static method on the process-side.
3
+ #===Examples
4
+ # rp.static(:File, :open, "/tmp/somefile", "w") do |fp|
5
+ # fp.write("Test")
6
+ # end
7
+ def static(classname, method, *args, &block)
8
+ debug "Args-before: #{args} (#{@my_pid})\n" if @debug
9
+ real_args = parse_args(args)
10
+ debug "Real-args: #{real_args}\n" if @debug
11
+
12
+ return send(:cmd => :static, :classname => classname, :method => method, :args => real_args, &block)
13
+ end
14
+
15
+ #Process-method for the 'static'-method.
16
+ def cmd_static(obj)
17
+ if obj.key?(:block)
18
+ real_block = proc{|*args|
19
+ debug "Block called! #{args}\n" if @debug
20
+ send(:cmd => :block_call, :block_id => obj[:block][:id], :args => handle_return_args(args))
21
+ }
22
+
23
+ block = block_with_arity(:arity => obj[:block][:arity], &real_block)
24
+ else
25
+ block = nil
26
+ end
27
+
28
+ debug "Static-args-before: #{obj[:args]}\n" if @debug
29
+ real_args = read_args(obj[:args])
30
+ debug "Static-args-after: #{real_args}\n" if @debug
31
+
32
+ const = obj[:classname].to_s.split("::").inject(Object, :const_get)
33
+ retobj = const.__send__(obj[:method], *real_args, &block)
34
+ return handle_return_object(retobj)
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ class Ruby_process
2
+ #Evalulates the given string in the process.
3
+ #===Examples
4
+ # rp.str_eval("return 10").__rp_marshal #=> 10
5
+ def str_eval(str)
6
+ send(:cmd => :str_eval, :str => str)
7
+ end
8
+
9
+ #Process-method for 'str_eval'.
10
+ def cmd_str_eval(obj)
11
+ #Lamda is used here because 'return' might be used in evalled code and thereby return an unhandeled object.
12
+ return handle_return_object(lambda{
13
+ eval(obj[:str])
14
+ }.call)
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ class Ruby_process
2
+ #Closes the process by executing exit.
3
+ def cmd_exit(obj)
4
+ exit
5
+ end
6
+
7
+ #Flushes various collected object-IDs to the subprocess, where they will be garbage-collected.
8
+ def flush_finalized
9
+ @flush_mutex.synchronize do
10
+ debug "Ruby-process-debug: Checking finalized\n" if @debug
11
+ ids = @proxy_objs_unsets.shift(500)
12
+ debug "IDS: #{ids} #{@proxy_objs_unsets}\n" if @debug
13
+ return nil if ids.empty?
14
+
15
+ debug "Ruby-process-debug: Finalizing (#{ids}).\n" if @debug
16
+ send(:cmd => :flush_finalized, :ids => ids)
17
+ @finalize_count += ids.length
18
+ return nil
19
+ end
20
+ end
21
+
22
+ #Flushes references to the given object IDs.
23
+ def cmd_flush_finalized(obj)
24
+ debug "Command-flushing finalized: '#{obj[:ids]}'.\n" if @debug
25
+ obj[:ids].each do |id|
26
+ raise "Unknown ID: '#{id}' (#{id.class.name})." if !@objects.key?(id)
27
+ @objects.delete(id)
28
+ end
29
+
30
+ return nil
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "ruby_process"
5
+
6
+ fpath = "/tmp/somefile"
7
+ Ruby_process.new.spawn_process do |rp|
8
+ #Opens file in subprocess.
9
+ rp.static(:File, :open, fpath, "w") do |fp|
10
+ #Writes to file in subprocess.
11
+ fp.write("Test!")
12
+ end
13
+ end
14
+
15
+ print "Content of '#{fpath}': #{File.read(fpath)}\n"
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #This example shows how to dump a database using 10 processes to do so (and effectivly use 10 cores).
4
+
5
+ require "rubygems"
6
+ require "knjrbfw"
7
+ require "ruby_process"
8
+
9
+ #Holds the 'db_settings'-global-variable.
10
+ require "#{Knj::Os.homedir}/example_knj_db_dump_settings.rb"
11
+
12
+ #Create database-connection.
13
+ db = Knj::Db.new($db_settings)
14
+
15
+ #Get list of databases.
16
+ tables = db.tables.list.values
17
+
18
+ tables_per_thread = (tables.length.to_f / 10.0).ceil
19
+ print "Tables per thread: #{tables_per_thread}\n"
20
+
21
+ threads = []
22
+ 1.upto(1) do |i|
23
+ threads << Thread.new do
24
+ begin
25
+ thread_tables = tables.shift(tables_per_thread)
26
+
27
+ Ruby_process.new(:debug => true).spawn_process do |rp|
28
+ rp.static(:Object, :require, "rubygems")
29
+ rp.static(:Object, :require, "knjrbfw")
30
+
31
+ fpath = "/tmp/dbdump_#{i}.sql"
32
+
33
+ thread_tables.each do |thread_db|
34
+ rp_db = rp.new("Knj::Db", $db_settings)
35
+ rp_dump = rp.new("Knj::Db::Dump", :db => rp_db, :tables => thread_tables)
36
+
37
+ rp.static(:File, :open, fpath, "w") do |rp_fp|
38
+ print "#{i} dumping #{thread_db}\n"
39
+ rp_dump.dump(rp_fp)
40
+ end
41
+ end
42
+ end
43
+ rescue => e
44
+ puts e.inspect
45
+ puts e.backtrace
46
+ end
47
+ end
48
+ end
49
+
50
+ threads.each do |thread|
51
+ thread.join
52
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "ruby_process"
5
+
6
+ Ruby_process.new.spawn_process do |rp|
7
+ #Spawns string in the subprocess.
8
+ str = rp.new(:String, "Kasper is 26 years old")
9
+
10
+ #Scans with regex in subprocess, but yields proxy-objects in the current process.
11
+ str.scan(/is (\d+) years old/) do |match|
12
+ puts match.__rp_marshal
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ class Ruby_process
2
+ #Recursivly parses arrays and hashes into proxy-object-hashes.
3
+ def parse_args(args)
4
+ if args.is_a?(Array)
5
+ newarr = []
6
+ args.each do |val|
7
+ newarr << parse_args(val)
8
+ end
9
+
10
+ return newarr
11
+ elsif args.is_a?(Hash)
12
+ newh = {}
13
+ args.each do |key, val|
14
+ newh[parse_args(key)] = parse_args(val)
15
+ end
16
+
17
+ return newh
18
+ elsif @args_allowed.index(args.class) != nil
19
+ return args
20
+ else
21
+ return handle_return_object(args)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ #Returns a special hash instead of an actual object. Some objects will be returned in their normal form (true, false and nil).
28
+ def handle_return_object(obj, pid = @my_pid)
29
+ #Dont proxy these objects.
30
+ return obj if obj.is_a?(TrueClass) or obj.is_a?(FalseClass) or obj.is_a?(NilClass)
31
+
32
+ #The object is a proxy-obj - just return its arguments that contains the true 'my_pid'.
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]}
36
+ end
37
+
38
+ #Check if object has already been spawned. If not: spawn id. Then returns hash for it.
39
+ id = obj.__id__
40
+ @objects[id] = obj if !@objects.key?(id)
41
+
42
+ debug "Proxy-object spawned (ID: #{id}, PID: #{pid}).\n" if @debug
43
+ return {:type => :proxy_obj, :id => id, :pid => pid}
44
+ end
45
+
46
+ #Parses an argument array to proxy-object-hashes.
47
+ def handle_return_args(arr)
48
+ newa = []
49
+ arr.each do |obj|
50
+ newa << handle_return_object(obj)
51
+ end
52
+
53
+ return newa
54
+ end
55
+
56
+ #Recursivly scans arrays and hashes for proxy-object-hashes and replaces them with actual proxy-objects.
57
+ def read_args(args)
58
+ if args.is_a?(Array)
59
+ newarr = []
60
+ args.each do |val|
61
+ newarr << read_args(val)
62
+ end
63
+
64
+ return newarr
65
+ elsif args.is_a?(Hash) and args.length == 3 and args[:type] == :proxy_obj and args.key?(:id) and args.key?(:pid)
66
+ debug "Comparing PID (#{args[:pid]}, #{@my_pid}).\n" if @debug
67
+
68
+ if args[:pid] == @my_pid
69
+ debug "Same!\n" if @debug
70
+ return proxyobj_object(args[:id])
71
+ else
72
+ debug "Not same!\n" if @debug
73
+ return proxyobj_get(args[:id], args[:pid])
74
+ end
75
+ elsif args.is_a?(Hash)
76
+ newh = {}
77
+ args.each do |key, val|
78
+ newh[read_args(key)] = read_args(val)
79
+ end
80
+
81
+ return newh
82
+ end
83
+
84
+ return args
85
+ end
86
+ end
@@ -0,0 +1,394 @@
1
+ require "rubygems"
2
+ require "base64"
3
+ require "wref" if !Kernel.const_defined?(:Wref)
4
+ require "tsafe" if !Kernel.const_defined?(:Tsafe)
5
+
6
+ #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
+ class Ruby_process
8
+ attr_reader :finalize_count
9
+
10
+ #Require all the different commands.
11
+ dir = "#{File.dirname(__FILE__)}/../cmds"
12
+ Dir.foreach(dir) do |file|
13
+ require "#{dir}/#{file}" if file =~ /\.rb$/
14
+ end
15
+
16
+ #Methods for handeling arguments and proxy-objects in arguments.
17
+ require "#{File.dirname(__FILE__)}/../include/args_handeling.rb"
18
+
19
+ #Constructor.
20
+ #===Examples
21
+ # Ruby_process.new.spawn_process do |rp|
22
+ # str = rp.new(:String, "Kasper")
23
+ # end
24
+ def initialize(args = {})
25
+ @args = args
26
+ @debug = @args[:debug]
27
+ @pid = @args[:pid]
28
+
29
+ #These classes are allowed in call-arguments. They can be marshalled without any errors.
30
+ @args_allowed = [FalseClass, Fixnum, Integer, NilClass, String, Symbol, TrueClass]
31
+
32
+ #Set IO variables if given.
33
+ @io_out = Tsafe::Proxy.new(:obj => @args[:out]) if @args[:out]
34
+ @io_in = @args[:in] if @args[:in]
35
+ @io_err = @args[:err] if @args[:err]
36
+
37
+ #This hash holds answers coming from the subprocess.
38
+ @answers = Tsafe::MonHash.new
39
+
40
+ #This hash holds objects that are referenced in the process.
41
+ @objects = Tsafe::MonHash.new
42
+
43
+ #This weak-map holds all proxy objects.
44
+ @proxy_objs = Wref_map.new
45
+ @proxy_objs_ids = Tsafe::MonHash.new
46
+ @proxy_objs_unsets = Tsafe::MonArray.new
47
+ @flush_mutex = Mutex.new
48
+ @finalize_count = 0
49
+
50
+ #Send ID is used to identify the correct answers.
51
+ @send_mutex = Mutex.new
52
+ @send_count = 0
53
+
54
+ #The PID is used to know which process proxy-objects belongs to.
55
+ @my_pid = Process.pid
56
+ end
57
+
58
+ #Spawns a new process in the same Ruby-inteterpeter as the current one.
59
+ #===Examples
60
+ # rp = Ruby_process.new.spawn_process
61
+ # rp.str_eval("return 10").__rp_marshal #=> 10
62
+ # rp.destroy
63
+ def spawn_process(args = nil)
64
+ #Used for printing debug-stuff.
65
+ @main = true
66
+
67
+ if args and args[:exec]
68
+ cmd = "#{args[:exec]}"
69
+ else
70
+ cmd = "ruby"
71
+ end
72
+
73
+ cmd << " \"#{File.realpath(File.dirname(__FILE__))}/../scripts/ruby_process_script.rb\" --pid=#{@my_pid}"
74
+ cmd << " --debug" if @args[:debug]
75
+
76
+ #Start process and set IO variables.
77
+ require "open3"
78
+ @io_out, @io_in, @io_err = Open3.popen3(cmd)
79
+ @io_out = Tsafe::Proxy.new(:obj => @io_out)
80
+ @io_out.sync = true
81
+ @io_in.sync = true
82
+ @io_err.sync = true
83
+
84
+ started = false
85
+ @io_in.each_line do |str|
86
+ if str == "ruby_process_started\n"
87
+ started = true
88
+ break
89
+ end
90
+
91
+ debug "Ruby-process-debug from stdout before started: '#{str}'\n" if @debug
92
+ end
93
+
94
+ raise "Ruby-sub-process couldnt start: '#{@io_err.read}'." if !started
95
+ self.listen
96
+
97
+ #Start by getting the PID of the process.
98
+ begin
99
+ @pid = self.static(:Process, :pid).__rp_marshal
100
+ raise "Unexpected PID: '#{@pid}'." if !@pid.is_a?(Fixnum) and !@pid.is_a?(Integer)
101
+ rescue => e
102
+ self.destroy
103
+ raise e
104
+ end
105
+
106
+ if block_given?
107
+ begin
108
+ yield(self)
109
+ ensure
110
+ self.destroy
111
+ end
112
+
113
+ return nil
114
+ else
115
+ return self
116
+ end
117
+ end
118
+
119
+ #Starts listening on the given IO's. It is useally not needed to call this method manually.
120
+ def listen
121
+ #Start listening for input.
122
+ start_listen
123
+
124
+ #Start listening for errors.
125
+ start_listen_errors
126
+ end
127
+
128
+ #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.
129
+ def destroy
130
+ begin
131
+ send(:cmd => :exit) if alive?
132
+ rescue => e
133
+ raise e if e.message != "Process is dead." and e.message != "Not listening."
134
+ end
135
+
136
+ #Make main kill it and make sure its dead...
137
+ begin
138
+ if @main and @pid
139
+ Process.kill("TERM", @pid)
140
+ Process.kill(9, @pid)
141
+ end
142
+ rescue Errno::ESRCH
143
+ #Process is already dead - ignore.
144
+ end
145
+ end
146
+
147
+ #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.
148
+ def join
149
+ debug "Joining listen-thread.\n" if @debug
150
+ @thr_listen.join if @thr_listen
151
+ raise @listen_err if @listen_err
152
+
153
+ debug "Joining error-thread.\n" if @debug
154
+ @thr_err.join if @thr_join
155
+ raise @listen_err_err if @listen_err_err
156
+ end
157
+
158
+ #Sends a command to the other process. This should not be called manually, but is used by various other parts of the framework.
159
+ def send(obj, &block)
160
+ raise "Ruby-process is dead." if !alive?
161
+
162
+ #Parse block.
163
+ if block
164
+ block_proxy_res = self.send(:cmd => :spawn_proxy_block, :id => block.__id__)
165
+ raise "No block ID was returned?" if !block_proxy_res[:id]
166
+ raise "Invalid block-ID: '#{block_proxy_res[:id]}'." if block_proxy_res[:id].to_i <= 0
167
+ @proxy_objs[block_proxy_res[:id]] = block
168
+ @proxy_objs_ids[block.__id__] = block_proxy_res[:id]
169
+ @objects[block_proxy_res[:id]] = block
170
+ ObjectSpace.define_finalizer(block_proxy_res, self.method(:proxyobj_finalizer))
171
+ obj[:block] = {
172
+ :id => block_proxy_res[:id],
173
+ :arity => block.arity
174
+ }
175
+ end
176
+
177
+ flush_finalized if obj[:cmd] != :flush_finalized
178
+
179
+ #Sync ID stuff so they dont get mixed up.
180
+ id = nil
181
+ @send_mutex.synchronize do
182
+ id = @send_count
183
+ @send_count += 1
184
+ end
185
+
186
+ debug "Sending(#{id}): #{obj}\n" if @debug
187
+ line = Base64.strict_encode64(Marshal.dump(
188
+ :id => id,
189
+ :type => :send,
190
+ :obj => obj
191
+ ))
192
+ @io_out.puts(line)
193
+ sleep 0.001
194
+ return answer_read(id)
195
+ end
196
+
197
+ private
198
+
199
+ def debug(str_full)
200
+ raise "Debug not enabled?" if !@debug
201
+
202
+ str_full.each_line do |str|
203
+ if @main
204
+ $stderr.print "(M#{@my_pid}) #{str}"
205
+ else
206
+ $stderr.print "(S#{@my_pid}) #{str}"
207
+ end
208
+ end
209
+ end
210
+
211
+ #Returns true if the child process is still running. Otherwise false.
212
+ def alive?
213
+ return false if !@io_out or !@io_in or @io_in.closed?
214
+ return true
215
+ end
216
+
217
+ #Raises an error if the subprocess is no longer alive.
218
+ def alive_check!
219
+ raise "Process is dead." unless alive?
220
+ return nil
221
+ end
222
+
223
+ #Registers an object ID as a proxy-object on the host-side.
224
+ def proxyobj_get(id, pid = @my_pid)
225
+ if proxy_obj = @proxy_objs.get!(id)
226
+ debug "Reuse proxy-obj (ID: #{id}, PID: #{pid}, fID: #{proxy_obj.args[:id]}, fPID: #{proxy_obj.args[:pid]})\n" if @debug
227
+ return proxy_obj
228
+ end
229
+
230
+ @proxy_objs_unsets.delete(id)
231
+ proxy_obj = Ruby_process::Proxyobj.new(:rp => self, :id => id, :pid => pid)
232
+ @proxy_objs[id] = proxy_obj
233
+ @proxy_objs_ids[proxy_obj.__id__] = id
234
+ ObjectSpace.define_finalizer(proxy_obj, self.method(:proxyobj_finalizer))
235
+
236
+ return proxy_obj
237
+ end
238
+
239
+ def proxyobj_object(id)
240
+ obj = @objects[id]
241
+ raise "No object by that ID: '#{id}' (#{@objects})." if !obj
242
+ return obj
243
+ end
244
+
245
+ #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.
246
+ def proxyobj_finalizer(id)
247
+ debug "Finalized #{id}\n" if @debug
248
+ proxy_id = @proxy_objs_ids[id]
249
+
250
+ if !proxy_id
251
+ debug "No such ID in proxy objects IDs hash: '#{id}'.\n" if @debug
252
+ else
253
+ @proxy_objs_unsets << proxy_id
254
+ debug "Done finalizing #{id}\n" if @debug
255
+ end
256
+
257
+ return nil
258
+ end
259
+
260
+ #Waits for an answer to appear in the answers-hash. Then deletes it from hash and returns it.
261
+ def answer_read(id)
262
+ debug "Waiting for answer #{id}\n" if @debug
263
+
264
+ loop do
265
+ if @answers.key?(id)
266
+ debug "Returning answer #{id}\n" if @debug
267
+ answer = @answers[id]
268
+ @answers.delete(id)
269
+
270
+ if answer.is_a?(Hash) and answer[:type] == :error and answer.key?(:class) and answer.key?(:msg) and answer.key?(:bt)
271
+ begin
272
+ raise "#{answer[:class]}: #{answer[:msg]}"
273
+ rescue => e
274
+ bt = []
275
+ answer[:bt].each do |btline|
276
+ bt << "(#{@pid}): #{btline}"
277
+ end
278
+
279
+ bt += e.backtrace
280
+ e.set_backtrace(bt)
281
+ raise e
282
+ end
283
+ elsif answer.is_a?(Hash) and answer[:type] == :proxy_obj and answer.key?(:id)
284
+ return proxyobj_get(answer[:id], answer[:pid])
285
+ end
286
+
287
+ return answer
288
+ end
289
+
290
+ debug "No answer by ID #{id} - sleeping...\n" if @debug
291
+ sleep 0.01
292
+ alive_check!
293
+ raise @listen_err if @listen_err
294
+ raise "Not listening." if !@thr_listen or !@thr_listen.alive?
295
+ end
296
+ end
297
+
298
+ #Starts the listen-thread that listens for, and executes, commands.
299
+ def start_listen
300
+ @thr_listen = Thread.new do
301
+ begin
302
+ @io_in.each_line do |line|
303
+ raise "No line?" if !line or line.to_s.strip.length <= 0
304
+ alive_check!
305
+ debug "Received: #{line}" if @debug
306
+
307
+ begin
308
+ obj = Marshal.load(Base64.strict_decode64(line.strip))
309
+ debug "Object received: #{obj}\n" if @debug
310
+ rescue => e
311
+ $stderr.puts "Base64Str: #{line}" if @debug
312
+ $stderr.puts e.inspect if @debug
313
+ $stderr.puts e.backtrace if @debug
314
+
315
+ raise e
316
+ end
317
+
318
+ if obj[:type] == :send
319
+ Thread.new do
320
+ begin
321
+ raise "Object was not a hash." if !obj.is_a?(Hash)
322
+ raise "No ID was given?" if !obj.key?(:id)
323
+ res = self.__send__("cmd_#{obj[:obj][:cmd]}", obj[:obj])
324
+ rescue Exception => e
325
+ raise e if e.is_a?(SystemExit) or e.is_a?(Interrupt)
326
+ res = {:type => :error, :class => e.class.name, :msg => e.message, :bt => e.backtrace}
327
+ end
328
+
329
+ data = Base64.strict_encode64(Marshal.dump(:type => :answer, :id => obj[:id], :answer => res))
330
+ @io_out.puts(data)
331
+ end
332
+ elsif obj[:type] == :answer
333
+ debug "Answer #{obj[:id]} saved.\n" if @debug
334
+ @answers[obj[:id]] = obj[:answer]
335
+ else
336
+ raise "Unknown object: '#{obj}'."
337
+ end
338
+ end
339
+ rescue => e
340
+ @listen_err = e
341
+ end
342
+ end
343
+ end
344
+
345
+ #Starts the listen thread that outputs the 'stderr' for the other process on this process's 'stderr'.
346
+ def start_listen_errors
347
+ return nil if !@io_err
348
+
349
+ @thr_err = Thread.new do
350
+ begin
351
+ @io_err.each_line do |str|
352
+ $stderr.print str if @debug
353
+ end
354
+ rescue => e
355
+ @listen_err_err = e
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ #This class handels the calling of methods on objects in the other process seamlessly.
362
+ class Ruby_process::Proxyobj
363
+ #Hash that contains various information about the proxyobj.
364
+ attr_reader :args
365
+
366
+ #Constructor. This should not be called manually but through a running 'Ruby_process'.
367
+ #===Examples
368
+ # proxy_obj = rp.new(:String, "Kasper") #=> <Ruby_process::Proxyobj>
369
+ # proxy_obj = rp.static(:File, :open, "/tmp/somefile") #=> <Ruby_process::Proxyobj>
370
+ def initialize(args)
371
+ @args = args
372
+ end
373
+
374
+ #Returns the object as the real object transfered by using the marshal-lib.
375
+ #===Examples
376
+ # str = rp.new(:String, "Kasper") #=> <Ruby_process::Proxyobj>
377
+ # str.__rp_marshal #=> "Kasper"
378
+ def __rp_marshal
379
+ return Marshal.load(@args[:rp].send(:cmd => :obj_marshal, :id => @args[:id]))
380
+ end
381
+
382
+ #Proxies all calls to the process-object.
383
+ #===Examples
384
+ # str = rp.new(:String, "Kasper") #=> <Ruby_process::Proxyobj::1>
385
+ # length_int = str.length #=> <Ruby_process::Proxyobj::2>
386
+ # length_int.__rp_marshal #=> 6
387
+ def method_missing(method, *args, &block)
388
+ debug "Method-missing-args-before: #{args} (#{@my_pid})\n" if @debug
389
+ real_args = @args[:rp].parse_args(args)
390
+ debug "Method-missing-args-after: #{real_args}\n" if @debug
391
+
392
+ return @args[:rp].send(:cmd => :obj_method, :id => @args[:id], :method => method, :args => real_args, &block)
393
+ end
394
+ end
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ruby_process}
8
+ s.version = "0.0.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kasper Johansen"]
12
+ s.date = %q{2012-10-10}
13
+ s.description = %q{A framework for spawning and communicating with other Ruby-processes}
14
+ s.email = %q{k@spernj.org}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "cmds/blocks.rb",
28
+ "cmds/marshal.rb",
29
+ "cmds/new.rb",
30
+ "cmds/numeric.rb",
31
+ "cmds/static.rb",
32
+ "cmds/str_eval.rb",
33
+ "cmds/system.rb",
34
+ "examples/example_file_write.rb",
35
+ "examples/example_knj_db_dump.rb",
36
+ "examples/example_strscan.rb",
37
+ "include/args_handeling.rb",
38
+ "lib/ruby_process.rb",
39
+ "ruby_process.gemspec",
40
+ "scripts/ruby_process_script.rb",
41
+ "spec/ruby_process_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/kaspernj/ruby_process}
45
+ s.licenses = ["MIT"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.6.2}
48
+ s.summary = %q{A framework for spawning and communicating with other Ruby-processes}
49
+
50
+ if s.respond_to? :specification_version then
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<wref>, [">= 0"])
55
+ s.add_runtime_dependency(%q<tsafe>, [">= 0"])
56
+ s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
57
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
58
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
60
+ s.add_development_dependency(%q<rcov>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<wref>, [">= 0"])
63
+ s.add_dependency(%q<tsafe>, [">= 0"])
64
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
65
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
66
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
68
+ s.add_dependency(%q<rcov>, [">= 0"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<wref>, [">= 0"])
72
+ s.add_dependency(%q<tsafe>, [">= 0"])
73
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
74
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
75
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
76
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
77
+ s.add_dependency(%q<rcov>, [">= 0"])
78
+ end
79
+ end
80
+
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby1.9
2
+
3
+ require "base64"
4
+ require "#{File.dirname(__FILE__)}/../lib/ruby_process.rb"
5
+
6
+ $stdin.sync = true
7
+ $stdout.sync = true
8
+ $stderr.sync = true
9
+
10
+ debug = false
11
+ pid = nil
12
+
13
+ ARGV.each do |arg|
14
+ if arg == "--debug"
15
+ debug = true
16
+ elsif match = arg.match(/--pid=(\d+)/)
17
+ pid = match[1].to_i
18
+ else
19
+ raise "Unknown argument: '#{arg}'."
20
+ end
21
+ end
22
+
23
+ debug = true if ARGV.index("--debug") != nil
24
+ raise "No PID given of parent process." if !pid
25
+
26
+ rps = Ruby_process.new(
27
+ :in => $stdin,
28
+ :out => $stdout,
29
+ :err => $stderr,
30
+ :debug => debug,
31
+ :pid => pid
32
+ )
33
+ rps.listen
34
+ $stdout.puts("ruby_process_started")
35
+ rps.join
@@ -0,0 +1,148 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "RubyProcess" do
4
+ it "should be able to do basic stuff" do
5
+ $rp = Ruby_process.new(:debug => false)
6
+ $rp.spawn_process
7
+
8
+ proxyarr = $rp.new(:Array)
9
+ proxyarr << 1
10
+ proxyarr << 3
11
+ proxyarr << 5
12
+ arr = proxyarr.__rp_marshal
13
+
14
+ raise "Not an array." if !arr.is_a?(Array)
15
+ raise "Expected three elements." if arr.length != 3
16
+ raise "Expected 1" if arr[0] != 1
17
+ raise "Expected 3" if arr[1] != 3
18
+ raise "Expected 5" if arr[2] != 5
19
+ end
20
+
21
+ it "should be able to pass proxy-objects as arguments." do
22
+ str = $rp.new(:String, "/tmp/somefile")
23
+ $rp.static(:File, :open, str, "w") do |fp|
24
+ fp.write("Test!")
25
+ end
26
+
27
+ raise "Unexpected" if File.read(str.__rp_marshal) != "Test!"
28
+ end
29
+
30
+ it "should be able to write files" do
31
+ fpath = "/tmp/ruby_process_file_write_test"
32
+ fp = $rp.static(:File, :open, fpath, "w")
33
+ fp.write("Test!")
34
+ fp.close
35
+
36
+ raise "Expected 'Test!'" if File.read(fpath) != "Test!"
37
+ end
38
+
39
+ it "should do garbage collection" do
40
+ GC.start
41
+ end
42
+
43
+ it "should be able to do static calls" do
44
+ pid = $rp.static(:Process, :pid).__rp_marshal
45
+ raise "Expected stuff to be finalized but it wasnt." if $rp.finalize_count <= 0 if RUBY_ENGINE != "jruby"
46
+ raise "Unexpcted" if !pid.is_a?(Fixnum) and !pid.is_a?(Integer)
47
+ end
48
+
49
+ it "should be able to handle blocking blocks" do
50
+ run_count = 0
51
+ fpath = "/tmp/ruby_process_file_write_test"
52
+ $rp.static(:File, :open, fpath, "w") do |fp|
53
+ sleep 0.1
54
+ run_count += 1
55
+ fp.write("Test!!!")
56
+ end
57
+
58
+ raise "Expected run-count to be 1 but it wasnt: '#{run_count}'." if run_count <= 0
59
+ raise "Expected 'Test!'" if File.read(fpath) != "Test!!!"
60
+ end
61
+
62
+ it "should be able to do slow block-results in JRuby." do
63
+ $rp.str_eval("
64
+ class ::Kaspertest
65
+ def self.kaspertest
66
+ 8.upto(12) do |i|
67
+ yield(i)
68
+ sleep 0.5
69
+ end
70
+ end
71
+ end
72
+
73
+ nil
74
+ ")
75
+
76
+ require "timeout"
77
+ Timeout.timeout(10) do
78
+ expect = 8
79
+ $rp.static("Kaspertest", "kaspertest") do |count|
80
+ raise "Expected '#{expect}' but got: '#{count.__rp_marshal}'." if expect != count.__rp_marshal
81
+ expect += 1
82
+ end
83
+
84
+ raise "Expected '13' but got: '#{expect}'." if expect != 13
85
+ end
86
+ end
87
+
88
+ it "should be able to handle large block-runs" do
89
+ #Try to define an integer and run upto with a block.
90
+ proxy_int = $rp.numeric(5)
91
+
92
+ expect = 5
93
+ proxy_int.upto(250) do |i|
94
+ raise "Expected '#{expect}' but got: '#{i.__rp_marshal}'." if i.__rp_marshal != expect
95
+ expect += 1
96
+ end
97
+
98
+ #Ensure the expected has actually been increased by running the block.
99
+ raise "Expected end-result of 1001 but got: '#{expect}'." if expect != 251
100
+ end
101
+
102
+ it "should handle stressed operations" do
103
+ #Spawn a test-object - a string - with a variable-name.
104
+ proxy_obj = $rp.new(:String, "Kasper")
105
+ raise "to_s should return 'Kasper' but didnt: '#{proxy_obj.__rp_marshal}'." if proxy_obj.__rp_marshal != "Kasper"
106
+
107
+ #Stress it a little by doing 500 calls.
108
+ 0.upto(500) do
109
+ res = proxy_obj.slice(0, 3).__rp_marshal
110
+ raise "Expected output was: 'Kas' but wasnt: '#{res}'." if res != "Kas"
111
+ end
112
+ end
113
+
114
+ it "should be thread-safe" do
115
+ #Do a lot of calls at the same time to test thread-safety.
116
+ proxy_obj = $rp.new(:String, "Kasper")
117
+ threads = []
118
+ 0.upto(5) do |thread_i|
119
+ should_return = "Kasper".slice(0, thread_i)
120
+ thread = Thread.new do
121
+ begin
122
+ 0.upto(250) do |num_i|
123
+ res = proxy_obj.slice(0, thread_i).__rp_marshal
124
+ raise "Should return: '#{should_return}' but didnt: '#{res}'." if res != should_return
125
+ end
126
+ rescue => e
127
+ Thread.current[:error] = e
128
+ end
129
+ end
130
+
131
+ threads << thread
132
+ end
133
+
134
+ threads.each do |thread|
135
+ thread.join
136
+ raise thread[:error] if thread[:error]
137
+ end
138
+ end
139
+
140
+ it "should be able to do evals" do
141
+ res = $rp.str_eval("return 10").__rp_marshal
142
+ raise "Unexpected: #{res}" if res != 10
143
+ end
144
+
145
+ it "should be able to destroy itself" do
146
+ $rp.destroy
147
+ end
148
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'ruby_process'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_process
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kasper Johansen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-10 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: wref
17
+ requirement: &11532040 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *11532040
26
+ - !ruby/object:Gem::Dependency
27
+ name: tsafe
28
+ requirement: &11531160 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *11531160
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &11530360 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 2.8.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *11530360
48
+ - !ruby/object:Gem::Dependency
49
+ name: rdoc
50
+ requirement: &11529480 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '3.12'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *11529480
59
+ - !ruby/object:Gem::Dependency
60
+ name: bundler
61
+ requirement: &11528660 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: 1.0.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *11528660
70
+ - !ruby/object:Gem::Dependency
71
+ name: jeweler
72
+ requirement: &11527920 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.3
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *11527920
81
+ - !ruby/object:Gem::Dependency
82
+ name: rcov
83
+ requirement: &11527180 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: *11527180
92
+ description: A framework for spawning and communicating with other Ruby-processes
93
+ email: k@spernj.org
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files:
97
+ - LICENSE.txt
98
+ - README.rdoc
99
+ files:
100
+ - .document
101
+ - .rspec
102
+ - Gemfile
103
+ - LICENSE.txt
104
+ - README.rdoc
105
+ - Rakefile
106
+ - VERSION
107
+ - cmds/blocks.rb
108
+ - cmds/marshal.rb
109
+ - cmds/new.rb
110
+ - cmds/numeric.rb
111
+ - cmds/static.rb
112
+ - cmds/str_eval.rb
113
+ - cmds/system.rb
114
+ - examples/example_file_write.rb
115
+ - examples/example_knj_db_dump.rb
116
+ - examples/example_strscan.rb
117
+ - include/args_handeling.rb
118
+ - lib/ruby_process.rb
119
+ - ruby_process.gemspec
120
+ - scripts/ruby_process_script.rb
121
+ - spec/ruby_process_spec.rb
122
+ - spec/spec_helper.rb
123
+ has_rdoc: true
124
+ homepage: http://github.com/kaspernj/ruby_process
125
+ licenses:
126
+ - MIT
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ segments:
138
+ - 0
139
+ hash: -762115718297605832
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 1.6.2
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: A framework for spawning and communicating with other Ruby-processes
152
+ test_files: []