zeus-justinf 0.13.5
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 +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
|