RubyProcess 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|