RubyProcess 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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