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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 407f820c7762cbbf5ae65f83ea578db5ffeb7e52
4
- data.tar.gz: a5d66c0a60201f6956b149a7ecd8d2c42dfe15a2
3
+ metadata.gz: 24740ded8fd2be27ffb5f7bd0d6e33f4b71f11f6
4
+ data.tar.gz: 32036b70931a245bc4e1c12b70446160e439123b
5
5
  SHA512:
6
- metadata.gz: 2f4024501a7eb0c401e71969ba806ccde95de5718a6eb2f04dc802936f776fc18621f04a586a69b2797128df4d052be6fd88a62a7a10516e41439967ce112f21
7
- data.tar.gz: f47ae78d068236d2fec92cdb431cae60c23fdd4c3ae0e8972a009dee49cbd8da49a8ad933bc1354f01e4aa78645802e6c6813bd46101fcc6620307280918f682
6
+ metadata.gz: bf53297c07ebeb7edc339f60e961e9159da08b46275aa51cc8636002f08b37ec1a4b7410e4cd964b9be7c7fb6eb4540458427701739f464af7edc78f3f5cbee4
7
+ data.tar.gz: eba4eff1d2c3d1c3ee07e80c541584ceede4231e6b20952a4c324fcd9d1e0b3a19141328906a5a10299b15cffbca961c53435bcd76a435774a3505f5ce6e4019
@@ -0,0 +1 @@
1
+ *.rb diff=ruby
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'flexmock', github: 'doudou/flexmock', branch: 'master'
3
+ gem 'pry'
4
+ gem 'pry-byebug'
4
5
  # Specify your gem's dependencies in autorespawn.gemspec
5
6
  gemspec
@@ -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"
@@ -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
- # Delete the envvars first, we really don't want them to leak
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
- if slave_result_fd
51
- @slave_result_fd = Integer(slave_result_fd)
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
- # Set of callbacks called just before we respawn the process
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
- # @return [Array<#call>]
64
- attr_reader :respawn_handlers
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
- backtrace = e.backtrace_locations.map { |l| Pathname.new(l.absolute_path) }
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
- _, yield_exceptions = watch_yield(&block)
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
- respawn_handlers.each { |b| b.call }
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
+
@@ -1,7 +1,22 @@
1
1
  class Autorespawn
2
2
  # Manager of a bunch of autorespawn slaves
3
3
  class Manager
4
- # @param [Integer] the number of processes allowed to work in parallel
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
- def initialize(parallel_level: 1)
13
- @parallel_level = parallel_level
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
- @active_slaves = Hash.new
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
- def add_slave(*cmdline, **spawn_options)
21
- slave = Slave.new(*cmdline, **spawn_options)
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
- while finished_child = Process.waitpid2(-1, Process::WNOHANG)
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
- search_path = ruby_load_path
44
- $LOADED_FEATURES.each do |file|
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.each do |path|
67
- begin
68
- if full_path = register_file(path, search_path)
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
- modified
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
@@ -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
- def initialize(*cmdline, env: Hash.new, **spawn_options)
37
- @program_id = ProgramID.new
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
@@ -1,3 +1,3 @@
1
1
  class Autorespawn
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
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.2.1
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-19 00:00:00.000000000 Z
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: