zeus 0.4.6 → 0.10.0.pre
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.
- data/Gemfile +0 -4
- data/Rakefile +52 -8
- data/bin/zeus +14 -6
- data/build/fsevents-wrapper +0 -0
- data/build/zeus-darwin-amd64 +0 -0
- data/build/zeus-linux-386 +0 -0
- data/build/zeus-linux-amd64 +0 -0
- data/examples/zeus.json +22 -0
- data/ext/fsevents-wrapper/fsevents-wrapper +0 -0
- data/ext/inotify-wrapper/extconf.rb +24 -0
- data/ext/inotify-wrapper/inotify-wrapper.cpp +86 -0
- data/lib/zeus.rb +123 -35
- data/lib/zeus/{server/load_tracking.rb → load_tracking.rb} +1 -3
- data/lib/zeus/rails.rb +141 -0
- data/man/build/zeus +49 -0
- data/man/build/zeus-init +13 -0
- data/man/build/zeus-init.txt +17 -0
- data/man/build/zeus-start +16 -0
- data/man/build/zeus-start.txt +18 -0
- data/man/build/zeus.txt +50 -0
- data/zeus.gemspec +17 -6
- metadata +27 -58
- data/.gitignore +0 -17
- data/.travis.yml +0 -5
- data/.zeus.rb +0 -11
- data/README.md +0 -73
- data/docs/acceptor_registration.md +0 -14
- data/docs/client_server_handshake.md +0 -25
- data/ext/fsevents-wrapper/main.m +0 -118
- data/lib/thrud.rb +0 -97
- data/lib/zeus/cli.rb +0 -80
- data/lib/zeus/client.rb +0 -114
- data/lib/zeus/client/winsize.rb +0 -28
- data/lib/zeus/error_printer.rb +0 -16
- data/lib/zeus/plan.rb +0 -18
- data/lib/zeus/plan/acceptor.rb +0 -38
- data/lib/zeus/plan/node.rb +0 -66
- data/lib/zeus/plan/stage.rb +0 -50
- data/lib/zeus/server.rb +0 -103
- data/lib/zeus/server/acceptor.rb +0 -79
- data/lib/zeus/server/acceptor_registration_monitor.rb +0 -75
- data/lib/zeus/server/client_handler.rb +0 -106
- data/lib/zeus/server/command_runner.rb +0 -70
- data/lib/zeus/server/file_monitor.rb +0 -8
- data/lib/zeus/server/file_monitor/fsevent.rb +0 -102
- data/lib/zeus/server/process_tree_monitor.rb +0 -89
- data/lib/zeus/server/stage.rb +0 -88
- data/lib/zeus/server/stage/error_state.rb +0 -42
- data/lib/zeus/server/stage/feature_notifier.rb +0 -38
- data/lib/zeus/templates/rails.rb +0 -133
- data/lib/zeus/ui.rb +0 -57
- data/lib/zeus/version.rb +0 -3
- data/spec/cli_spec.rb +0 -95
- data/spec/error_printer_spec.rb +0 -27
- data/spec/integration_spec.rb +0 -106
- data/spec/server/file_monitor/fsevent_spec.rb +0 -88
- data/spec/server/load_tracking_spec.rb +0 -67
- data/spec/server/process_tree_monitor_spec.rb +0 -50
- data/spec/spec_helper.rb +0 -38
- data/spec/ui_spec.rb +0 -54
@@ -1,106 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'json'
|
3
|
-
|
4
|
-
# This class is a little confusing. See the docs/ directory for guidance.
|
5
|
-
module Zeus
|
6
|
-
class Server
|
7
|
-
class ClientHandler
|
8
|
-
def datasource ; @listener ; end
|
9
|
-
def on_datasource_event ; handle_server_connection ; end
|
10
|
-
def close_child_socket ; end
|
11
|
-
def close_parent_socket ; @listener.close ; end
|
12
|
-
|
13
|
-
REATTEMPT_HANDSHAKE = 204
|
14
|
-
|
15
|
-
def initialize(acceptor_commands, server)
|
16
|
-
@server = server
|
17
|
-
@acceptor_commands = acceptor_commands
|
18
|
-
@listener = UNIXServer.new(Zeus::SOCKET_NAME)
|
19
|
-
@listener.listen(10)
|
20
|
-
rescue Errno::EADDRINUSE
|
21
|
-
Zeus.ui.error "Zeus appears to be already running in this project. If not, remove #{Zeus::SOCKET_NAME} and try again."
|
22
|
-
exit 1
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
# See docs/client_server_handshake.md for details
|
28
|
-
def handle_server_connection
|
29
|
-
s_client = @listener.accept
|
30
|
-
|
31
|
-
data = JSON.parse(s_client.readline.chomp)
|
32
|
-
command, arguments = data.values_at('command', 'arguments')
|
33
|
-
|
34
|
-
client_terminal = s_client.recv_io
|
35
|
-
exit_status_socket = s_client.recv_io
|
36
|
-
|
37
|
-
Thread.new {
|
38
|
-
# This is a little ugly. Gist: Try to handshake the client to the acceptor.
|
39
|
-
# If the acceptor is not booted yet, this will hang until it is, then terminate with
|
40
|
-
# REATTEMPT_HANDSHAKE. We catch that exit code and try once more.
|
41
|
-
begin
|
42
|
-
loop do
|
43
|
-
pid = fork { handshake_client_to_acceptor(s_client, command, arguments, client_terminal, exit_status_socket) ; exit }
|
44
|
-
Process.wait(pid)
|
45
|
-
break if $?.exitstatus != REATTEMPT_HANDSHAKE
|
46
|
-
end
|
47
|
-
ensure
|
48
|
-
client_terminal.close
|
49
|
-
s_client.close
|
50
|
-
end
|
51
|
-
}
|
52
|
-
end
|
53
|
-
|
54
|
-
def handshake_client_to_acceptor(s_client, command, arguments, client_terminal, exit_status_socket)
|
55
|
-
unless @acceptor_commands.include?(command.to_s)
|
56
|
-
msg = "no such command `#{command}`."
|
57
|
-
return exit_with_message(s_client, client_terminal, msg)
|
58
|
-
end
|
59
|
-
|
60
|
-
unless acceptor = send_io_to_acceptor(client_terminal, exit_status_socket, command)
|
61
|
-
wait_for_acceptor(s_client, client_terminal, command)
|
62
|
-
exit REATTEMPT_HANDSHAKE
|
63
|
-
end
|
64
|
-
|
65
|
-
Zeus.ui.info "accepting connection for #{command}"
|
66
|
-
|
67
|
-
acceptor.socket.puts arguments.to_json
|
68
|
-
pid = acceptor.socket.readline.chomp.to_i
|
69
|
-
s_client.puts pid
|
70
|
-
s_client.close
|
71
|
-
end
|
72
|
-
|
73
|
-
def exit_with_message(s_client, client_terminal, msg)
|
74
|
-
s_client << "0\n"
|
75
|
-
client_terminal << "[zeus] #{msg}\n"
|
76
|
-
client_terminal.close
|
77
|
-
s_client.close
|
78
|
-
exit 1
|
79
|
-
end
|
80
|
-
|
81
|
-
def wait_for_acceptor(s_client, client_terminal, command)
|
82
|
-
s_client << "0\n"
|
83
|
-
client_terminal << "[zeus] waiting for `#{command}` to finish booting...\n"
|
84
|
-
|
85
|
-
s, r = UNIXSocket.pair
|
86
|
-
s << {type: 'wait', command: command}.to_json << "\n"
|
87
|
-
@server.__CHILD__register_acceptor(r)
|
88
|
-
|
89
|
-
s.readline # wait until acceptor is booted
|
90
|
-
end
|
91
|
-
|
92
|
-
def send_io_to_acceptor(io, io2, command)
|
93
|
-
return false unless acceptor = @server.__CHILD__find_acceptor_for_command(command)
|
94
|
-
return false unless usock = UNIXSocket.for_fd(acceptor.socket.fileno)
|
95
|
-
usock.send_io(io)
|
96
|
-
usock.send_io(io2)
|
97
|
-
io.close
|
98
|
-
io2.close
|
99
|
-
return acceptor
|
100
|
-
rescue Errno::EPIPE
|
101
|
-
return false
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
module Zeus
|
2
|
-
class Server
|
3
|
-
class CommandRunner
|
4
|
-
|
5
|
-
def initialize(name, action, s_acceptor)
|
6
|
-
@name = name
|
7
|
-
@action = action
|
8
|
-
@s_acceptor = s_acceptor
|
9
|
-
end
|
10
|
-
|
11
|
-
def run(terminal, exit_status_socket, arguments)
|
12
|
-
child = fork { _run(terminal, exit_status_socket, arguments) }
|
13
|
-
terminal.close
|
14
|
-
exit_status_socket.close
|
15
|
-
Process.detach(child)
|
16
|
-
child
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def _run(terminal, exit_status_socket, arguments)
|
22
|
-
$0 = "zeus runner: #{@name}"
|
23
|
-
@exit_status_socket = exit_status_socket
|
24
|
-
@terminal = terminal
|
25
|
-
Process.setsid
|
26
|
-
reconnect_activerecord!
|
27
|
-
@s_acceptor << $$ << "\n"
|
28
|
-
reopen_streams(terminal, terminal, terminal)
|
29
|
-
ARGV.replace(arguments)
|
30
|
-
|
31
|
-
return_process_exit_status
|
32
|
-
|
33
|
-
run_action
|
34
|
-
end
|
35
|
-
|
36
|
-
def return_process_exit_status
|
37
|
-
at_exit do
|
38
|
-
if $!.nil? || $!.is_a?(SystemExit) && $!.success?
|
39
|
-
@exit_status_socket.puts(0)
|
40
|
-
else
|
41
|
-
code = $!.is_a?(SystemExit) ? $!.status : 1
|
42
|
-
@exit_status_socket.puts(code)
|
43
|
-
end
|
44
|
-
|
45
|
-
@exit_status_socket.close
|
46
|
-
@terminal.close
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def run_action
|
51
|
-
@action.call
|
52
|
-
rescue StandardError => error
|
53
|
-
ErrorPrinter.new(error).write_to($stderr)
|
54
|
-
raise
|
55
|
-
end
|
56
|
-
|
57
|
-
def reopen_streams(i, o, e)
|
58
|
-
$stdin.reopen(i)
|
59
|
-
$stdout.reopen(o)
|
60
|
-
$stderr.reopen(e)
|
61
|
-
end
|
62
|
-
|
63
|
-
def reconnect_activerecord!
|
64
|
-
ActiveRecord::Base.clear_all_connections! rescue nil
|
65
|
-
ActiveRecord::Base.establish_connection rescue nil
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,102 +0,0 @@
|
|
1
|
-
require 'open3'
|
2
|
-
require 'pathname'
|
3
|
-
|
4
|
-
module Zeus
|
5
|
-
class Server
|
6
|
-
module FileMonitor
|
7
|
-
class FSEvent
|
8
|
-
WRAPPER_PATH = File.expand_path("../../../../../ext/fsevents-wrapper/fsevents-wrapper", __FILE__)
|
9
|
-
|
10
|
-
def datasource ; @io_out ; end
|
11
|
-
def on_datasource_event ; handle_changed_files ; end
|
12
|
-
def close_child_socket ; end
|
13
|
-
def close_parent_socket ; [@io_in, @io_out].each(&:close) ; end
|
14
|
-
|
15
|
-
def initialize(&change_callback)
|
16
|
-
@change_callback = change_callback
|
17
|
-
@io_in, @io_out, @wrapper_thread = open_wrapper
|
18
|
-
@givenpath_to_realpath = {}
|
19
|
-
@realpath_to_givenpath = {}
|
20
|
-
@buffer = ""
|
21
|
-
end
|
22
|
-
|
23
|
-
# The biggest complicating factor here is that ruby doesn't fully resolve
|
24
|
-
# symlinks in paths, but FSEvents does. We resolve all paths fully with
|
25
|
-
# Pathname#realpath, and keep mappings in both directions.
|
26
|
-
# It's conceivable that the same file would be required by two different paths,
|
27
|
-
# so we keep an array and fire callbacks for all given paths matching a real
|
28
|
-
# path when a change is detected.
|
29
|
-
def watch(given)
|
30
|
-
return false if @givenpath_to_realpath[given]
|
31
|
-
|
32
|
-
real = realpath(given)
|
33
|
-
@givenpath_to_realpath[given] = real
|
34
|
-
@realpath_to_givenpath[real] ||= []
|
35
|
-
@realpath_to_givenpath[real] << given
|
36
|
-
|
37
|
-
@io_in.write("#{real}\n")
|
38
|
-
true
|
39
|
-
end
|
40
|
-
|
41
|
-
def kill_wrapper
|
42
|
-
Process.kill(9, @wrapper_thread.pid)
|
43
|
-
rescue Errno::ESRCH # already dead. SIGINT to master causes this often.
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def realpath(file)
|
49
|
-
Pathname.new(file).realpath.to_s
|
50
|
-
rescue Errno::ENOENT
|
51
|
-
file
|
52
|
-
end
|
53
|
-
|
54
|
-
def open_wrapper
|
55
|
-
Open3.popen2e(WRAPPER_PATH)
|
56
|
-
end
|
57
|
-
|
58
|
-
def handle_changed_files
|
59
|
-
50.times { read_and_notify_files }
|
60
|
-
rescue Stop
|
61
|
-
end
|
62
|
-
|
63
|
-
Stop = Class.new(Exception)
|
64
|
-
|
65
|
-
def read_and_notify_files
|
66
|
-
begin
|
67
|
-
lines = @io_out.read_nonblock(1000)
|
68
|
-
rescue Errno::EAGAIN
|
69
|
-
raise Stop
|
70
|
-
rescue EOFError
|
71
|
-
Zeus.ui.error("fsevents-wrapper crashed.")
|
72
|
-
Process.kill("INT", 0)
|
73
|
-
end
|
74
|
-
files = lines.split("\n")
|
75
|
-
files[0] = "#{@buffer}#{files[0]}" unless @buffer == ""
|
76
|
-
unless lines[-1] == "\n"
|
77
|
-
@buffer = files.pop
|
78
|
-
end
|
79
|
-
|
80
|
-
files.each do |real|
|
81
|
-
file_did_change(real)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def file_did_change(real)
|
86
|
-
realpaths_for_givenpath(real).each do |given|
|
87
|
-
Zeus.ui.info("Dependency change at #{given}")
|
88
|
-
@change_callback.call(given)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def realpaths_for_givenpath(real)
|
93
|
-
@realpath_to_givenpath[real] || []
|
94
|
-
end
|
95
|
-
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
|
@@ -1,89 +0,0 @@
|
|
1
|
-
module Zeus
|
2
|
-
class Server
|
3
|
-
class ProcessTreeMonitor
|
4
|
-
STARTING_MARKER = "P"
|
5
|
-
FEATURE_MARKER = "F"
|
6
|
-
|
7
|
-
def datasource ; @sock ; end
|
8
|
-
def on_datasource_event ; handle_messages ; end
|
9
|
-
def close_child_socket ; @__CHILD__sock.close ; end
|
10
|
-
def close_parent_socket ; @sock.close ; end
|
11
|
-
|
12
|
-
def initialize(file_monitor, tree)
|
13
|
-
@root = tree
|
14
|
-
@file_monitor = file_monitor
|
15
|
-
|
16
|
-
@sock, @__CHILD__sock = open_socketpair
|
17
|
-
end
|
18
|
-
|
19
|
-
def kill_nodes_with_feature(file)
|
20
|
-
@root.kill_nodes_with_feature(file)
|
21
|
-
end
|
22
|
-
|
23
|
-
def kill_all_nodes
|
24
|
-
@root.kill!
|
25
|
-
end
|
26
|
-
|
27
|
-
module ChildProcessApi
|
28
|
-
def __CHILD__stage_starting_with_pid(name, pid)
|
29
|
-
buffer_send("#{STARTING_MARKER}#{name}:#{pid}")
|
30
|
-
end
|
31
|
-
|
32
|
-
def __CHILD__stage_has_feature(name, feature)
|
33
|
-
buffer_send("#{FEATURE_MARKER}#{name}:#{feature}")
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def buffer_send(msg)
|
39
|
-
@__CHILD__sock.send(msg, 0)
|
40
|
-
rescue Errno::ENOBUFS
|
41
|
-
sleep 0.2
|
42
|
-
retry
|
43
|
-
end
|
44
|
-
|
45
|
-
end ; include ChildProcessApi
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def handle_messages
|
50
|
-
50.times { handle_message }
|
51
|
-
rescue Stop
|
52
|
-
end
|
53
|
-
|
54
|
-
Stop = Class.new(Exception)
|
55
|
-
|
56
|
-
def handle_message
|
57
|
-
begin
|
58
|
-
data = @sock.recv_nonblock(4096)
|
59
|
-
rescue Errno::EAGAIN
|
60
|
-
raise Stop
|
61
|
-
end
|
62
|
-
case data[0]
|
63
|
-
when STARTING_MARKER
|
64
|
-
handle_starting_message(data[1..-1])
|
65
|
-
when FEATURE_MARKER
|
66
|
-
handle_feature_message(data[1..-1])
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def open_socketpair
|
71
|
-
Socket.pair(:UNIX, :DGRAM)
|
72
|
-
end
|
73
|
-
|
74
|
-
def handle_starting_message(data)
|
75
|
-
data =~ /(.+):(\d+)/
|
76
|
-
name, pid = $1.to_sym, $2.to_i
|
77
|
-
@root.stage_has_pid(name, pid)
|
78
|
-
end
|
79
|
-
|
80
|
-
def handle_feature_message(data)
|
81
|
-
data =~ /(.+?):(.*)/
|
82
|
-
name, file = $1.to_sym, $2
|
83
|
-
@root.stage_has_feature(name, file)
|
84
|
-
@file_monitor.watch(file)
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
data/lib/zeus/server/stage.rb
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
require 'zeus/server/stage/error_state'
|
2
|
-
require 'zeus/server/stage/feature_notifier'
|
3
|
-
|
4
|
-
module Zeus
|
5
|
-
class Server
|
6
|
-
# NONE of the code in the module is run in the master process,
|
7
|
-
# so every communication to the master must be done with IPC.
|
8
|
-
class Stage
|
9
|
-
|
10
|
-
attr_accessor :name, :stages, :actions
|
11
|
-
def initialize(server)
|
12
|
-
@server = server
|
13
|
-
end
|
14
|
-
|
15
|
-
def descendent_acceptors
|
16
|
-
@stages.map(&:descendent_acceptors).flatten
|
17
|
-
end
|
18
|
-
|
19
|
-
def run(close_parent_sockets = false)
|
20
|
-
@pid = fork {
|
21
|
-
setup_fork(close_parent_sockets)
|
22
|
-
run_actions
|
23
|
-
feature_notifier.notify_new_features
|
24
|
-
start_child_stages
|
25
|
-
handle_child_exit_loop!
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def setup_fork(close_parent_sockets)
|
32
|
-
$0 = "zeus #{process_type}: #{@name}"
|
33
|
-
@server.__CHILD__close_parent_sockets if close_parent_sockets
|
34
|
-
notify_started
|
35
|
-
trap("INT") { exit }
|
36
|
-
trap("TERM") { notify_terminated ; exit }
|
37
|
-
ActiveRecord::Base.clear_all_connections! rescue nil
|
38
|
-
end
|
39
|
-
|
40
|
-
def feature_notifier
|
41
|
-
FeatureNotifier.new(@server, @name)
|
42
|
-
end
|
43
|
-
|
44
|
-
def start_child_stages
|
45
|
-
@pids = {}
|
46
|
-
@stages.each do |stage|
|
47
|
-
@pids[stage.run] = stage
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def run_actions
|
52
|
-
begin
|
53
|
-
@actions.each(&:call)
|
54
|
-
rescue => e
|
55
|
-
extend(ErrorState)
|
56
|
-
handle_load_error(e)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def handle_child_exit_loop!
|
61
|
-
loop do
|
62
|
-
begin
|
63
|
-
pid = Process.wait
|
64
|
-
rescue Errno::ECHILD
|
65
|
-
sleep # if this is a terminal node, just let acceptors run...
|
66
|
-
end
|
67
|
-
stage = @pids[pid]
|
68
|
-
@pids[stage.run] = stage
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def notify_started
|
73
|
-
@server.__CHILD__stage_starting_with_pid(@name, Process.pid)
|
74
|
-
Zeus.ui.info("starting #{process_type} `#{@name}`")
|
75
|
-
end
|
76
|
-
|
77
|
-
def notify_terminated
|
78
|
-
Zeus.ui.info("killing #{process_type} `#{@name}`")
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
def process_type
|
83
|
-
"spawner"
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|