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.
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