zeus-justinf 0.13.5

Sign up to get free protection for your applications and to get access to all the features.
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