RubyProcess 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +20 -0
- data/README.md +98 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/cmds/blocks.rb +68 -0
- data/cmds/marshal.rb +8 -0
- data/cmds/new.rb +44 -0
- data/cmds/numeric.rb +14 -0
- data/cmds/static.rb +36 -0
- data/cmds/str_eval.rb +16 -0
- data/cmds/system.rb +47 -0
- data/examples/example_csv.rb +13 -0
- data/examples/example_file_write.rb +15 -0
- data/examples/example_knj_db_dump.rb +52 -0
- data/examples/example_strscan.rb +14 -0
- data/include/args_handeling.rb +88 -0
- data/lib/ruby_process.rb +458 -0
- data/lib/ruby_process/class_proxy.rb +106 -0
- data/lib/ruby_process/proxy_object.rb +55 -0
- data/ruby_process.gemspec +85 -0
- data/scripts/ruby_process_script.rb +37 -0
- data/shippable.yml +7 -0
- data/spec/class_proxy_spec.rb +95 -0
- data/spec/hard_load_spec.rb +35 -0
- data/spec/leaks_spec.rb +24 -0
- data/spec/ruby_process_spec.rb +174 -0
- data/spec/spec_helper.rb +14 -0
- metadata +173 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
#This class is used to seamlessly use leaky classes without working through 'RubyProcess'.
|
2
|
+
#===Examples
|
3
|
+
# RubyProcess::ClassProxy.run do |data|
|
4
|
+
# data[:subproc].static(:Object, :require, "rubygems")
|
5
|
+
# data[:subproc].static(:Object, :require, "rexml/document")
|
6
|
+
#
|
7
|
+
# doc = RubyProcess::ClassProxy::REXML::Document.new("test")
|
8
|
+
# strio = StringIO.new
|
9
|
+
# doc.write(strio)
|
10
|
+
# puts strio.string #=> "<test/>"
|
11
|
+
# raise "REXML shouldnt be defined?" if Kernel.const_defined?(:REXML)
|
12
|
+
class RubyProcess::ClassProxy
|
13
|
+
#Lock is used to to create new Ruby-process-instances and not doing double-counts.
|
14
|
+
@@lock = Mutex.new
|
15
|
+
|
16
|
+
#Counts how many instances are using the Cproxy-module. This way it can be safely unset once no-body is using it again.
|
17
|
+
@@instances = 0
|
18
|
+
|
19
|
+
#This variable will hold the 'RubyProcess'-object where sub-objects will be created.
|
20
|
+
@@subproc = nil
|
21
|
+
|
22
|
+
#All use should go through this method to automatically destroy sub-processes and keep track of ressources.
|
23
|
+
def self.run
|
24
|
+
#Increase count of instances that are using Cproxy and set the subproc-object if not already set.
|
25
|
+
@@lock.synchronize do
|
26
|
+
#Check if the sub-process is alive.
|
27
|
+
if @@subproc && (!@@subproc.alive? || @@subproc.destroyed?)
|
28
|
+
raise "Cant destroy sub-process because instances are running: '#{@@instances}'." if @@instances > 0
|
29
|
+
@@subproc.destroy
|
30
|
+
@@subproc = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
#Start a new subprocess if none is defined and active.
|
34
|
+
unless @@subproc
|
35
|
+
subproc = RubyProcess.new(title: "ruby_process_cproxy", debug: false)
|
36
|
+
subproc.spawn_process
|
37
|
+
@@subproc = subproc
|
38
|
+
end
|
39
|
+
|
40
|
+
@@instances += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
yield(subproc: @@subproc)
|
45
|
+
raise "'run'-caller destroyed sub-process. This shouldn't happen." if @@subproc.destroyed?
|
46
|
+
ensure
|
47
|
+
@@lock.synchronize do
|
48
|
+
@@instances -= 1
|
49
|
+
|
50
|
+
if @@instances <= 0
|
51
|
+
begin
|
52
|
+
@@subproc.destroy
|
53
|
+
ensure
|
54
|
+
@@subproc = nil
|
55
|
+
self.destroy_loaded_constants
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#Returns the 'RubyProcess'-object or raises an error if it has not been set.
|
63
|
+
def self.subproc
|
64
|
+
raise "CProxy process not set for some reason?" unless @@subproc
|
65
|
+
return @@subproc
|
66
|
+
end
|
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
|
+
|
75
|
+
#Creates the new constant under the 'RubyProcess::ClassProxy'-namespace.
|
76
|
+
def self.const_missing(name)
|
77
|
+
RubyProcess::ClassProxy.load_class(self, name) unless const_defined?(name)
|
78
|
+
raise "Still not created on const: '#{name}'." unless const_defined?(name)
|
79
|
+
return self.const_get(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
#Loads a new class to the given constants namespace for recursive creating of missing classes.
|
83
|
+
def self.load_class(const, name)
|
84
|
+
const.const_set(name, Class.new{
|
85
|
+
#Use 'const_missing' to auto-create missing sub-constants recursivly.
|
86
|
+
def self.const_missing(name)
|
87
|
+
RubyProcess::ClassProxy.load_class(self, name) unless const_defined?(name)
|
88
|
+
raise "Still not created on const: '#{name}'." unless self.const_defined?(name)
|
89
|
+
return self.const_get(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
#Manipulate 'new'-method return proxy-objects instead of real objects.
|
93
|
+
def self.new(*args, &blk)
|
94
|
+
name_match = self.name.to_s.match(/^RubyProcess::ClassProxy::(.+)$/)
|
95
|
+
class_name = name_match[1]
|
96
|
+
return RubyProcess::ClassProxy.subproc.new(class_name, *args, &blk)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.method_missing(method_name, *args, &blk)
|
100
|
+
name_match = self.name.to_s.match(/^RubyProcess::ClassProxy::(.+)$/)
|
101
|
+
class_name = name_match[1]
|
102
|
+
return RubyProcess::ClassProxy.subproc.static(class_name, method_name, *args, &blk)
|
103
|
+
end
|
104
|
+
})
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#This class handels the calling of methods on objects in the other process seamlessly.
|
2
|
+
class RubyProcess::ProxyObject
|
3
|
+
#Hash that contains various information about the proxyobj.
|
4
|
+
attr_reader :__rp_rp, :__rp_id, :__rp_pid
|
5
|
+
|
6
|
+
#Constructor. This should not be called manually but through a running 'RubyProcess'.
|
7
|
+
#===Examples
|
8
|
+
# proxy_obj = rp.new(:String, "Kasper") #=> <RubyProcess::ProxyObject>
|
9
|
+
# proxy_obj = rp.static(:File, :open, "/tmp/somefile") #=> <RubyProcess::ProxyObject>
|
10
|
+
def initialize(rp, id, pid)
|
11
|
+
@__rp_rp, @__rp_id, @__rp_pid = rp, id, pid
|
12
|
+
end
|
13
|
+
|
14
|
+
#Returns the object as the real object transfered by using the marshal-lib.
|
15
|
+
#===Examples
|
16
|
+
# str = rp.new(:String, "Kasper") #=> <RubyProcess::ProxyObject>
|
17
|
+
# str.__rp_marshal #=> "Kasper"
|
18
|
+
def __rp_marshal
|
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
|
25
|
+
end
|
26
|
+
|
27
|
+
#Overwrite certain convert methods.
|
28
|
+
RUBY_METHODS = [:to_i, :to_s, :to_str, :to_f]
|
29
|
+
RUBY_METHODS.each do |method_name|
|
30
|
+
define_method(method_name) do |*args, &blk|
|
31
|
+
return @__rp_rp.send(cmd: :obj_method, id: @__rp_id, method: method_name, args: args, &blk).__rp_marshal
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#Overwrite certain methods.
|
36
|
+
PROXY_METHODS = [:send]
|
37
|
+
PROXY_METHODS.each do |method_name|
|
38
|
+
define_method(method_name) do |*args, &blk|
|
39
|
+
self.method_missing(method_name, *args, &blk)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#Proxies all calls to the process-object.
|
44
|
+
#===Examples
|
45
|
+
# str = rp.new(:String, "Kasper") #=> <RubyProcess::ProxyObject::1>
|
46
|
+
# length_int = str.length #=> <RubyProcess::ProxyObject::2>
|
47
|
+
# length_int.__rp_marshal #=> 6
|
48
|
+
def method_missing(method, *args, &block)
|
49
|
+
debug "Method-missing-args-before: #{args} (#{@__rp_pid})\n" if @debug
|
50
|
+
real_args = @__rp_rp.parse_args(args)
|
51
|
+
debug "Method-missing-args-after: #{real_args}\n" if @debug
|
52
|
+
|
53
|
+
return @__rp_rp.send(cmd: :obj_method, id: @__rp_id, method: method, args: real_args, &block)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,85 @@
|
|
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
|
+
# stub: ruby_process 0.0.10 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "ruby_process"
|
9
|
+
s.version = "0.0.10"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Kasper Johansen"]
|
14
|
+
s.date = "2015-04-06"
|
15
|
+
s.description = "A framework for spawning and communicating with other Ruby-processes"
|
16
|
+
s.email = "k@spernj.org"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
"Gemfile",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.md",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"cmds/blocks.rb",
|
30
|
+
"cmds/marshal.rb",
|
31
|
+
"cmds/new.rb",
|
32
|
+
"cmds/numeric.rb",
|
33
|
+
"cmds/static.rb",
|
34
|
+
"cmds/str_eval.rb",
|
35
|
+
"cmds/system.rb",
|
36
|
+
"examples/example_csv.rb",
|
37
|
+
"examples/example_file_write.rb",
|
38
|
+
"examples/example_knj_db_dump.rb",
|
39
|
+
"examples/example_strscan.rb",
|
40
|
+
"include/args_handeling.rb",
|
41
|
+
"lib/ruby_process.rb",
|
42
|
+
"lib/ruby_process_cproxy.rb",
|
43
|
+
"lib/ruby_process_proxyobj.rb",
|
44
|
+
"ruby_process.gemspec",
|
45
|
+
"scripts/ruby_process_script.rb",
|
46
|
+
"shippable.yml",
|
47
|
+
"spec/cproxy_spec.rb",
|
48
|
+
"spec/hard_load_spec.rb",
|
49
|
+
"spec/leaks_spec.rb",
|
50
|
+
"spec/RubyProcess_spec.rb",
|
51
|
+
"spec/spec_helper.rb"
|
52
|
+
]
|
53
|
+
s.homepage = "http://github.com/kaspernj/ruby_process"
|
54
|
+
s.licenses = ["MIT"]
|
55
|
+
s.rubygems_version = "2.4.0"
|
56
|
+
s.summary = "A framework for spawning and communicating with other Ruby-processes"
|
57
|
+
|
58
|
+
if s.respond_to? :specification_version then
|
59
|
+
s.specification_version = 4
|
60
|
+
|
61
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
62
|
+
s.add_runtime_dependency(%q<wref>, [">= 0"])
|
63
|
+
s.add_runtime_dependency(%q<tsafe>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
65
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
66
|
+
s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
|
67
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<wref>, [">= 0"])
|
70
|
+
s.add_dependency(%q<tsafe>, [">= 0"])
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
72
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
73
|
+
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
74
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<wref>, [">= 0"])
|
78
|
+
s.add_dependency(%q<tsafe>, [">= 0"])
|
79
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
80
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
81
|
+
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
82
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,37 @@
|
|
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
|
+
elsif match = arg.match(/--title=(.+)$/)
|
19
|
+
#ignore - its for finding process via 'ps aux'.
|
20
|
+
else
|
21
|
+
raise "Unknown argument: '#{arg}'."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
debug = true if ARGV.index("--debug") != nil
|
26
|
+
raise "No PID given of parent process." if !pid
|
27
|
+
|
28
|
+
rps = RubyProcess.new(
|
29
|
+
in: $stdin,
|
30
|
+
out: $stdout,
|
31
|
+
err: $stderr,
|
32
|
+
debug: debug,
|
33
|
+
pid: pid
|
34
|
+
)
|
35
|
+
rps.listen
|
36
|
+
$stdout.puts("ruby_process_started")
|
37
|
+
rps.join
|
data/shippable.yml
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
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
|
+
RubyProcess::ClassProxy.run do |data|
|
11
|
+
sp = data[:subproc]
|
12
|
+
str = sp.new(:String, "Wee")
|
13
|
+
str.__rp_marshal.should eq "Wee"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ts.each do |thread|
|
20
|
+
thread.join
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be able to do basic stuff" do
|
25
|
+
require "stringio"
|
26
|
+
|
27
|
+
RubyProcess::ClassProxy.run do |data|
|
28
|
+
data[:subproc].static(:Object, :require, "rubygems")
|
29
|
+
data[:subproc].static(:Object, :require, "rexml/document")
|
30
|
+
|
31
|
+
doc = RubyProcess::ClassProxy::REXML::Document.new
|
32
|
+
doc.add_element("test")
|
33
|
+
|
34
|
+
strio = StringIO.new
|
35
|
+
doc.write(strio)
|
36
|
+
|
37
|
+
Kernel.const_defined?(:REXML).should eq false
|
38
|
+
strio.string.should eq "<test/>"
|
39
|
+
end
|
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
|
+
RubyProcess::ClassProxy.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
|
+
str.__rp_marshal.should include "Kasper"
|
55
|
+
str << " More"
|
56
|
+
|
57
|
+
str.__rp_marshal.should include "Johansen"
|
58
|
+
str << " Even more"
|
59
|
+
|
60
|
+
str.__rp_marshal.should_not include "Christina"
|
61
|
+
str << " Much more"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
count = 0
|
68
|
+
ts.each do |t|
|
69
|
+
count += 1
|
70
|
+
#puts "Thread #{count}"
|
71
|
+
t.join
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not leak" do
|
76
|
+
str = "kasper"
|
77
|
+
str = nil
|
78
|
+
sleep 0.1
|
79
|
+
GC.start
|
80
|
+
sleep 0.1
|
81
|
+
|
82
|
+
count_objs = 0
|
83
|
+
ObjectSpace.each_object(RubyProcess) do |obj|
|
84
|
+
count_objs += 1
|
85
|
+
end
|
86
|
+
|
87
|
+
count_proxy_objs = 0
|
88
|
+
ObjectSpace.each_object(RubyProcess::ProxyObject) do |obj|
|
89
|
+
count_proxy_objs += 1
|
90
|
+
end
|
91
|
+
|
92
|
+
count_objs.should be <= 1
|
93
|
+
RubyProcess::ClassProxy.constants.empty?.should eq true
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
+
RubyProcess::ClassProxy.run do |data|
|
6
|
+
sp = data[:subproc]
|
7
|
+
sp.new(:String, "Wee")
|
8
|
+
|
9
|
+
ts = []
|
10
|
+
|
11
|
+
1.upto(50) do |tcount|
|
12
|
+
ts << Thread.new do
|
13
|
+
1.upto(250) do
|
14
|
+
str = sp.new(:String, "Kasper Johansen")
|
15
|
+
|
16
|
+
str.__rp_marshal.should eq "Kasper Johansen"
|
17
|
+
str << " More"
|
18
|
+
|
19
|
+
str.__rp_marshal.should include "Johansen"
|
20
|
+
str << " Even more"
|
21
|
+
|
22
|
+
str.__rp_marshal.should_not include "Christina"
|
23
|
+
str << " Much more"
|
24
|
+
|
25
|
+
str.__rp_marshal.should eq "Kasper Johansen More Even more Much more"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
ts.each do |t|
|
31
|
+
t.join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/spec/leaks_spec.rb
ADDED
@@ -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
|
+
RubyProcess::ClassProxy.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
|
+
answers.should be_empty
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|