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