zeus-justinf 0.13.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +36 -0
- data/MIT-LICENSE +22 -0
- data/Rakefile +22 -0
- data/bin/zeus +17 -0
- data/build/zeus-darwin-amd64 +0 -0
- data/build/zeus-linux-386 +0 -0
- data/build/zeus-linux-amd64 +0 -0
- data/ext/inotify-wrapper/extconf.rb +24 -0
- data/ext/inotify-wrapper/inotify-wrapper.cpp +116 -0
- data/lib/zeus.rb +226 -0
- data/lib/zeus/load_tracking.rb +64 -0
- data/lib/zeus/m.rb +354 -0
- data/lib/zeus/m/test_collection.rb +52 -0
- data/lib/zeus/m/test_method.rb +39 -0
- data/lib/zeus/plan.rb +6 -0
- data/lib/zeus/rails.rb +256 -0
- data/lib/zeus/version.rb +3 -0
- data/spec/fake_mini_test.rb +42 -0
- data/spec/m_spec.rb +110 -0
- data/spec/rails_spec.rb +62 -0
- data/spec/spec_helper.rb +38 -0
- data/zeus.gemspec +35 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YzA2MDdhY2JjNGYwMjBkNjRmNDI1MzJkNDcwNTU1MzcwNTE2NjdiYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YjAzZWU2OGFiYmVlYmY1OTRjNDUyZTM3YjhkYmZmYWZhZmQ2NjBlNw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NDgyNmQ4ZmE0NWUyNzZkZjZjNDViNzlhZDMyZTczYmJjYTQ0ZTA2NTViM2Jl
|
10
|
+
NjY1NjFhOWU2NGMyMzc3NDBiOGQ1M2FiYzU4NjlmNDBjODFlZTNjYzhiNWM2
|
11
|
+
OTc0ZTJlYTgzYjUyYTIxZDE4MTFlYjYyZGU3ZWEzNTMyMmJlOWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjZiMGNkNTkwNzE2ZGJhYTU5MjAzMTEwYWFjNzA3MDU0OWVhOWMxNTNmNzk2
|
14
|
+
NDBiNzAyNGMwYzExMDE1YzI4ZGI3YWEzZWVlZDI1ZTdjNGM5MDA1N2QzMjM5
|
15
|
+
MWI2ZTgwMzI5YzJjMWUxZWM0NWYxNDY4Yjc4OTM1MzI5YzMzYzA=
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
zeus-justinf (0.13.5)
|
5
|
+
method_source (>= 0.6.7)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
hpricot (0.8.6)
|
12
|
+
method_source (0.8.2)
|
13
|
+
mustache (0.99.5)
|
14
|
+
rake (10.2.2)
|
15
|
+
rdiscount (2.1.7)
|
16
|
+
ronn (0.7.3)
|
17
|
+
hpricot (>= 0.8.2)
|
18
|
+
mustache (>= 0.7.0)
|
19
|
+
rdiscount (>= 1.5.8)
|
20
|
+
rspec (2.12.0)
|
21
|
+
rspec-core (~> 2.12.0)
|
22
|
+
rspec-expectations (~> 2.12.0)
|
23
|
+
rspec-mocks (~> 2.12.0)
|
24
|
+
rspec-core (2.12.2)
|
25
|
+
rspec-expectations (2.12.1)
|
26
|
+
diff-lcs (~> 1.1.3)
|
27
|
+
rspec-mocks (2.12.2)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
rake
|
34
|
+
ronn (>= 0.7.0)
|
35
|
+
rspec (~> 2.12.0)
|
36
|
+
zeus-justinf!
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Burke Libbey
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
ROOT_PATH = Pathname.new(File.expand_path("../../", __FILE__))
|
7
|
+
RUBYGEM_PATH = Pathname.new(File.expand_path("../", __FILE__))
|
8
|
+
|
9
|
+
task build: [:manifest]
|
10
|
+
task default: :build
|
11
|
+
|
12
|
+
task :manifest do
|
13
|
+
files = `find . -type f | sed 's|^\./||'`.lines.map(&:chomp)
|
14
|
+
exceptions = [
|
15
|
+
/.gitignore$/,
|
16
|
+
/^MANIFEST$/,
|
17
|
+
/^pkg\//,
|
18
|
+
]
|
19
|
+
files.reject! { |f| exceptions.any? {|ex| f =~ ex }}
|
20
|
+
File.open('MANIFEST', 'w') {|f| f.puts files.join("\n") }
|
21
|
+
end
|
22
|
+
|
data/bin/zeus
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
platform = `uname -sm`
|
3
|
+
|
4
|
+
exe = case platform
|
5
|
+
when /^Darwin/ ; "zeus-darwin-amd64"
|
6
|
+
when /^Linux.*64/ ; "zeus-linux-amd64"
|
7
|
+
when /^Linux.*/ ; "zeus-linux-386"
|
8
|
+
else
|
9
|
+
puts "Zeus is not supported on your platform."
|
10
|
+
puts "It's not likely to ever be possible on Windows."
|
11
|
+
puts "If you're using another platform that you think should work easily, open an issue at:"
|
12
|
+
puts "https://github.com/burke/zeus/issues"
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
zeusgemdir = File.expand_path("../../", __FILE__)
|
17
|
+
exec "#{zeusgemdir}/build/#{exe}", *ARGV
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
if /linux/ =~ RUBY_PLATFORM
|
2
|
+
open("Makefile", "wb") do |f|
|
3
|
+
f.write <<-EOF
|
4
|
+
CXX = g++
|
5
|
+
CXXFLAGS = -O3 -g -Wall
|
6
|
+
|
7
|
+
inotify-wrapper: inotify-wrapper.o
|
8
|
+
$(CXX) $(CXXFLAGS) $< -o $@
|
9
|
+
|
10
|
+
%.o: %.cpp
|
11
|
+
$(CXX) $(CXXFLAGS) -c $< -o $@
|
12
|
+
|
13
|
+
install:
|
14
|
+
# do nothing
|
15
|
+
EOF
|
16
|
+
end
|
17
|
+
else
|
18
|
+
open("Makefile", "wb") do |f|
|
19
|
+
f.write <<-EOF
|
20
|
+
install:
|
21
|
+
# do nothing
|
22
|
+
EOF
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#include <map>
|
2
|
+
#include <string>
|
3
|
+
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <errno.h>
|
8
|
+
#include <unistd.h>
|
9
|
+
#include <sys/types.h>
|
10
|
+
#include <sys/inotify.h>
|
11
|
+
|
12
|
+
#include <errno.h>
|
13
|
+
|
14
|
+
#define EVENT_SIZE (sizeof (struct inotify_event))
|
15
|
+
#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))
|
16
|
+
|
17
|
+
using namespace std;
|
18
|
+
|
19
|
+
static int _inotify_fd;
|
20
|
+
static map<int, string> _WatchedFiles;
|
21
|
+
static map<string, bool> _FileIsWatched;
|
22
|
+
|
23
|
+
// static int inotifyFlags = IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF;
|
24
|
+
static int inotifyFlags = IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF;
|
25
|
+
|
26
|
+
void maybeAddFileToWatchList(string file)
|
27
|
+
{
|
28
|
+
if (_FileIsWatched[file]) return;
|
29
|
+
|
30
|
+
int wd = inotify_add_watch(_inotify_fd, file.c_str(), inotifyFlags);
|
31
|
+
int attempts = 0;
|
32
|
+
// Files are momentarily inaccessible when they are rewritten. I couldn't
|
33
|
+
// find a good way to deal with this, so we poll 'deleted' files for 0.25s or so
|
34
|
+
// to see if they reappear.
|
35
|
+
while (wd == -1 && errno == ENOENT) {
|
36
|
+
usleep(10000);
|
37
|
+
wd = inotify_add_watch(_inotify_fd, file.c_str(), inotifyFlags);
|
38
|
+
if (attempts++ == 25) break; // try for at most about a quarter of a second
|
39
|
+
}
|
40
|
+
if (wd != -1) {
|
41
|
+
_WatchedFiles[wd] = file;
|
42
|
+
_FileIsWatched[file] = true;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
// This essentially removes a file from the watchlist then
|
47
|
+
// immediately re-adds it. This is because when a file is rewritten,
|
48
|
+
// as so many editors love to do, the watchdescriptor no longer refers to
|
49
|
+
// the file, so re must re-watch the path.
|
50
|
+
void replaceFileInWatchList(int wd, string file)
|
51
|
+
{
|
52
|
+
_FileIsWatched.erase(file);
|
53
|
+
_WatchedFiles.erase(wd);
|
54
|
+
inotify_rm_watch(_inotify_fd, wd);
|
55
|
+
maybeAddFileToWatchList(file);
|
56
|
+
}
|
57
|
+
|
58
|
+
void handleStdin()
|
59
|
+
{
|
60
|
+
char line[2048];
|
61
|
+
if (fgets(line, sizeof(line), stdin) == NULL) return;
|
62
|
+
line[strlen(line)-1] = 0;
|
63
|
+
|
64
|
+
maybeAddFileToWatchList(string(line));
|
65
|
+
}
|
66
|
+
|
67
|
+
void handleInotify()
|
68
|
+
{
|
69
|
+
int length;
|
70
|
+
int i = 0;
|
71
|
+
char buffer[EVENT_BUF_LEN];
|
72
|
+
string filename;
|
73
|
+
|
74
|
+
length = read(_inotify_fd, buffer, EVENT_BUF_LEN);
|
75
|
+
if (length < 0) return;
|
76
|
+
|
77
|
+
while (i < length) {
|
78
|
+
struct inotify_event *event = (struct inotify_event *) &buffer[i];
|
79
|
+
string file = _WatchedFiles[event->wd];
|
80
|
+
if (file != "") {
|
81
|
+
printf("%s\n", file.c_str());
|
82
|
+
fflush(stdout);
|
83
|
+
replaceFileInWatchList(event->wd, file);
|
84
|
+
}
|
85
|
+
|
86
|
+
i += EVENT_SIZE + event->len;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
void go()
|
91
|
+
{
|
92
|
+
fd_set rfds;
|
93
|
+
int retval;
|
94
|
+
|
95
|
+
for (;;) {
|
96
|
+
FD_ZERO(&rfds);
|
97
|
+
FD_SET(0, &rfds);
|
98
|
+
FD_SET(_inotify_fd, &rfds);
|
99
|
+
|
100
|
+
retval = select(_inotify_fd+1, &rfds, NULL, NULL, NULL);
|
101
|
+
|
102
|
+
if (retval == -1) {
|
103
|
+
// perror("select");
|
104
|
+
} else if (retval) {
|
105
|
+
if (FD_ISSET(0, &rfds)) handleStdin();
|
106
|
+
if (FD_ISSET(_inotify_fd, &rfds)) handleInotify();
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
|
112
|
+
int main(int argc, const char *argv[])
|
113
|
+
{
|
114
|
+
_inotify_fd = inotify_init();
|
115
|
+
go();
|
116
|
+
}
|
data/lib/zeus.rb
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
# load exact json version from Gemfile.lock to avoid conflicts
|
5
|
+
gemfile = "#{ENV["BUNDLE_GEMFILE"] || "Gemfile"}.lock"
|
6
|
+
if File.exist?(gemfile) && version = File.read(gemfile)[/^ json \((.*)\)/, 1]
|
7
|
+
gem 'json', version
|
8
|
+
end
|
9
|
+
require 'json'
|
10
|
+
require 'pty'
|
11
|
+
require 'set'
|
12
|
+
|
13
|
+
require 'zeus/load_tracking'
|
14
|
+
require 'zeus/plan'
|
15
|
+
require 'zeus/version'
|
16
|
+
|
17
|
+
module Zeus
|
18
|
+
class << self
|
19
|
+
attr_accessor :plan, :dummy_tty, :master_socket
|
20
|
+
|
21
|
+
# this is totally asinine, but readline gets super confused when it's
|
22
|
+
# required at a time when stdin or stdout is not connected to a TTY,
|
23
|
+
# no matter what we do to tell it otherwise later. So we create a dummy
|
24
|
+
# TTY in case readline is required.
|
25
|
+
#
|
26
|
+
# Yup.
|
27
|
+
def setup_dummy_tty!
|
28
|
+
return if self.dummy_tty
|
29
|
+
master, self.dummy_tty = PTY.send(:open)
|
30
|
+
Thread.new {
|
31
|
+
loop { master.read(1024) }
|
32
|
+
}
|
33
|
+
STDIN.reopen(dummy_tty)
|
34
|
+
STDOUT.reopen(dummy_tty)
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup_master_socket!
|
38
|
+
return master_socket if master_socket
|
39
|
+
|
40
|
+
fd = ENV['ZEUS_MASTER_FD'].to_i
|
41
|
+
self.master_socket = UNIXSocket.for_fd(fd)
|
42
|
+
end
|
43
|
+
|
44
|
+
def go(identifier=:boot)
|
45
|
+
# Thanks to the magic of fork, this following line will return
|
46
|
+
# many times: Every time the parent step receives a request to
|
47
|
+
# run a command.
|
48
|
+
if run_command = boot_steps(identifier)
|
49
|
+
ident, local = run_command
|
50
|
+
return command(ident, local)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def boot_steps(identifier)
|
55
|
+
while true
|
56
|
+
boot_step = catch(:boot_step) do
|
57
|
+
$0 = "zeus slave: #{identifier}"
|
58
|
+
|
59
|
+
setup_dummy_tty!
|
60
|
+
master = setup_master_socket!
|
61
|
+
feature_pipe_r, feature_pipe_w = IO.pipe
|
62
|
+
|
63
|
+
# I need to give the master a way to talk to me exclusively
|
64
|
+
local, remote = UNIXSocket.pair(Socket::SOCK_STREAM)
|
65
|
+
master.send_io(remote)
|
66
|
+
|
67
|
+
# Now I need to tell the master about my PID and ID
|
68
|
+
local.write "P:#{Process.pid}:#{identifier}\0"
|
69
|
+
local.send_io(feature_pipe_r)
|
70
|
+
feature_pipe_r.close
|
71
|
+
|
72
|
+
# Now we run the action and report its success/fail status to the master.
|
73
|
+
features = Zeus::LoadTracking.features_loaded_by {
|
74
|
+
run_action(local, identifier, feature_pipe_w)
|
75
|
+
}
|
76
|
+
|
77
|
+
# the master wants to know about the files that running the action caused us to load.
|
78
|
+
Thread.new { notify_features(feature_pipe_w, features) }
|
79
|
+
|
80
|
+
# We are now 'connected'. From this point, we may receive requests to fork.
|
81
|
+
children = Set.new
|
82
|
+
while true
|
83
|
+
messages = local.recv(2**16)
|
84
|
+
|
85
|
+
# Reap any child runners or slaves that might have exited in
|
86
|
+
# the meantime. Note that reaping them like this can leave <=1
|
87
|
+
# zombie process per slave around while the slave waits for a
|
88
|
+
# new command.
|
89
|
+
children.each do |pid|
|
90
|
+
children.delete(pid) if Process.waitpid(pid, Process::WNOHANG)
|
91
|
+
end
|
92
|
+
|
93
|
+
messages.split("\0").each do |new_identifier|
|
94
|
+
new_identifier =~ /^(.):(.*)/
|
95
|
+
code, ident = $1, $2
|
96
|
+
pid = fork
|
97
|
+
if pid
|
98
|
+
# We're in the parent. Record the child:
|
99
|
+
children << pid
|
100
|
+
elsif code == "S"
|
101
|
+
# Child, supposed to start another step:
|
102
|
+
throw(:boot_step, ident.to_sym)
|
103
|
+
else
|
104
|
+
# Child, supposed to run a command:
|
105
|
+
return [ident.to_sym, local]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
identifier = boot_step
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def command(identifier, sock)
|
117
|
+
$0 = "zeus runner: #{identifier}"
|
118
|
+
Process.setsid
|
119
|
+
|
120
|
+
local, remote = UNIXSocket.pair(:DGRAM)
|
121
|
+
sock.send_io(remote)
|
122
|
+
remote.close
|
123
|
+
sock.close
|
124
|
+
|
125
|
+
pid_and_argument_and_environment_count = local.recv(2**16).chomp("\0")
|
126
|
+
client_pid, argument_count, environment_count = pid_and_argument_and_environment_count.split(":")
|
127
|
+
|
128
|
+
argument_count = argument_count.to_i
|
129
|
+
environment_count = environment_count.to_i
|
130
|
+
|
131
|
+
arg_io = local.recv_io
|
132
|
+
transmitted = arg_io.read.chomp("\0").split("\0")
|
133
|
+
|
134
|
+
if transmitted.length != argument_count + environment_count
|
135
|
+
raise "Transmitted count mismatch: Expected #{argument_count} (args) + #{environment_count} (env), got #{transmitted.length}"
|
136
|
+
end
|
137
|
+
|
138
|
+
arguments = transmitted.slice(0, argument_count)
|
139
|
+
environment = transmitted.slice(argument_count, environment_count)
|
140
|
+
|
141
|
+
pid = fork {
|
142
|
+
$0 = "zeus command: #{identifier}"
|
143
|
+
|
144
|
+
environment.each do |env_var|
|
145
|
+
key, val = env_var.split("=")
|
146
|
+
ENV[key] = val
|
147
|
+
end
|
148
|
+
|
149
|
+
plan.after_fork
|
150
|
+
client_terminal = local.recv_io
|
151
|
+
|
152
|
+
local.write "P:#{Process.pid}:\0"
|
153
|
+
local.close
|
154
|
+
|
155
|
+
$stdin.reopen(client_terminal)
|
156
|
+
$stdout.reopen(client_terminal)
|
157
|
+
$stderr.reopen(client_terminal)
|
158
|
+
ARGV.replace(arguments)
|
159
|
+
|
160
|
+
plan.send(identifier)
|
161
|
+
}
|
162
|
+
|
163
|
+
kill_command_if_client_quits!(pid, client_pid)
|
164
|
+
|
165
|
+
Process.wait(pid)
|
166
|
+
code = $?.exitstatus || 0
|
167
|
+
|
168
|
+
local.write "#{code}\0"
|
169
|
+
|
170
|
+
local.close
|
171
|
+
rescue Exception
|
172
|
+
# If anything at all went wrong, kill the client - if anything
|
173
|
+
# went wrong before the runner can clean up, it might hang
|
174
|
+
# around forever.
|
175
|
+
Process.kill(:TERM, client_pid)
|
176
|
+
end
|
177
|
+
|
178
|
+
def kill_command_if_client_quits!(command_pid, client_pid)
|
179
|
+
Thread.new {
|
180
|
+
loop {
|
181
|
+
begin
|
182
|
+
Process.kill(0, client_pid)
|
183
|
+
rescue Errno::ESRCH
|
184
|
+
Process.kill(9, command_pid)
|
185
|
+
exit 0
|
186
|
+
end
|
187
|
+
sleep 1
|
188
|
+
}
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
def notify_features(pipe, features)
|
193
|
+
features.each do |t|
|
194
|
+
pipe.puts t
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def report_error_to_master(local, error)
|
199
|
+
str = "R:"
|
200
|
+
str << "#{error.backtrace[0]}: #{error.message} (#{error.class})\n"
|
201
|
+
error.backtrace[1..-1].each do |line|
|
202
|
+
str << "\tfrom #{line}\n"
|
203
|
+
end
|
204
|
+
str << "\0"
|
205
|
+
local.write str
|
206
|
+
end
|
207
|
+
|
208
|
+
def run_action(socket, identifier, feature_pipe_w)
|
209
|
+
loaded = false
|
210
|
+
begin
|
211
|
+
plan.after_fork unless identifier == :boot
|
212
|
+
plan.send(identifier)
|
213
|
+
loaded = true
|
214
|
+
socket.write "R:OK\0"
|
215
|
+
rescue Exception => e
|
216
|
+
report_error_to_master(socket, e)
|
217
|
+
|
218
|
+
# Report any setup-time failures back to the Zeus master:
|
219
|
+
unless loaded
|
220
|
+
notify_features(feature_pipe_w, Zeus::LoadTracking.all_features)
|
221
|
+
end
|
222
|
+
feature_pipe_w.close
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|