dont-stall-my-process 0.0.1 → 0.1.1

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 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