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.
@@ -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
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ script:
5
+ - CODECLIMATE_REPO_TOKEN=32061319cc6d2e0bd0d0eb88afdc6d7b68829b357ca76efb99f86d7a1a89539e bundle exec rspec
6
+ notifications:
7
+ email: false
@@ -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
@@ -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