autorespawn 0.5.1 → 0.6.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: 602581acf1fd8576ccb38fe1423d6f26f5eb63cb
4
- data.tar.gz: 573418360cf9b7309a0af3dd5113d6abec52e2f9
3
+ metadata.gz: ff4cfc6c34ead02c1c7d41292c6061978018a914
4
+ data.tar.gz: 1a549ef5140d9918228595d87e24664a17b43c77
5
5
  SHA512:
6
- metadata.gz: e2ce49df3632a38fd65306a9f3efe43a940359b490e706958bf690b64ab5e7803c040b7dffab4f8b3735156107622faf9e0b91bb4165d26afb916143e6578dd0
7
- data.tar.gz: ae83c99246847d0cbb714160e67798fb358875e399a5ec324e31c216cdd9a3c37b9ea95b8040698828ed13be5c33fcedf29cb2f6dc7dd80f973b10b6972ddcfb
6
+ metadata.gz: 2e5a7513ff2ba21c63373263e5f1075978cac6266226ec808bf92e3f37227bad5d596d0ea9de3eb34c96ce7ab5fdcc2b7832bd5f998186c27af987817d375dba
7
+ data.tar.gz: f65c67dc887c0cab8ad812a9398a8b3ba815c1b45187c4efea49e7f035a4a6320d8a7f29600260e7e92e6531a97d88b4107866164a1e56c2790ca6fbc3680636
@@ -89,7 +89,8 @@ class Autorespawn
89
89
  #
90
90
  # (see ProgramID#register_files)
91
91
  def register_seed_files(files, search_patch = seed.ruby_load_path, ignore_not_found: true)
92
- seed.register_files(files, search_path, ignore_not_found)
92
+ files = seed.resolve_file_list(files, search_path, ignore_not_found: ignore_not_found)
93
+ seed.register_files(files)
93
94
  end
94
95
 
95
96
  # Check whether this manager has slaves
@@ -246,18 +247,19 @@ class Autorespawn
246
247
  new_slaves = Array.new
247
248
 
248
249
  trigger_slaves_as_necessary
250
+ active_slaves.each_value.each(&:write_initial_dump)
249
251
 
250
252
  while active_slaves.size < parallel_level + 1
251
253
  if slave = queued_slaves.find { |s| !s.running? }
252
254
  queued_slaves.delete(slave)
253
255
  elsif autospawn
254
- needed_slaves, remaining = workers.partition { |s| !s.running? && s.needed? }
256
+ needed_slaves, _remaining = workers.partition { |s| !s.running? && s.needed? }
255
257
  failed, normal = needed_slaves.partition { |s| s.finished? && !s.success? }
256
258
  slave = failed.first || normal.first
257
259
  end
258
260
 
259
261
  if slave
260
- slave.spawn
262
+ slave.spawn(send_initial_dump: false)
261
263
  # We manually track the slave's needed flag, just forcefully
262
264
  # set it to false at that point
263
265
  slave.not_needed!
@@ -1,4 +1,3 @@
1
- require 'autorespawn/program_id'
2
1
  require 'pathname'
3
2
  require 'digest/sha1'
4
3
 
@@ -8,7 +7,7 @@ class Autorespawn
8
7
  # It basically stores information about all the files that form this
9
8
  # program.
10
9
  class ProgramID
11
- FileInfo = Struct.new :require_path, :path, :mtime, :size
10
+ FileInfo = Struct.new :path, :mtime, :size
12
11
 
13
12
  # Information about the files that form this program
14
13
  #
@@ -58,7 +57,7 @@ class Autorespawn
58
57
  loaded_features = $LOADED_FEATURES.map do |file|
59
58
  Pathname.new(file)
60
59
  end
61
- register_files(loaded_features)
60
+ register_files(resolve_file_list(loaded_features))
62
61
  end
63
62
 
64
63
  # Resolve a file list into absolute paths
@@ -78,21 +77,31 @@ class Autorespawn
78
77
  # @param [Boolean] ignore_not_found whether files that cannot be
79
78
  # resolved are ignored or cause a FileNotFound exception
80
79
  # @return [Boolean] whether the program ID has been modified
81
- def register_files(files, search_path = ruby_load_path, ignore_not_found: true)
82
- modified = Array.new
83
- files = resolve_file_list(files, search_path, ignore_not_found: ignore_not_found)
80
+ def register_files(files, ignore_not_found: true)
84
81
  files.find_all do |path|
85
- register_file(path, search_path)
82
+ if path.exist?
83
+ register_file(path)
84
+ elsif ignore_not_found
85
+ false
86
+ else raise FileNotFound, "#{path} cannot be found"
87
+ end
86
88
  end
87
89
  end
88
90
 
89
91
  # Removes any file in self that is not in the given file list and
90
92
  # returns the result
91
- def slice(files, search_path = ruby_load_path, ignore_not_found: true)
93
+ def slice(files, ignore_not_found: true)
92
94
  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) }
95
+ result.files.delete_if do |k, _|
96
+ if !files.include?(k)
97
+ true
98
+ elsif !k.exist?
99
+ if !ignore_not_found
100
+ raise FileNotFound, "#{k} does not exist"
101
+ end
102
+ true
103
+ end
104
+ end
96
105
  result
97
106
  end
98
107
 
@@ -101,8 +110,8 @@ class Autorespawn
101
110
  # @param [Pathname] file the path to the file
102
111
  # @return [Boolean] whether the registration modified the program ID's
103
112
  # state
104
- def register_file(file, search_path = ruby_load_path)
105
- info = file_info(file, search_path)
113
+ def register_file(file)
114
+ info = file_info(file)
106
115
  modified = (files[info.path] != info)
107
116
  files[info.path] = info
108
117
 
@@ -193,19 +202,16 @@ class Autorespawn
193
202
  #
194
203
  # @param [Array<Pathname>]
195
204
  def ruby_load_path
196
- $LOAD_PATH.map { |p| Pathname.new(p) }
205
+ @ruby_load_path ||= $LOAD_PATH.map { |p| Pathname.new(p) }
197
206
  end
198
207
 
199
208
  # Resolve file information about a single file
200
209
  #
201
- # @param [Pathname] path the path to the file
202
- # @param [Array<Pathname>] search_path the search path to use to resolve
203
- # 'path' if it is relative
210
+ # @param [Pathname] path abolute path to the file
204
211
  # @return [FileInfo]
205
- def file_info(path, search_path = ruby_load_path)
206
- resolved = resolve_file_path(path, search_path)
207
- stat = resolved.stat
208
- return FileInfo.new(path, resolved, stat.mtime, stat.size)
212
+ def file_info(path)
213
+ stat = path.stat
214
+ return FileInfo.new(path, stat.mtime, stat.size)
209
215
  end
210
216
  end
211
217
  end
@@ -12,6 +12,9 @@ class Autorespawn
12
12
  def spawn
13
13
  pid
14
14
  end
15
+ def write_initial_dump
16
+ true
17
+ end
15
18
  def kill(*, **)
16
19
  end
17
20
  def join
@@ -38,6 +38,15 @@ class Autorespawn
38
38
  #
39
39
  # @return [String] the result data as received
40
40
  attr_reader :result_buffer
41
+ # @api private
42
+ #
43
+ # @return [IO] the IO used to transmit initial information to the slave
44
+ attr_reader :initial_w
45
+ # @api private
46
+ #
47
+ # @return [String] what is remaining of the initial dump that should be
48
+ # passed to the slave
49
+ attr_reader :initial_dump
41
50
 
42
51
  # @param [Object] name an arbitrary object that can be used for
43
52
  # reporting / tracking reasons
@@ -71,14 +80,21 @@ class Autorespawn
71
80
  # Register files on the program ID
72
81
  #
73
82
  # (see ProgramID#register_files)
74
- def register_files(files, search_path = program_id.ruby_load_path, ignore_not_found: true)
75
- program_id.register_files(files, search_path, ignore_not_found: ignore_not_found)
83
+ def register_files(files)
84
+ program_id.register_files(files)
76
85
  end
77
86
 
78
87
  # Start the slave
79
88
  #
89
+ # @param [Boolean] send_initial_dump Initial information is sent to the
90
+ # slave through the {#initial_w} pipe. This transmission can be done
91
+ # asynchronously by setting this flag to true. In this case, the
92
+ # caller should make sure that {#write_initial_dump} is called after
93
+ # {#spawn} until it returns true. Note that {Manager#poll} takes care
94
+ # of this already
95
+ #
80
96
  # @return [Integer] the slave's PID
81
- def spawn
97
+ def spawn(send_initial_dump: true)
82
98
  if running?
83
99
  raise AlreadyRunning, "cannot call #spawn on #{self}: already running"
84
100
  end
@@ -94,7 +110,14 @@ class Autorespawn
94
110
  pid = Kernel.spawn(env, *cmdline, initial_r => initial_r, result_w => result_w, **spawn_options)
95
111
  initial_r.close
96
112
  result_w.close
97
- Marshal.dump([name, program_id], initial_w)
113
+ @initial_w = initial_w
114
+ @initial_dump = Marshal.dump([name, program_id])
115
+ initial_w.write([initial_dump.size].pack("L<"))
116
+ if send_initial_dump
117
+ while !write_initial_dump
118
+ select([], [initial_w])
119
+ end
120
+ end
98
121
 
99
122
  @pid = pid
100
123
  @status = nil
@@ -102,17 +125,33 @@ class Autorespawn
102
125
  @result_r = result_r
103
126
  pid
104
127
 
105
- rescue Exception => e
128
+ rescue Exception
106
129
  if pid
107
130
  Process.kill 'TERM', pid
108
131
  end
132
+ initial_w.close if initial_w && !initial_w.closed?
133
+ initial_r.close if initial_r && !initial_r.closed?
134
+ result_w.close if result_w && !result_w.closed?
109
135
  result_r.close if result_r && !result_r.closed?
110
136
  raise
137
+ end
111
138
 
112
- ensure
113
- initial_r.close if initial_r && !initial_r.closed?
114
- initial_w.close if initial_w && !initial_r.closed?
115
- result_w.close if result_w && !result_w.closed?
139
+ # Write as much of the initial dump to the slave
140
+ #
141
+ # To avoid blocking in {#spawn}, the initial dump
142
+ def write_initial_dump
143
+ if !initial_dump.empty?
144
+ begin
145
+ written_bytes = initial_w.write_nonblock(initial_dump)
146
+ rescue IO::WaitWritable
147
+ written_bytes = 0
148
+ end
149
+
150
+ @initial_dump = @initial_dump[written_bytes, initial_dump.size - written_bytes]
151
+ initial_dump.empty?
152
+ else
153
+ true
154
+ end
116
155
  end
117
156
 
118
157
  # Whether this slave would need to be spawned, either because it has
@@ -214,6 +253,7 @@ class Autorespawn
214
253
  end
215
254
  @success = @success && status.success?
216
255
  result_r.close
256
+ initial_w.close
217
257
  modified
218
258
  end
219
259
 
@@ -1,3 +1,3 @@
1
1
  class Autorespawn
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/autorespawn.rb CHANGED
@@ -9,6 +9,7 @@ require "autorespawn/slave"
9
9
  require "autorespawn/self"
10
10
  require "autorespawn/manager"
11
11
  require 'autorespawn/tracked_file'
12
+ require 'tempfile'
12
13
 
13
14
  # Automatically exec's the current program when one of the source file changes
14
15
  #
@@ -55,6 +56,7 @@ class Autorespawn
55
56
  def self.initial_program_id
56
57
  @initial_program_id
57
58
  end
59
+ @name, @slave_result_fd, @initial_program_id = nil
58
60
 
59
61
  def self.read_child_state
60
62
  # Delete the envvars first, we really don't want them to leak
@@ -63,7 +65,8 @@ class Autorespawn
63
65
  if slave_initial_state_fd
64
66
  slave_initial_state_fd = Integer(slave_initial_state_fd)
65
67
  io = IO.for_fd(slave_initial_state_fd)
66
- @name, @initial_program_id = Marshal.load(io)
68
+ size = io.read(4).unpack('L<').first
69
+ @name, @initial_program_id = Marshal.load(io.read(size))
67
70
  io.close
68
71
  end
69
72
  if slave_result_fd
@@ -205,10 +208,13 @@ class Autorespawn
205
208
  # there
206
209
  def dump_initial_state(files)
207
210
  program_id = ProgramID.new
211
+ files = program_id.resolve_file_list(files)
208
212
  program_id.register_files(files)
209
213
 
210
214
  io = Tempfile.new "autorespawn_initial_state"
211
- Marshal.dump([name, program_id], io)
215
+ initial_info = Marshal.dump([name, program_id])
216
+ io.write([initial_info.size].pack("L<"))
217
+ io.write(initial_info)
212
218
  io.flush
213
219
  io.rewind
214
220
  io
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autorespawn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.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: 2016-03-18 00:00:00.000000000 Z
11
+ date: 2016-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hooks