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