dont-stall-my-process 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bba5b99c3f45cf21b973302a4b9d43711045d8e0
4
- data.tar.gz: b5754b5ebf28641cb7968a6a117e17c6fbf6f43e
3
+ metadata.gz: 68d9fbd15fa53138d40bf1c042bbc284421fade2
4
+ data.tar.gz: 262a38004cf7b4f8ecddbb49d370aae5343d5e6b
5
5
  SHA512:
6
- metadata.gz: d3c8179f80acb4dbbb5a84311d18fa0625dd4a5980cfe4692d74c32cba4555b68de05d12caec8879f4b239f936185b7e6e864b49ee25d0dfc753b026b0f45e65
7
- data.tar.gz: 5b56b6b4699377beebcd92479930b31390897976b46763f8d6f222032ece2404e05cc32cda0be096fe28ba9425155ea9ae19ac3630ac6471e4c89440a44dfb68
6
+ metadata.gz: 7fe43327afdc8e662ae41b40397a3395f097ccf81ff175321e544585d6250c39d57174d989120b4c38ca8be44d6c6a28809136006af2bd2ab20b3f6f636af220
7
+ data.tar.gz: 9cf418fc795e9382b0849731ffc347debfb427619ef725e05b55b28f4a3c952e6c3cb499906e156ab275957f66f07a6ad67500a4ffc4370165d83528c7a19312
@@ -1,26 +1,51 @@
1
+ require 'dont-stall-my-process/configuration'
2
+ require 'dont-stall-my-process/exceptions'
3
+ require 'dont-stall-my-process/proxy_registry'
4
+ require 'dont-stall-my-process/version'
1
5
  require 'dont-stall-my-process/local/child_process'
6
+ require 'dont-stall-my-process/local/child_process_pool'
2
7
  require 'dont-stall-my-process/local/local_proxy'
8
+ require 'dont-stall-my-process/local/process_exit_handler'
3
9
  require 'dont-stall-my-process/remote/remote_application'
10
+ require 'dont-stall-my-process/remote/remote_application_controller'
4
11
  require 'dont-stall-my-process/remote/remote_proxy'
5
- require 'dont-stall-my-process/version'
6
12
 
7
13
  module DontStallMyProcess
8
- DEFAULT_TIMEOUT = 300
14
+ def self.configure
15
+ yield Configuration.get if block_given?
16
+ end
9
17
 
10
- def self.create(klass, opts = {}, sigkill_only = false)
18
+ def self.create(klass, opts = {})
11
19
  fail 'no klass given' unless klass && klass.is_a?(Class)
12
20
 
13
21
  # Set default values and validate configuration.
14
22
  opts = sanitize_options(opts)
15
23
 
24
+ # Start a local DRbServer (unnamed?) for callbacks (blocks!).
25
+ # Each new DontStallMyProcess object will overwrite the main master DRbServer.
26
+ # This looks weird, but doesn't affect concurrent uses of multiple
27
+ # Watchdogs, I tested it. Trust me.
28
+ DRb.start_service
29
+
16
30
  # Fork the child process.
17
- p = Local::ChildProcess.new(klass, opts)
31
+ process = Local::ChildProcessPool.alloc
32
+
33
+ # Start the DRb service for the main class and create a proxy.
34
+ proxy = process.start_service(klass, opts)
18
35
 
19
- # Return the main proxy class.
20
- Local::MainLocalProxy.new(p, opts, sigkill_only)
36
+ # If a block is given, we finalize the service immediately after its return.
37
+ if block_given?
38
+
39
+ yield proxy
40
+
41
+ proxy.stop_service!
42
+ proxy = nil
43
+ end
44
+
45
+ proxy
21
46
  end
22
47
 
23
- def self.sanitize_options(opts, default_timeout = DEFAULT_TIMEOUT)
48
+ def self.sanitize_options(opts, default_timeout = Configuration::DEFAULT_TIMEOUT)
24
49
  fail 'opts is not a hash' unless opts.is_a?(Hash)
25
50
 
26
51
  opts[:timeout] ||= default_timeout
@@ -37,12 +62,4 @@ module DontStallMyProcess
37
62
  ]
38
63
  }
39
64
  end
40
-
41
- # This exception is raised when the watchdog bites.
42
- class TimeoutExceeded < StandardError; end
43
-
44
- # This exception is raised when a forbidden Kernel method is called.
45
- # These methods do not get forwarded over the DRb.
46
- class KernelMethodCalled < StandardError; end
47
-
48
65
  end
@@ -0,0 +1,36 @@
1
+ module DontStallMyProcess
2
+ class Configuration
3
+ DEFAULT_TIMEOUT = 300
4
+
5
+ ATTRIBUTES = [:sigkill_only, :close_stdio, :restore_all_traps, :skip_at_exit_handlers,
6
+ :process_pool_size, :subprocess_name, :after_fork_handler]
7
+ attr_writer *(ATTRIBUTES - [:after_fork_handler])
8
+
9
+ def initialize
10
+ @sigkill_only = false
11
+ @close_stdio = true
12
+ @restore_all_traps = false
13
+ @skip_at_exit_handlers = false
14
+ @process_pool_size = nil
15
+ @after_fork_handler = Proc.new {}
16
+ @subprocess_name = nil
17
+ end
18
+
19
+ def after_fork(p = nil, &block)
20
+ fail 'after_fork needs a block or Proc object' unless (p && p.is_a?(Proc)) || block_given?
21
+ @after_fork_handler = p || block
22
+ end
23
+
24
+ class << self
25
+ def get
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ ATTRIBUTES.each do |a|
30
+ define_method(a) do
31
+ get.instance_variable_get("@#{a}")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ module DontStallMyProcess
2
+ DontStallMyProcessError = Class.new(StandardError)
3
+
4
+ # This exception is raised when the watchdog bites.
5
+ TimeoutExceeded = Class.new(StandardError)
6
+
7
+ # This exception is raised when the subprocess could not be created, or
8
+ # its initialization failed.
9
+ SubprocessInitializationFailed = Class.new(StandardError)
10
+
11
+ # This exception is raised when a forbidden Kernel method is called.
12
+ # These methods do not get forwarded over the DRb.
13
+ KernelMethodCalled = Class.new(StandardError)
14
+ end
@@ -4,48 +4,90 @@ module DontStallMyProcess
4
4
  # The ChildProcess class encapsulates the forked subprocess that
5
5
  # provides the DRb service object.
6
6
  class ChildProcess
7
- attr_reader :main_uri
7
+ attr_reader :pid
8
8
 
9
- def initialize(klass, opts)
9
+ def initialize
10
10
  # Start RemoteApplication in child process, connect to it thru pipe.
11
11
  r, w = IO.pipe
12
12
  @pid = fork do
13
13
  r.close
14
- $stdin.close rescue nil
15
- $stdout.reopen('/dev/null', 'w')
16
- $stderr.reopen('/dev/null', 'w')
17
- DontStallMyProcess::Remote::RemoteApplication.new(klass, opts, w).loop!
14
+
15
+ # Disable process cleanup, otherwise killing the
16
+ # subprocess (when Configuration.skip_at_exit_handlers is false) will
17
+ # terminate all the other subprocesses, or at least screw with their
18
+ # unix sockets.
19
+ ProcessExitHandler.disable_at_exit
20
+
21
+ app = DontStallMyProcess::Remote::RemoteApplication.new(w)
22
+ app.loop!
18
23
  end
19
24
  w.close
20
25
 
21
- # Wait for the RemoteApplication to finish its setup.
22
- @main_uri = Marshal.load(r.gets)
23
- r.close
26
+ # Wait for the RemoteApplication to finish its setup, and retrieve
27
+ # the URI to the RemoteApplicationController.
28
+ ctrl_uri = Marshal.load(r)
24
29
 
25
- # RemoteApplication sends us the DRb URI or an Exception.
26
- raise @main_uri if @main_uri.is_a?(Exception)
27
- end
30
+ # RemoteApplication sends us the URI or an Exception instance if anything
31
+ # went wrong.
32
+ if ctrl_uri.is_a?(Exception)
33
+ e = SubprocessInitializationFailed.new(ctrl_uri)
34
+ e.set_backtrace(ctrl_uri.backtrace)
35
+ raise e
36
+ end
37
+
38
+ # Connect to and store the controller DRb client.
39
+ @controller = DRbObject.new_with_uri(ctrl_uri)
40
+
41
+ # Setup LocalProxy registry for this pid.
42
+ LocalProxy.setup_proxy_registry(@pid) do
43
+ # This block will be called when all LocalProxies
44
+ # of this pid are gone (either garbage-collected or
45
+ # manually destroyed by 'stop_service!').
46
+
47
+ # Hand back ourself to the ChildProcessPool to get new jobs.
48
+ ChildProcessPool.free(self)
49
+ end
28
50
 
29
- def term
30
- # Exceptions in these methods almost always mean the process is already dead.
31
- Process.kill('TERM', @pid) rescue nil
51
+ @alive = true
52
+ ensure
53
+ r.close
32
54
  end
33
55
 
34
- def detach
35
- Process.detach(@pid) rescue nil
56
+ def start_service(klass, opts)
57
+ uri = @controller.start_service(klass, opts)
58
+
59
+ # Create the main proxy class.
60
+ Local::LocalProxy.new(uri, self, opts)
36
61
  end
37
62
 
38
- def kill
39
- Process.kill('KILL', @pid) rescue nil
63
+ def alive?
64
+ @alive && @controller.alive? rescue false
40
65
  end
41
66
 
42
- def wait
43
- Process.wait(@pid) rescue nil
67
+ def quit
68
+ @controller.stop_application rescue nil
69
+ @controller = nil
70
+ sleep 0.5
71
+ terminate(false, 0.5)
44
72
  end
45
73
 
46
- def alive?
74
+ def terminate(sigkill = true, term_sleep = 5)
75
+ unless Configuration.sigkill_only
76
+ Process.kill('TERM', @pid)
77
+ sleep term_sleep
78
+ end
79
+
47
80
  # http://stackoverflow.com/questions/325082/how-can-i-check-from-ruby-whether-a-process-with-a-certain-pid-is-running
48
- Process.waitpid(@pid, Process::WNOHANG).nil?
81
+ Process.kill('KILL', @pid) if sigkill && Process.waitpid(@pid, Process::WNOHANG).nil?
82
+
83
+ # Collect process status to not have a zombie hanging around.
84
+ Process.wait(@pid)
85
+
86
+ # Do not reuse this process ever again.
87
+ @alive = false
88
+ rescue
89
+ # Exceptions in these Process.* methods almost always mean the process is already dead.
90
+ nil
49
91
  end
50
92
  end
51
93
 
@@ -0,0 +1,52 @@
1
+ module DontStallMyProcess
2
+ module Local
3
+ class ChildProcessPool
4
+ MAX_ALLOC_TRIES = 5
5
+ SEMAPHORE = Mutex.new
6
+
7
+ def self.alloc
8
+ process = nil
9
+ tries = MAX_ALLOC_TRIES
10
+ until (process && process.alive?) || tries == 0
11
+ tries -= 1
12
+ SEMAPHORE.synchronize do
13
+ if !@pool || @pool.empty?
14
+ process = ChildProcess.new
15
+ else
16
+ process = @pool.shift
17
+ end
18
+ end
19
+ end
20
+
21
+ unless process && process.alive?
22
+ fail SubprocessInitializationFailed, "failed to allocated subprocess (tried #{MAX_ALLOC_TRIES} times)"
23
+ end
24
+
25
+ process
26
+ end
27
+
28
+ def self.free(process)
29
+ # We do not want killed processes to enter the pool again.
30
+ return unless process.alive?
31
+
32
+ terminate = true
33
+
34
+ if Configuration.process_pool_size && Configuration.process_pool_size > 0
35
+ SEMAPHORE.synchronize do
36
+ @pool ||= []
37
+ if @pool.size < Configuration.process_pool_size
38
+ @pool << process
39
+ terminate = false
40
+ end
41
+ end
42
+ end
43
+
44
+ process.quit if terminate
45
+ end
46
+
47
+ def self.each(&block)
48
+ @pool.each(&block) if @pool
49
+ end
50
+ end
51
+ end
52
+ end
@@ -6,14 +6,39 @@ module DontStallMyProcess
6
6
 
7
7
  # LocalProxy connects to an instance of a class on a child process
8
8
  # and watches the execution of remote procedure calls.
9
+ # Furthermore, it takes care of automatically ending the remote DRb
10
+ # service when the proxy object gets garbage-collected.
9
11
  class LocalProxy
10
- def initialize(uri, process, opts, sigkill_only)
11
- @process = process
12
- @opts = opts
13
- @sigkill_only = sigkill_only
12
+ class << self
13
+ include DontStallMyProcess::ProxyRegistry
14
+
15
+ def gc_finalize_proxy(pid, uri)
16
+ LocalProxy.unregister(pid, proxy_uri) { |proxy| proxy.__destroy }
17
+ end
18
+
19
+ def gc_finalize_proxy_proc(pid, uri)
20
+ # http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
21
+ proc { LocalProxy.gc_finalize_proxy(pid, uri) }
22
+ end
23
+ end
24
+
25
+ def initialize(uri, process, opts)
26
+ @uri = uri
27
+ @process = process
28
+ @opts = opts
14
29
 
15
30
  # Get a DRb reference to the remote class.
16
- @object = DRbObject.new_with_uri(uri)
31
+ @object = DRbObject.new_with_uri(uri)
32
+
33
+ # Store this proxy in the registry for book-keeping.
34
+ LocalProxy.register(process.pid, uri, self)
35
+
36
+ # Destroy the proxy on GC.
37
+ ObjectSpace.define_finalizer(self, self.class.gc_finalize_proxy_proc(process.pid, uri))
38
+ end
39
+
40
+ def stop_service!
41
+ LocalProxy.each_proxy(@process.pid) { |proxy| proxy.send(:__destroy) }
17
42
  end
18
43
 
19
44
  def respond_to?(m, ia = false)
@@ -35,18 +60,12 @@ module DontStallMyProcess
35
60
  end
36
61
  end
37
62
 
38
- private
39
-
40
- def __create_nested_proxy(meth, *args, &block)
41
- # Get the DRb URI from the remote.
42
- uri = __timed(meth) { @object.public_send(meth, *args, &block) }
43
-
44
- # Create a new local proxy and return that.
45
- # Note: We do not need to cache these here, as there can be multiple
46
- # clients to a single DRb service.
47
- LocalProxy.new(uri, @process, @opts[:methods][meth], @sigkill_only)
63
+ def inspect
64
+ self.to_s.gsub('>', " URI=#{@uri} CHILD_PID=#{@process.pid}>")
48
65
  end
49
66
 
67
+ private
68
+
50
69
  def __delegate_with_timeout(meth, *args, &block)
51
70
  __timed(meth) do
52
71
  @object.public_send(meth, *args, &block)
@@ -58,55 +77,23 @@ module DontStallMyProcess
58
77
  yield
59
78
  end
60
79
  rescue Timeout::Error
61
- __kill_child_process!
80
+ @process.terminate
62
81
  fail TimeoutExceeded, "Method #{meth} took more than #{@opts[:timeout]} seconds to process! Child process killed."
63
82
  end
64
83
 
65
- def __kill_child_process!
66
- unless @sigkill_only
67
- Sidekiq.logger.info "TERMINATIIIING"
68
- @process.term
69
- sleep 5
70
- end
71
-
72
- @process.detach
73
- @process.kill if @process.alive?
74
- end
75
- end
76
-
77
- # MainLocalProxy encapsulates the main DRb client, i.e. the top-level
78
- # client class requested by the user. It takes care of initially starting
79
- # the DRb service for callbacks and cleaning up child processes on
80
- # garbage collection.
81
- class MainLocalProxy < LocalProxy
82
- def self.register_remote_proxy(main_uri, object)
83
- @objects ||= {}
84
- @objects[main_uri] = object
85
- end
86
-
87
- def self.stop_remote_application(main_uri)
88
- @objects[main_uri].stop! rescue nil
89
- @process.wait
90
- end
84
+ def __create_nested_proxy(meth, *args, &block)
85
+ # Get the DRb URI from the remote.
86
+ uri = __timed(meth) { @object.public_send(meth, *args, &block) }
91
87
 
92
- def self.stop_remote_application_proc(main_uri)
93
- # See also: http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
94
- proc { MainLocalProxy.stop_remote_application(main_uri) }
88
+ # Create a new local proxy and return that.
89
+ LocalProxy.new(uri, @process, @opts[:methods][meth])
95
90
  end
96
91
 
97
- def initialize(process, opts, sigkill_only)
98
- # Start a local DRbServer (unnamed?) for callbacks (blocks!).
99
- # Each new Watchdog will overwrite the main master DRbServer.
100
- # This looks weird, but doesn't affect concurrent uses of multiple
101
- # Watchdogs, I tested it. Trust me.
102
- DRb.start_service
103
-
104
- # Initialize the base class, connect to the DRb service or the main client class.
105
- super(process.main_uri, process, opts, sigkill_only)
92
+ def __destroy
93
+ # We rescue the exception here in case the subprocess is already dead.
94
+ @object.__local_proxy_destroyed rescue nil
106
95
 
107
- # Stop the child process on GC.
108
- MainLocalProxy.register_remote_proxy(process.main_uri, @object)
109
- ObjectSpace.define_finalizer(self, self.class.stop_remote_application_proc(process.main_uri))
96
+ LocalProxy.unregister(@process.pid, @uri)
110
97
  end
111
98
  end
112
99
  end
@@ -0,0 +1,30 @@
1
+ require 'fileutils'
2
+
3
+ module DontStallMyProcess
4
+ module Local
5
+ class ProcessExitHandler
6
+ def self.disable_at_exit
7
+ @at_exit_disabled = true
8
+ end
9
+
10
+ def self.at_exit_disabled?
11
+ @at_exit_disabled
12
+ end
13
+
14
+ at_exit do
15
+ # If we're in a subprocess, this handler should not run.
16
+ unless ProcessExitHandler.at_exit_disabled?
17
+
18
+ # Make sure we terminate all subprocesses when
19
+ # the main process exits.
20
+ ChildProcessPool.each do |process|
21
+ process.quit
22
+ end
23
+
24
+ # Clean remaining unix sockets from /tmp.
25
+ FileUtils.rm(Dir["/tmp/dsmp-#{Process.pid}*"])
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module DontStallMyProcess
2
+ module ProxyRegistry
3
+ def setup_proxy_registry(pid, &block)
4
+ @empty_handler ||= {}
5
+ @empty_handler[pid] = block
6
+
7
+ @proxies ||= {}
8
+ @proxies[pid] = {}
9
+ end
10
+
11
+ def register(pid, key, object)
12
+ @proxies[pid][key] = object
13
+ end
14
+
15
+ def unregister(pid, key)
16
+ if @proxies[pid].key?(key) && block_given?
17
+ yield @proxies[pid]
18
+ end
19
+ @proxies[pid].delete(key)
20
+
21
+ # Check if all proxies are gone now and call the process.
22
+ @empty_handler[pid].call if @proxies[pid].empty?
23
+ end
24
+
25
+ def each_proxy(pid, &block)
26
+ @proxies[pid].dup.values.each(&block)
27
+ end
28
+ end
29
+ end
@@ -4,18 +4,62 @@ module DontStallMyProcess
4
4
  # RemoteObject is the base 'application' class for the child process.
5
5
  # It starts the DRb service and goes to sleep.
6
6
  class RemoteApplication
7
- def initialize(klass, opts, pipe)
7
+ def self.update_process_name(klass_name = '<pool>')
8
+ if Configuration.subprocess_name
9
+ $0 = Configuration.subprocess_name % { klass: klass_name.to_s }
10
+ end
11
+ end
12
+
13
+ def initialize(pipe)
14
+ # Do not write to stdout/stderr unless requested by the client.
15
+ if Configuration.close_stdio
16
+ $stdout.reopen('/dev/null', 'w')
17
+ $stderr.reopen('/dev/null', 'w')
18
+ end
19
+
20
+ # Reset signal handlers if requested by client.
21
+ if Configuration.restore_all_traps
22
+ # Plenty of signals are not trappable, simply ignore this here.
23
+ Signal.list.keys.each { |sig| Signal.trap(sig, 'DEFAULT') rescue nil }
24
+ end
25
+
26
+ if Configuration.skip_at_exit_handlers
27
+ # Clearing the Ruby end proc list is not possible without modifying
28
+ # Ruby itself, see eval_jump.c in current Ruby sources. However,
29
+ # as these handlers get executed in reverse order, simply calling
30
+ # exit! here is enough.
31
+ at_exit { exit! }
32
+ end
33
+
34
+ # Call the after_block_handler early, before DRb setup (i.e. before anything
35
+ # can go wrong).
36
+ Configuration.after_fork_handler.call
37
+
38
+ # Initially display the process name without a class name (-> '<pool>').
39
+ RemoteApplication.update_process_name
40
+
41
+ RemoteProxy.setup_proxy_registry(Process.pid) do
42
+ # This block is called when all RemoteProxies have closed there
43
+ # DRb servers. We're now idling again, waiting for new jobs in the
44
+ # pool *or* are going to be be terminated in a second.
45
+
46
+ # Update process name again to indicate availability.
47
+ RemoteApplication.update_process_name
48
+ end
49
+
50
+ # Start our controller class, expose via DRb.
51
+ @controller = RemoteApplicationController.new(self)
52
+
53
+ # Everything went fine, set up the main process synchronization now.
8
54
  @m = Mutex.new
9
55
  @c = ConditionVariable.new
10
56
 
11
- # Start the main DRb service.
12
- proxy = MainRemoteProxy.new(self, klass, opts)
13
-
14
57
  # Tell our parent that setup is done and the new main DRb URI.
15
- pipe.write(Marshal.dump(proxy.uri))
58
+ Marshal.dump(@controller.uri, pipe)
16
59
  rescue => e
17
60
  # Something went wrong, also tell our parent.
18
- pipe.write(Marshal.dump(e))
61
+ Marshal.dump(e, pipe)
62
+ raise
19
63
  ensure
20
64
  pipe.close
21
65
  end
@@ -25,17 +69,17 @@ module DontStallMyProcess
25
69
  @m.synchronize do
26
70
  @c.wait(@m)
27
71
  end
72
+
73
+ # Remote application end.
74
+ exit 0
75
+ rescue SystemExit, Interrupt
76
+ raise
28
77
  end
29
78
 
30
79
  def stop!
31
- Thread.new do
32
- # Wait for DRb answer package to be sent.
33
- sleep 0.2
34
-
35
- # End main thread -> exit.
36
- @m.synchronize do
37
- @c.signal
38
- end
80
+ # End main thread -> exit.
81
+ @m.synchronize do
82
+ @c.signal
39
83
  end
40
84
  end
41
85
  end
@@ -0,0 +1,55 @@
1
+ require 'fileutils'
2
+ require 'drb'
3
+
4
+ module DontStallMyProcess
5
+ module Remote
6
+ class RemoteApplicationController
7
+ attr_reader :uri
8
+
9
+ def initialize(application)
10
+ @applicarion = application
11
+
12
+ @uri = "drbunix:///tmp/dsmp-#{Process.ppid}-controller-#{Process.pid}"
13
+ @server = DRb.start_service(uri, self)
14
+ end
15
+
16
+ def start_service(klass, opts)
17
+ # Instantiate the main class now to get early failures.
18
+ instance = klass.new
19
+
20
+ # Start the main DRb service.
21
+ @proxy = RemoteProxy.new(opts, instance)
22
+
23
+ # Set subprocess name if requested.
24
+ RemoteApplication.update_process_name(klass.name.to_s)
25
+
26
+ # Return the DRb URI.
27
+ @proxy.uri
28
+ end
29
+
30
+ def stop_application
31
+ # Kill remaining DRb servers, shouldn't be any at this point.
32
+ RemoteProxy.each_proxy { |proxy| __destroy.destroy }
33
+
34
+ Thread.new do
35
+ # Wait for DRb answer package to be sent.
36
+ sleep 0.2
37
+
38
+ # Kill our own DRb server.
39
+ @server.stop_service
40
+ FileUtils.rm_f(@uri)
41
+
42
+ # Let DRb resolve its pthread mutexes and stuff.
43
+ sleep 0.2
44
+
45
+ # Wake up the main application thread.
46
+ @application.stop!
47
+ end
48
+ end
49
+
50
+ def alive?
51
+ true
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,3 +1,4 @@
1
+ require 'fileutils'
1
2
  require 'drb'
2
3
  require 'securerandom'
3
4
 
@@ -9,15 +10,24 @@ module DontStallMyProcess
9
10
  # instance of the 'real' class. Furthermore, it takes care of creating
10
11
  # nested DRb services as requested in the option hash.
11
12
  class RemoteProxy
13
+ class << self
14
+ include DontStallMyProcess::ProxyRegistry
15
+ end
16
+
12
17
  attr_reader :uri
13
18
 
14
- def initialize(opts, instance)
19
+ def initialize(opts, instance, parent = nil)
15
20
  @opts = opts
16
21
  @object = instance
17
- @children = {}
18
22
 
19
- @uri = "drbunix:///tmp/dsmp-#{SecureRandom.hex(8)}"
20
- DRb.start_service(uri, self)
23
+ @uri = "drbunix:///tmp/dsmp-#{Process.ppid}-#{SecureRandom.hex(8)}"
24
+ @server = DRb.start_service(@uri, self)
25
+
26
+ RemoteProxy.register(Process.pid, @uri, self)
27
+ end
28
+
29
+ def __local_proxy_destroyed
30
+ __destroy
21
31
  end
22
32
 
23
33
  def respond_to?(m, ia = false)
@@ -39,37 +49,18 @@ module DontStallMyProcess
39
49
  private
40
50
 
41
51
  def __create_nested_proxy(meth, *args, &block)
42
- # Create a new DRb proxy if needed, and save its URI.
43
- unless @children[meth]
44
- # Call the object method now, save the returned object.
45
- instance = @object.public_send(meth, *args, &block)
52
+ instance = @object.public_send(meth, *args, &block)
46
53
 
47
- # Start the proxy, convert the object 0into a DRb service.
48
- @children[meth] = RemoteProxy.new(@opts[:methods][meth], instance).uri
49
- end
50
-
51
- # Return the DRb URI.
52
- @children[meth]
54
+ # Start the proxy, convert the object into a DRb service.
55
+ RemoteProxy.new(@opts[:methods][meth], instance, self).uri
53
56
  end
54
- end
55
57
 
56
- # The MainRemoteProxy is the first DRb object to be created in
57
- # the child process. In addition to the real class' methods it
58
- # provides a 'stop!' method that brings down the child process
59
- # gracefully.
60
- class MainRemoteProxy < RemoteProxy
61
- def initialize(mother, klass, opts)
62
- @mother = mother
58
+ def __destroy
59
+ @server.stop_service
60
+ FileUtils.rm_f(@uri)
63
61
 
64
- # Instantiate the main class now, initialize the base class with
65
- # the new instance, create the DRb service.
66
- super(opts, klass.new)
67
- end
68
-
69
- def stop!
70
- @mother.stop!
62
+ RemoteProxy.unregister(Process.pid, @uri)
71
63
  end
72
64
  end
73
-
74
65
  end
75
66
  end
@@ -1,3 +1,3 @@
1
1
  module DontStallMyProcess
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dont-stall-my-process
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - FlavourSys Technology GmbH
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-05 00:00:00.000000000 Z
11
+ date: 2014-09-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Executes code or native extensions in child processes and monitors their
14
14
  execution times
@@ -18,9 +18,15 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - lib/dont-stall-my-process.rb
21
+ - lib/dont-stall-my-process/configuration.rb
22
+ - lib/dont-stall-my-process/exceptions.rb
21
23
  - lib/dont-stall-my-process/local/child_process.rb
24
+ - lib/dont-stall-my-process/local/child_process_pool.rb
22
25
  - lib/dont-stall-my-process/local/local_proxy.rb
26
+ - lib/dont-stall-my-process/local/process_exit_handler.rb
27
+ - lib/dont-stall-my-process/proxy_registry.rb
23
28
  - lib/dont-stall-my-process/remote/remote_application.rb
29
+ - lib/dont-stall-my-process/remote/remote_application_controller.rb
24
30
  - lib/dont-stall-my-process/remote/remote_proxy.rb
25
31
  - lib/dont-stall-my-process/version.rb
26
32
  homepage: http://github.com/flavoursys/dont-stall-my-process