autorespawn 0.2.1 → 0.3.0
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 +4 -4
- data/.gitattributes +1 -0
- data/Gemfile +2 -1
- data/autorespawn.gemspec +1 -0
- data/lib/autorespawn.rb +81 -55
- data/lib/autorespawn/hooks.rb +26 -0
- data/lib/autorespawn/manager.rb +144 -8
- data/lib/autorespawn/program_id.rb +41 -20
- data/lib/autorespawn/self.rb +25 -0
- data/lib/autorespawn/slave.rb +22 -3
- data/lib/autorespawn/version.rb +1 -1
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24740ded8fd2be27ffb5f7bd0d6e33f4b71f11f6
|
4
|
+
data.tar.gz: 32036b70931a245bc4e1c12b70446160e439123b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf53297c07ebeb7edc339f60e961e9159da08b46275aa51cc8636002f08b37ec1a4b7410e4cd964b9be7c7fb6eb4540458427701739f464af7edc78f3f5cbee4
|
7
|
+
data.tar.gz: eba4eff1d2c3d1c3ee07e80c541584ceede4231e6b20952a4c324fcd9d1e0b3a19141328906a5a10299b15cffbca961c53435bcd76a435774a3505f5ce6e4019
|
data/.gitattributes
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.rb diff=ruby
|
data/Gemfile
CHANGED
data/autorespawn.gemspec
CHANGED
@@ -25,6 +25,7 @@ EOD
|
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
26
|
spec.require_paths = ["lib"]
|
27
27
|
|
28
|
+
spec.add_dependency "hooks", ">= 0.4.1"
|
28
29
|
spec.add_development_dependency "bundler", "~> 1.10"
|
29
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
31
|
spec.add_development_dependency "minitest", ">= 5.0", "~> 5.0"
|
data/lib/autorespawn.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'hooks'
|
3
|
+
require 'autorespawn/hooks'
|
2
4
|
require "autorespawn/version"
|
3
5
|
require "autorespawn/exceptions"
|
4
6
|
require "autorespawn/program_id"
|
5
7
|
require "autorespawn/watch"
|
6
8
|
require "autorespawn/slave"
|
9
|
+
require "autorespawn/self"
|
7
10
|
require "autorespawn/manager"
|
8
11
|
|
9
12
|
# Automatically exec's the current program when one of the source file changes
|
@@ -25,31 +28,53 @@
|
|
25
28
|
# passed as-is to Kernel.spawn and Kernel.exec
|
26
29
|
# @param options keyword options to pass to Kernel.spawn and Kernel.exec
|
27
30
|
class Autorespawn
|
31
|
+
include Hooks
|
32
|
+
include Hooks::InstanceHooks
|
33
|
+
|
28
34
|
INITIAL_STATE_FD = "AUTORESPAWN_AUTORELOAD"
|
29
35
|
|
30
36
|
SLAVE_RESULT_ENV = 'AUTORESPAWN_SLAVE_RESULT_FD'
|
31
37
|
SLAVE_INITIAL_STATE_ENV = 'AUTORESPAWN_SLAVE_INITIAL_STATE_FD'
|
32
38
|
|
39
|
+
# ID object
|
40
|
+
#
|
41
|
+
# An arbitrary object passed to {#initialize} or {#add_slave} to identify
|
42
|
+
# this process.
|
43
|
+
#
|
44
|
+
# @return [nil,Object]
|
45
|
+
def self.name
|
46
|
+
@name
|
47
|
+
end
|
33
48
|
def self.slave_result_fd
|
34
49
|
@slave_result_fd
|
35
50
|
end
|
36
|
-
def self.slave_initial_state_fd
|
37
|
-
@slave_initial_state_fd
|
38
|
-
end
|
39
51
|
def self.slave?
|
40
52
|
!!slave_result_fd
|
41
53
|
end
|
42
|
-
|
43
|
-
|
44
|
-
slave_initial_state_fd = ENV.delete(SLAVE_INITIAL_STATE_ENV)
|
45
|
-
slave_result_fd = ENV.delete(SLAVE_RESULT_ENV)
|
46
|
-
|
47
|
-
if slave_initial_state_fd
|
48
|
-
@slave_initial_state_fd = Integer(slave_initial_state_fd)
|
54
|
+
def self.initial_program_id
|
55
|
+
@initial_program_id
|
49
56
|
end
|
50
|
-
|
51
|
-
|
57
|
+
|
58
|
+
def self.read_child_state
|
59
|
+
# Delete the envvars first, we really don't want them to leak
|
60
|
+
slave_initial_state_fd = ENV.delete(SLAVE_INITIAL_STATE_ENV)
|
61
|
+
slave_result_fd = ENV.delete(SLAVE_RESULT_ENV)
|
62
|
+
if slave_initial_state_fd
|
63
|
+
slave_initial_state_fd = Integer(slave_initial_state_fd)
|
64
|
+
io = IO.for_fd(slave_initial_state_fd)
|
65
|
+
@name, @initial_program_id = Marshal.load(io)
|
66
|
+
io.close
|
67
|
+
end
|
68
|
+
if slave_result_fd
|
69
|
+
@slave_result_fd = Integer(slave_result_fd)
|
70
|
+
end
|
52
71
|
end
|
72
|
+
read_child_state
|
73
|
+
|
74
|
+
# An arbitrary objcet that can be used to identify the processes/slaves
|
75
|
+
#
|
76
|
+
# @return [nil,Object]
|
77
|
+
attr_reader :name
|
53
78
|
|
54
79
|
# The arguments that should be passed to Kernel.exec in standalone mode
|
55
80
|
#
|
@@ -58,10 +83,26 @@ def self.slave?
|
|
58
83
|
# @return [(Array,Hash)]
|
59
84
|
attr_reader :process_command_line
|
60
85
|
|
61
|
-
#
|
86
|
+
# @!group Hooks
|
87
|
+
|
88
|
+
# @!method on_exception
|
89
|
+
#
|
90
|
+
# Register a callback that is called whenever an exception is rescued by
|
91
|
+
# {#watch_yield}
|
92
|
+
#
|
93
|
+
# @yieldparam [Exception] exception
|
94
|
+
define_hooks :on_exception
|
95
|
+
|
96
|
+
# @!method at_exit
|
97
|
+
#
|
98
|
+
# Register a callback that is called after the block passed to {#run} has
|
99
|
+
# been called, but before the process gets respawned. Meant to perform what
|
100
|
+
# hass been done in {#run} that should be cleaned before respawning.
|
62
101
|
#
|
63
|
-
# @
|
64
|
-
|
102
|
+
# @yieldparam [Exception] exception
|
103
|
+
define_hooks :at_respawn
|
104
|
+
|
105
|
+
# @!endgroup
|
65
106
|
|
66
107
|
# @return [ProgramID] object currently known state of files makind this
|
67
108
|
# program
|
@@ -87,13 +128,15 @@ def self.slave?
|
|
87
128
|
# In master/slave mode, the list of subcommands that the master should spawn
|
88
129
|
attr_reader :subcommands
|
89
130
|
|
90
|
-
def initialize(*command, track_current: false, **options)
|
131
|
+
def initialize(*command, name: Autorespawn.name, track_current: false, **options)
|
91
132
|
if command.empty?
|
92
133
|
command = [$0, *ARGV]
|
93
134
|
end
|
135
|
+
@name = name
|
136
|
+
@program_id = Autorespawn.initial_program_id ||
|
137
|
+
ProgramID.new
|
138
|
+
|
94
139
|
@process_command_line = [command, options]
|
95
|
-
@respawn_handlers = Array.new
|
96
|
-
@program_id = ProgramID.new
|
97
140
|
@exceptions = Array.new
|
98
141
|
@required_paths = Set.new
|
99
142
|
@error_paths = Set.new
|
@@ -104,18 +147,6 @@ def initialize(*command, track_current: false, **options)
|
|
104
147
|
end
|
105
148
|
end
|
106
149
|
|
107
|
-
# Returns true if there is an initial state dump
|
108
|
-
def has_initial_state?
|
109
|
-
!!Autorespawn.slave_initial_state_fd
|
110
|
-
end
|
111
|
-
|
112
|
-
# Loads the initial state from STDIN
|
113
|
-
def load_initial_state
|
114
|
-
io = IO.for_fd(Autorespawn.slave_initial_state_fd)
|
115
|
-
@program_id = Marshal.load(io)
|
116
|
-
io.close
|
117
|
-
end
|
118
|
-
|
119
150
|
# Requires one file under the autorespawn supervision
|
120
151
|
#
|
121
152
|
# If the require fails, the call to {.run} will not execute its block,
|
@@ -134,8 +165,20 @@ def watch_yield
|
|
134
165
|
raise
|
135
166
|
rescue Exception => e
|
136
167
|
new_exceptions << e
|
168
|
+
run_hook :on_exception, e
|
137
169
|
exceptions << e
|
138
|
-
|
170
|
+
# cross-drb exceptions are broken w.r.t. #backtrace_locations. It
|
171
|
+
# returns a string in their case. Since it happens only on
|
172
|
+
# exceptions that originate from the server (which means a broken
|
173
|
+
# Roby codepath), let's just ignore it
|
174
|
+
if !e.backtrace_locations.kind_of?(String)
|
175
|
+
backtrace = e.backtrace_locations.map { |l| Pathname.new(l.absolute_path) }
|
176
|
+
else
|
177
|
+
STDERR.puts "Caught what appears to be a cross-drb exception, which should not happen"
|
178
|
+
STDERR.puts e.message
|
179
|
+
e.backtrace.join("\n ")
|
180
|
+
backtrace = Array.new
|
181
|
+
end
|
139
182
|
error_paths.merge(backtrace)
|
140
183
|
if e.kind_of?(LoadError)
|
141
184
|
error_paths << e.path
|
@@ -153,8 +196,8 @@ def slave?
|
|
153
196
|
# Request that the master spawns these subcommands
|
154
197
|
#
|
155
198
|
# @raise [NotSlave] if the script is being executed in standalone mode
|
156
|
-
def add_slave(*cmdline, **spawn_options)
|
157
|
-
subcommands << [cmdline, spawn_options]
|
199
|
+
def add_slave(*cmdline, name: nil, **spawn_options)
|
200
|
+
subcommands << [name, cmdline, spawn_options]
|
158
201
|
end
|
159
202
|
|
160
203
|
# Create a pipe and dump the program ID state of the current program
|
@@ -164,7 +207,7 @@ def dump_initial_state(files)
|
|
164
207
|
program_id.register_files(files)
|
165
208
|
|
166
209
|
io = Tempfile.new "autorespawn_initial_state"
|
167
|
-
Marshal.dump(program_id, io)
|
210
|
+
Marshal.dump([name, program_id], io)
|
168
211
|
io.flush
|
169
212
|
io.rewind
|
170
213
|
io
|
@@ -174,12 +217,6 @@ def currently_loaded_files
|
|
174
217
|
$LOADED_FEATURES.map { |p| Pathname.new(p) } +
|
175
218
|
caller_locations.map { |l| Pathname.new(l.absolute_path) }
|
176
219
|
end
|
177
|
-
|
178
|
-
# Declares a handler that should be called in a process, just before
|
179
|
-
# exec'ing a fresh process *if* the block has been executed
|
180
|
-
def at_respawn(&block)
|
181
|
-
respawn_handlers << block
|
182
|
-
end
|
183
220
|
|
184
221
|
# Defines the exit code for this instance
|
185
222
|
def exit_code(value = nil)
|
@@ -227,8 +264,8 @@ def run(&block)
|
|
227
264
|
raise ArgumentError, "cannot call #run with a block after using #add_slave"
|
228
265
|
end
|
229
266
|
manager = Manager.new
|
230
|
-
subcommands.each do |command, options|
|
231
|
-
manager.add_slave(*command, **options)
|
267
|
+
subcommands.each do |name, command, options|
|
268
|
+
manager.add_slave(*command, name: name, **options)
|
232
269
|
end
|
233
270
|
return manager.run
|
234
271
|
end
|
@@ -236,10 +273,6 @@ def run(&block)
|
|
236
273
|
|
237
274
|
# @api private
|
238
275
|
def perform_work(all_files, &block)
|
239
|
-
if has_initial_state?
|
240
|
-
load_initial_state
|
241
|
-
end
|
242
|
-
|
243
276
|
not_tracked = all_files.
|
244
277
|
find_all do |p|
|
245
278
|
begin !program_id.include?(p)
|
@@ -250,14 +283,7 @@ def perform_work(all_files, &block)
|
|
250
283
|
if not_tracked.empty? && !program_id.changed?
|
251
284
|
if exceptions.empty?
|
252
285
|
did_yield = true
|
253
|
-
|
254
|
-
yield_exceptions.each do |e|
|
255
|
-
backtrace = (e.backtrace || Array.new).dup
|
256
|
-
first_line = backtrace.shift
|
257
|
-
STDERR.puts "#{e.message}: #{first_line}"
|
258
|
-
STDERR.puts " #{e.backtrace.join("\n ")}"
|
259
|
-
end
|
260
|
-
|
286
|
+
watch_yield(&block)
|
261
287
|
end
|
262
288
|
|
263
289
|
all_files = required_paths | error_paths
|
@@ -272,7 +298,7 @@ def perform_work(all_files, &block)
|
|
272
298
|
Watch.new(program_id).wait
|
273
299
|
end
|
274
300
|
if did_yield
|
275
|
-
|
301
|
+
run_hook :at_respawn
|
276
302
|
end
|
277
303
|
end
|
278
304
|
all_files
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Autorespawn
|
2
|
+
# Override of the global Hooks behaviour w.r.t. blocks. Blocks are evaluated
|
3
|
+
# in their definition context in Autorespawn instead of the default evaluation in
|
4
|
+
# the context of the receiver
|
5
|
+
module Hooks
|
6
|
+
include ::Hooks
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
extend Uber::InheritableAttr
|
11
|
+
extend ClassMethods
|
12
|
+
inheritable_attr :_hooks
|
13
|
+
self._hooks= HookSet.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
include ::Hooks::ClassMethods
|
19
|
+
|
20
|
+
def define_hooks(callback, scope: lambda { |c, s| s if !c.proc? })
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/lib/autorespawn/manager.rb
CHANGED
@@ -1,7 +1,22 @@
|
|
1
1
|
class Autorespawn
|
2
2
|
# Manager of a bunch of autorespawn slaves
|
3
3
|
class Manager
|
4
|
-
|
4
|
+
include Hooks
|
5
|
+
include Hooks::InstanceHooks
|
6
|
+
|
7
|
+
# @return [ProgramID] a seed object that is passed to new slaves to
|
8
|
+
# represent the currently known state of file, to avoid unnecessary
|
9
|
+
# respawning
|
10
|
+
#
|
11
|
+
# @see add_seed_file
|
12
|
+
attr_reader :seed
|
13
|
+
# @return [Object] an object that is used to identify the manager itself
|
14
|
+
attr_reader :name
|
15
|
+
# @return [Self] an object that has the same API than [Slave] to
|
16
|
+
# represent the manager's process itself. It is always included in
|
17
|
+
# {#workers} and {#active_slaves}
|
18
|
+
attr_reader :self_slave
|
19
|
+
# @return [Integer] the number of processes allowed to work in parallel
|
5
20
|
attr_reader :parallel_level
|
6
21
|
# @return [Array<Slave>] declared worker processes, as a hash from
|
7
22
|
# the PID to a Slave object
|
@@ -9,17 +24,105 @@ class Manager
|
|
9
24
|
# @return [Hash<Slave>] list of active slaves
|
10
25
|
attr_reader :active_slaves
|
11
26
|
|
12
|
-
|
13
|
-
|
27
|
+
# @!group Hooks
|
28
|
+
|
29
|
+
# Register a callback for when a new slave is added by {#add_slave}
|
30
|
+
#
|
31
|
+
# @param [#call] block the callback
|
32
|
+
# @yieldparam [Slave] the new slave
|
33
|
+
def on_slave_new(&block)
|
34
|
+
__on_slave_new(&block)
|
35
|
+
workers.each do |w|
|
36
|
+
block.call(w)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
define_hooks :__on_slave_new
|
40
|
+
|
41
|
+
# Register a callback that should be called when a new slave has been
|
42
|
+
# spawned by {#poll}
|
43
|
+
#
|
44
|
+
# @param [#call] block the callback
|
45
|
+
# @yieldparam [Slave] the newly started slave
|
46
|
+
def on_slave_start(&block)
|
47
|
+
__on_slave_start(&block)
|
48
|
+
active_slaves.each_value do |w|
|
49
|
+
block.call(w)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
define_hooks :__on_slave_start
|
53
|
+
|
54
|
+
# @!method on_slave_finished
|
55
|
+
#
|
56
|
+
# Hook called when a slave finishes
|
57
|
+
#
|
58
|
+
# @yieldparam [Slave] the slave
|
59
|
+
define_hooks :on_slave_finished
|
60
|
+
|
61
|
+
# @!method on_slave_removed
|
62
|
+
#
|
63
|
+
# Hook called when a slave has been removed from this manager
|
64
|
+
#
|
65
|
+
# @yieldparam [Slave] the slave
|
66
|
+
define_hooks :on_slave_removed
|
67
|
+
|
68
|
+
# @!endgroup
|
69
|
+
|
70
|
+
def initialize(name: nil, parallel_level: 1)
|
71
|
+
@parallel_level = parallel_level + 1
|
14
72
|
@workers = Array.new
|
15
|
-
@
|
73
|
+
@name = name
|
74
|
+
@seed = ProgramID.for_self
|
75
|
+
|
76
|
+
@self_slave = Self.new(name: name)
|
77
|
+
@workers << self_slave
|
78
|
+
@active_slaves = Hash[self_slave.pid => self_slave]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Add files to {#seed}
|
82
|
+
#
|
83
|
+
# (see ProgramID#register_files)
|
84
|
+
def register_seed_files(files, search_patch = seed.ruby_load_path, ignore_not_found: true)
|
85
|
+
seed.register_files(files, search_path, ignore_not_found)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Tests whether this slave is registered as a worker on self
|
89
|
+
def include?(slave)
|
90
|
+
workers.include?(slave)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Tests whether this slave is currently active on self
|
94
|
+
def active?(slave)
|
95
|
+
active_slaves[slave.pid] == slave
|
16
96
|
end
|
17
97
|
|
18
98
|
# Spawns a worker, i.e. a program that will perform the intended work
|
19
99
|
# and report the program state
|
20
|
-
|
21
|
-
|
100
|
+
#
|
101
|
+
# @param [Object] name an arbitrary object that can be used for
|
102
|
+
# reporting / tracking
|
103
|
+
def add_slave(*cmdline, name: nil, **spawn_options)
|
104
|
+
slave = Slave.new(*cmdline, name: name, seed: seed, **spawn_options)
|
105
|
+
register_slave(slave)
|
106
|
+
slave
|
107
|
+
end
|
108
|
+
|
109
|
+
# Remove a worker from this manager
|
110
|
+
#
|
111
|
+
# @raise [ArgumentError] if the slave is still running
|
112
|
+
def remove_slave(slave)
|
113
|
+
if active?(slave)
|
114
|
+
raise ArgumentError, "#{slave} is still running"
|
115
|
+
end
|
116
|
+
workers.delete(slave)
|
117
|
+
run_hook :on_slave_removed, slave
|
118
|
+
end
|
119
|
+
|
120
|
+
# @api private
|
121
|
+
#
|
122
|
+
# Registers a slave
|
123
|
+
def register_slave(slave)
|
22
124
|
workers << slave
|
125
|
+
run_hook :__on_slave_new, slave
|
23
126
|
slave
|
24
127
|
end
|
25
128
|
|
@@ -28,13 +131,23 @@ def add_slave(*cmdline, **spawn_options)
|
|
28
131
|
# Collect information about the finished slaves
|
29
132
|
#
|
30
133
|
# @return [Array<Slave>] the slaves that finished
|
31
|
-
def collect_finished_slaves
|
134
|
+
def collect_finished_slaves(wait: false)
|
32
135
|
finished_slaves = Array.new
|
33
|
-
|
136
|
+
waitpid_options =
|
137
|
+
if wait then []
|
138
|
+
else [Process::WNOHANG]
|
139
|
+
end
|
140
|
+
|
141
|
+
while finished_child = Process.waitpid2(-1, *waitpid_options)
|
34
142
|
pid, status = *finished_child
|
35
143
|
if slave = active_slaves.delete(pid)
|
36
144
|
finished_slaves << slave
|
37
145
|
slave.finished(status)
|
146
|
+
slave.subcommands.each do |name, cmdline, spawn_options|
|
147
|
+
add_slave(*cmdline, name: name, **spawn_options)
|
148
|
+
end
|
149
|
+
seed.merge!(slave.program_id)
|
150
|
+
run_hook :on_slave_finished, slave
|
38
151
|
end
|
39
152
|
end
|
40
153
|
finished_slaves
|
@@ -42,6 +155,28 @@ def collect_finished_slaves
|
|
42
155
|
Array.new
|
43
156
|
end
|
44
157
|
|
158
|
+
# Kill all active slaves
|
159
|
+
#
|
160
|
+
# @see clear
|
161
|
+
def kill
|
162
|
+
active_slaves.each { |s| s.kill(join: false) }
|
163
|
+
while active_slaves != [self_slave]
|
164
|
+
collect_finished_slaves(wait: true)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Kill and remove all workers from this manager
|
169
|
+
#
|
170
|
+
# @see kill
|
171
|
+
def clear
|
172
|
+
kill
|
173
|
+
workers.dup.each do |w|
|
174
|
+
if w != self_slave
|
175
|
+
remove_slave(w)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
45
180
|
def run
|
46
181
|
while true
|
47
182
|
poll
|
@@ -64,6 +199,7 @@ def poll
|
|
64
199
|
slave = workers.delete_at(slave_i)
|
65
200
|
@workers = workers[slave_i..-1] + workers[0, slave_i] + [slave]
|
66
201
|
slave.spawn
|
202
|
+
run_hook :__on_slave_start, slave
|
67
203
|
new_slaves << slave
|
68
204
|
active_slaves[slave.pid] = slave
|
69
205
|
else
|
@@ -19,6 +19,21 @@ def initialize
|
|
19
19
|
@files = Hash.new
|
20
20
|
end
|
21
21
|
|
22
|
+
def initialize_copy(old)
|
23
|
+
super
|
24
|
+
@files = @files.dup
|
25
|
+
end
|
26
|
+
|
27
|
+
# Merge the information contained in another ProgramID object into self
|
28
|
+
#
|
29
|
+
# @param [ProgramID] id the object whose information we should merge
|
30
|
+
# @return self
|
31
|
+
def merge!(id)
|
32
|
+
@files.merge!(id.files)
|
33
|
+
@id = nil
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
22
37
|
# Compute ID information abou thte current Ruby process
|
23
38
|
def self.for_self
|
24
39
|
id = ProgramID.new
|
@@ -40,18 +55,20 @@ def clear
|
|
40
55
|
#
|
41
56
|
# @return [void]
|
42
57
|
def register_loaded_features
|
43
|
-
|
44
|
-
|
45
|
-
# enumerator.so is listed in $LOADED_FEATURES but is not present
|
46
|
-
# on disk ... no idea
|
47
|
-
begin
|
48
|
-
begin
|
49
|
-
register_file(Pathname.new(file))
|
50
|
-
rescue FileNotFound => e
|
51
|
-
STDERR.puts "WARN: could not find #{e.path} in ruby search path, ignored"
|
52
|
-
end
|
53
|
-
end
|
58
|
+
loaded_features = $LOADED_FEATURES.map do |file|
|
59
|
+
Pathname.new(file)
|
54
60
|
end
|
61
|
+
register_files(loaded_features)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Resolve a file list into absolute paths
|
65
|
+
def resolve_file_list(files, search_path = ruby_load_path, ignore_not_found: true)
|
66
|
+
files.map do |path|
|
67
|
+
begin resolve_file_path(path, search_path)
|
68
|
+
rescue FileNotFound
|
69
|
+
raise if !ignore_not_found
|
70
|
+
end
|
71
|
+
end.compact
|
55
72
|
end
|
56
73
|
|
57
74
|
# Register a set of files
|
@@ -63,16 +80,20 @@ def register_loaded_features
|
|
63
80
|
# @return [Boolean] whether the program ID has been modified
|
64
81
|
def register_files(files, search_path = ruby_load_path, ignore_not_found: true)
|
65
82
|
modified = Array.new
|
66
|
-
files
|
67
|
-
|
68
|
-
|
69
|
-
modified << full_path
|
70
|
-
end
|
71
|
-
rescue FileNotFound
|
72
|
-
raise if !ignore_not_found
|
73
|
-
end
|
83
|
+
files = resolve_file_list(files, search_path, ignore_not_found: ignore_not_found)
|
84
|
+
files.find_all do |path|
|
85
|
+
register_file(path, search_path)
|
74
86
|
end
|
75
|
-
|
87
|
+
end
|
88
|
+
|
89
|
+
# Removes any file in self that is not in the given file list and
|
90
|
+
# returns the result
|
91
|
+
def slice(files, search_path = ruby_load_path, ignore_not_found: true)
|
92
|
+
result = dup
|
93
|
+
files = resolve_file_list(files, search_path, ignore_not_found: ignore_not_found).
|
94
|
+
to_set
|
95
|
+
result.files.delete_if { |k, _| !files.include?(k) }
|
96
|
+
result
|
76
97
|
end
|
77
98
|
|
78
99
|
# Registers file information for one file
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Autorespawn
|
2
|
+
# A Slave-compatible object that represents the manager's process itself
|
3
|
+
class Self < Slave
|
4
|
+
def initialize(*args, **options)
|
5
|
+
super
|
6
|
+
|
7
|
+
@pid = Process.pid
|
8
|
+
end
|
9
|
+
|
10
|
+
def needs_spawn?; false end
|
11
|
+
def spawn
|
12
|
+
pid
|
13
|
+
end
|
14
|
+
def kill
|
15
|
+
end
|
16
|
+
def join
|
17
|
+
end
|
18
|
+
def running?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
def finished?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/autorespawn/slave.rb
CHANGED
@@ -5,6 +5,12 @@ class Autorespawn
|
|
5
5
|
# Slaves have two roles: the one of discovery (what are the commands that
|
6
6
|
# need to be started) and the one of
|
7
7
|
class Slave
|
8
|
+
# The slave's name
|
9
|
+
#
|
10
|
+
# It is an arbitrary object useful for reporting/tracking
|
11
|
+
#
|
12
|
+
# @return [Object]
|
13
|
+
attr_reader :name
|
8
14
|
# The currently known program ID
|
9
15
|
attr_reader :program_id
|
10
16
|
# The command line of the subprocess
|
@@ -33,8 +39,13 @@ class Slave
|
|
33
39
|
# @return [String] the result data as received
|
34
40
|
attr_reader :result_buffer
|
35
41
|
|
36
|
-
|
37
|
-
|
42
|
+
# @param [Object] name an arbitrary object that can be used for
|
43
|
+
# reporting / tracking reasons
|
44
|
+
# @param [ProgramID] seed a seed object with some relevant files already
|
45
|
+
# registered, to avoid respawning the slave unnecessarily.
|
46
|
+
def initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **spawn_options)
|
47
|
+
@name = name
|
48
|
+
@program_id = seed.dup
|
38
49
|
@cmdline = cmdline
|
39
50
|
@needs_spawn = true
|
40
51
|
@spawn_env = env
|
@@ -52,6 +63,13 @@ def inspect
|
|
52
63
|
|
53
64
|
def to_s; inspect end
|
54
65
|
|
66
|
+
# Register files on the program ID
|
67
|
+
#
|
68
|
+
# (see ProgramID#register_files)
|
69
|
+
def register_files(files, search_path = program_id.ruby_load_path, ignore_not_found: true)
|
70
|
+
program_id.register_files(files, search_path, ignore_not_found: ignore_not_found)
|
71
|
+
end
|
72
|
+
|
55
73
|
# Start the slave
|
56
74
|
#
|
57
75
|
# @return [Integer] the slave's PID
|
@@ -71,7 +89,7 @@ def spawn
|
|
71
89
|
pid = Kernel.spawn(env, *cmdline, initial_r => initial_r, result_w => result_w, **spawn_options)
|
72
90
|
initial_r.close
|
73
91
|
result_w.close
|
74
|
-
Marshal.dump(program_id, initial_w)
|
92
|
+
Marshal.dump([name, program_id], initial_w)
|
75
93
|
|
76
94
|
@pid = pid
|
77
95
|
@status = nil
|
@@ -155,6 +173,7 @@ def finished(status)
|
|
155
173
|
@success = false
|
156
174
|
end
|
157
175
|
modified = program_id.register_files(file_list)
|
176
|
+
@program_id = program_id.slice(file_list)
|
158
177
|
if !modified.empty?
|
159
178
|
@needs_spawn = true
|
160
179
|
end
|
data/lib/autorespawn/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autorespawn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sylvain Joyeux
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hooks
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.4.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.4.1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,7 @@ executables: []
|
|
122
136
|
extensions: []
|
123
137
|
extra_rdoc_files: []
|
124
138
|
files:
|
139
|
+
- ".gitattributes"
|
125
140
|
- ".gitignore"
|
126
141
|
- ".travis.yml"
|
127
142
|
- Gemfile
|
@@ -131,8 +146,10 @@ files:
|
|
131
146
|
- autorespawn.gemspec
|
132
147
|
- lib/autorespawn.rb
|
133
148
|
- lib/autorespawn/exceptions.rb
|
149
|
+
- lib/autorespawn/hooks.rb
|
134
150
|
- lib/autorespawn/manager.rb
|
135
151
|
- lib/autorespawn/program_id.rb
|
152
|
+
- lib/autorespawn/self.rb
|
136
153
|
- lib/autorespawn/slave.rb
|
137
154
|
- lib/autorespawn/version.rb
|
138
155
|
- lib/autorespawn/watch.rb
|
@@ -161,3 +178,4 @@ signing_key:
|
|
161
178
|
specification_version: 4
|
162
179
|
summary: functionality to respawn a Ruby program when its source changes
|
163
180
|
test_files: []
|
181
|
+
has_rdoc:
|