autorespawn 0.5.1 → 0.6.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: 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