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 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zeus.gemspec
4
+ gemspec
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