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.
Files changed (60) hide show
  1. data/Gemfile +0 -4
  2. data/Rakefile +52 -8
  3. data/bin/zeus +14 -6
  4. data/build/fsevents-wrapper +0 -0
  5. data/build/zeus-darwin-amd64 +0 -0
  6. data/build/zeus-linux-386 +0 -0
  7. data/build/zeus-linux-amd64 +0 -0
  8. data/examples/zeus.json +22 -0
  9. data/ext/fsevents-wrapper/fsevents-wrapper +0 -0
  10. data/ext/inotify-wrapper/extconf.rb +24 -0
  11. data/ext/inotify-wrapper/inotify-wrapper.cpp +86 -0
  12. data/lib/zeus.rb +123 -35
  13. data/lib/zeus/{server/load_tracking.rb → load_tracking.rb} +1 -3
  14. data/lib/zeus/rails.rb +141 -0
  15. data/man/build/zeus +49 -0
  16. data/man/build/zeus-init +13 -0
  17. data/man/build/zeus-init.txt +17 -0
  18. data/man/build/zeus-start +16 -0
  19. data/man/build/zeus-start.txt +18 -0
  20. data/man/build/zeus.txt +50 -0
  21. data/zeus.gemspec +17 -6
  22. metadata +27 -58
  23. data/.gitignore +0 -17
  24. data/.travis.yml +0 -5
  25. data/.zeus.rb +0 -11
  26. data/README.md +0 -73
  27. data/docs/acceptor_registration.md +0 -14
  28. data/docs/client_server_handshake.md +0 -25
  29. data/ext/fsevents-wrapper/main.m +0 -118
  30. data/lib/thrud.rb +0 -97
  31. data/lib/zeus/cli.rb +0 -80
  32. data/lib/zeus/client.rb +0 -114
  33. data/lib/zeus/client/winsize.rb +0 -28
  34. data/lib/zeus/error_printer.rb +0 -16
  35. data/lib/zeus/plan.rb +0 -18
  36. data/lib/zeus/plan/acceptor.rb +0 -38
  37. data/lib/zeus/plan/node.rb +0 -66
  38. data/lib/zeus/plan/stage.rb +0 -50
  39. data/lib/zeus/server.rb +0 -103
  40. data/lib/zeus/server/acceptor.rb +0 -79
  41. data/lib/zeus/server/acceptor_registration_monitor.rb +0 -75
  42. data/lib/zeus/server/client_handler.rb +0 -106
  43. data/lib/zeus/server/command_runner.rb +0 -70
  44. data/lib/zeus/server/file_monitor.rb +0 -8
  45. data/lib/zeus/server/file_monitor/fsevent.rb +0 -102
  46. data/lib/zeus/server/process_tree_monitor.rb +0 -89
  47. data/lib/zeus/server/stage.rb +0 -88
  48. data/lib/zeus/server/stage/error_state.rb +0 -42
  49. data/lib/zeus/server/stage/feature_notifier.rb +0 -38
  50. data/lib/zeus/templates/rails.rb +0 -133
  51. data/lib/zeus/ui.rb +0 -57
  52. data/lib/zeus/version.rb +0 -3
  53. data/spec/cli_spec.rb +0 -95
  54. data/spec/error_printer_spec.rb +0 -27
  55. data/spec/integration_spec.rb +0 -106
  56. data/spec/server/file_monitor/fsevent_spec.rb +0 -88
  57. data/spec/server/load_tracking_spec.rb +0 -67
  58. data/spec/server/process_tree_monitor_spec.rb +0 -50
  59. data/spec/spec_helper.rb +0 -38
  60. 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,8 +0,0 @@
1
- require 'zeus/server/file_monitor/fsevent'
2
-
3
- module Zeus
4
- class Server
5
- module FileMonitor
6
- end
7
- end
8
- 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
@@ -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