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