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 +4 -4
- data/lib/autorespawn/manager.rb +5 -3
- data/lib/autorespawn/program_id.rb +27 -21
- data/lib/autorespawn/self.rb +3 -0
- data/lib/autorespawn/slave.rb +49 -9
- data/lib/autorespawn/version.rb +1 -1
- data/lib/autorespawn.rb +8 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff4cfc6c34ead02c1c7d41292c6061978018a914
|
4
|
+
data.tar.gz: 1a549ef5140d9918228595d87e24664a17b43c77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e5a7513ff2ba21c63373263e5f1075978cac6266226ec808bf92e3f37227bad5d596d0ea9de3eb34c96ce7ab5fdcc2b7832bd5f998186c27af987817d375dba
|
7
|
+
data.tar.gz: f65c67dc887c0cab8ad812a9398a8b3ba815c1b45187c4efea49e7f035a4a6320d8a7f29600260e7e92e6531a97d88b4107866164a1e56c2790ca6fbc3680636
|
data/lib/autorespawn/manager.rb
CHANGED
@@ -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.
|
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,
|
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 :
|
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,
|
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
|
-
|
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,
|
93
|
+
def slice(files, ignore_not_found: true)
|
92
94
|
result = dup
|
93
|
-
files
|
94
|
-
|
95
|
-
|
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
|
105
|
-
info = file_info(file
|
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
|
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
|
206
|
-
|
207
|
-
|
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
|
data/lib/autorespawn/self.rb
CHANGED
data/lib/autorespawn/slave.rb
CHANGED
@@ -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
|
75
|
-
program_id.register_files(files
|
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
|
-
|
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
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
|
data/lib/autorespawn/version.rb
CHANGED
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
|
-
|
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]
|
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.
|
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-
|
11
|
+
date: 2016-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hooks
|