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