autorespawn 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|