zeus 0.2.4 → 0.2.5

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.
@@ -123,11 +123,9 @@ void configureTimerAndRun()
123
123
 
124
124
  int main(int argc, const char * argv[])
125
125
  {
126
- @autoreleasepool {
127
- _watchedFiles = CFArrayCreateMutable(NULL, 0, NULL);
128
- _fileIsWatched = [[NSMutableDictionary alloc] initWithCapacity:500];
126
+ _watchedFiles = CFArrayCreateMutable(NULL, 0, NULL);
127
+ _fileIsWatched = [[NSMutableDictionary alloc] initWithCapacity:500];
129
128
 
130
- configureTimerAndRun();
131
- }
129
+ configureTimerAndRun();
132
130
  return 0;
133
131
  }
@@ -7,6 +7,7 @@ module Zeus
7
7
  autoload :Stage, 'zeus/server/stage'
8
8
  autoload :Acceptor, 'zeus/server/acceptor'
9
9
  autoload :FileMonitor, 'zeus/server/file_monitor'
10
+ autoload :ProcessTree, 'zeus/server/process_tree'
10
11
  autoload :ClientHandler, 'zeus/server/client_handler'
11
12
  autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
12
13
  autoload :AcceptorRegistrationMonitor, 'zeus/server/acceptor_registration_monitor'
@@ -19,15 +20,13 @@ module Zeus
19
20
  @@definition.acceptors
20
21
  end
21
22
 
22
- attr_reader :client_handler, :acceptor_registration_monitor
23
23
  def initialize
24
24
  @file_monitor = FileMonitor::FSEvent.new(&method(:dependency_did_change))
25
25
  @acceptor_registration_monitor = AcceptorRegistrationMonitor.new
26
- @process_tree_monitor = ProcessTreeMonitor.new
26
+ @process_tree_monitor = ProcessTreeMonitor.new(@file_monitor)
27
27
  acceptor_commands = self.class.acceptors.map(&:commands).flatten
28
- @client_handler = ClientHandler.new(acceptor_commands, acceptor_registration_monitor)
28
+ @client_handler = ClientHandler.new(acceptor_commands, self)
29
29
 
30
- # TODO: deprecate Zeus::Server.define! maybe. We can do that better...
31
30
  @plan = @@definition.to_domain_object(self)
32
31
  end
33
32
 
@@ -35,85 +34,51 @@ module Zeus
35
34
  @process_tree_monitor.kill_nodes_with_feature(file)
36
35
  end
37
36
 
38
- PID_TYPE = "P"
39
- def w_pid line
40
- @w_msg.send(PID_TYPE + line, 0)
41
- rescue Errno::ENOBUFS
42
- sleep 0.2
43
- retry
44
- end
45
-
46
- FEATURE_TYPE = "F"
47
- def w_feature line
48
- @w_msg.send(FEATURE_TYPE + line, 0)
49
- rescue Errno::ENOBUFS
50
- sleep 0.2
51
- retry
37
+ def monitors
38
+ [@file_monitor, @process_tree_monitor, @acceptor_registration_monitor, @client_handler]
52
39
  end
53
40
 
54
41
  def run
55
42
  $0 = "zeus master"
56
43
  trap("INT") { exit 0 }
57
44
 
58
- @r_msg, @w_msg = Socket.pair(:UNIX, :DGRAM)
59
-
60
- # boot the actual app
61
- @plan.run
62
- @w_msg.close
45
+ @plan.run(true) # boot the actual app
46
+ monitors.each(&:close_child_socket)
63
47
 
64
48
  loop do
65
- monitors = [@file_monitor, @acceptor_registration_monitor, @client_handler]
66
- # TODO: Make @r_msg a Monitor instead. All that logic should be its own thing.
67
- datasources = [@r_msg, *monitors.map(&:datasource)]
68
-
69
- ready, _, _ = IO.select(datasources, [], [], 1)
49
+ ready, = IO.select(monitors.map(&:datasource), [], [], 1)
70
50
  next unless ready
71
51
  monitors.each do |m|
72
52
  m.on_datasource_event if ready.include?(m.datasource)
73
53
  end
74
- handle_messages if ready.include?(@r_msg)
75
54
  end
76
-
77
55
  ensure
78
56
  File.unlink(Zeus::SOCKET_NAME)
79
57
  end
80
58
 
81
- def handle_messages
82
- 50.times {
83
- handle_message
84
- }
85
- rescue Errno::EAGAIN
86
- end
59
+ module ChildProcessApi
87
60
 
88
- def handle_message
89
- data = @r_msg.recv_nonblock(1024)
90
- case data[0]
91
- when FEATURE_TYPE
92
- handle_feature_message(data[1..-1])
93
- when PID_TYPE
94
- handle_pid_message(data[1..-1])
95
- else
96
- raise "Unrecognized message"
61
+ def __CHILD__close_parent_sockets
62
+ monitors.each(&:close_parent_socket)
97
63
  end
98
- end
99
64
 
100
- def handle_pid_message(data)
101
- data =~ /(\d+):(\d+)/
102
- pid, ppid = $1.to_i, $2.to_i
103
- @process_tree_monitor.process_has_parent(pid, ppid)
104
- end
65
+ def __CHILD__pid_has_ppid(pid, ppid)
66
+ @process_tree_monitor.__CHILD__send_pid("#{pid}:#{Process.ppid}")
67
+ end
105
68
 
106
- def handle_feature_message(data)
107
- data =~ /(\d+):(.*)/
108
- pid, file = $1.to_i, $2
109
- @process_tree_monitor.process_has_feature(pid, file)
110
- @file_monitor.watch(file)
111
- end
69
+ def __CHILD__pid_has_feature(pid, feature)
70
+ @process_tree_monitor.__CHILD__send_feature("#{pid}:#{feature}")
71
+ end
112
72
 
113
- def self.pid_has_file(pid, file)
114
- @@files[file] ||= []
115
- @@files[file] << pid
116
- end
73
+ def __CHILD__register_acceptor(io)
74
+ @acceptor_registration_monitor.__CHILD__register_acceptor(io)
75
+ end
76
+
77
+ def __CHILD__find_acceptor_for_command(command)
78
+ @acceptor_registration_monitor.__CHILD__find_acceptor_for_command(command)
79
+ end
80
+
81
+ end ; include ChildProcessApi
117
82
 
118
83
  end
119
84
  end
@@ -9,8 +9,6 @@ module Zeus
9
9
  attr_accessor :name, :aliases, :description, :action
10
10
  def initialize(server)
11
11
  @server = server
12
- @client_handler = server.client_handler
13
- @registration_monitor = server.acceptor_registration_monitor
14
12
  end
15
13
 
16
14
  def register_with_client_handler(pid)
@@ -18,7 +16,7 @@ module Zeus
18
16
 
19
17
  @s_acceptor.puts registration_data(pid)
20
18
 
21
- @registration_monitor.acceptor_registration_socket.send_io(@s_client_handler)
19
+ @server.__CHILD__register_acceptor(@s_client_handler)
22
20
  end
23
21
 
24
22
  def registration_data(pid)
@@ -58,7 +56,7 @@ module Zeus
58
56
 
59
57
  register_with_client_handler(pid)
60
58
 
61
- @server.w_pid "#{pid}:#{Process.ppid}"
59
+ @server.__CHILD__pid_has_ppid(pid, Process.ppid)
62
60
 
63
61
  Zeus.ui.as_zeus "starting acceptor `#{@name}`"
64
62
  trap("INT") {
@@ -69,7 +67,7 @@ module Zeus
69
67
  # Apparently threads don't continue in forks.
70
68
  Thread.new {
71
69
  $LOADED_FEATURES.each do |f|
72
- @server.w_feature "#{pid}:#{f}"
70
+ @server.__CHILD__pid_has_feature(pid, f)
73
71
  end
74
72
  }
75
73
 
@@ -2,11 +2,13 @@ module Zeus
2
2
  class Server
3
3
  class AcceptorRegistrationMonitor
4
4
 
5
- def datasource ; @reg_monitor ; end
5
+ def datasource ; @sock ; end
6
6
  def on_datasource_event ; handle_message ; end
7
+ def close_child_socket ; @__CHILD__sock.close ; end
8
+ def close_parent_socket ; @sock.close ; end
7
9
 
8
10
  def initialize
9
- @reg_monitor, @reg_acceptor = UNIXSocket.pair
11
+ @sock, @__CHILD__sock = UNIXSocket.pair
10
12
  @acceptors = []
11
13
  @pings = {}
12
14
  end
@@ -14,7 +16,7 @@ module Zeus
14
16
  AcceptorStub = Struct.new(:pid, :socket, :commands, :description)
15
17
 
16
18
  def handle_message
17
- io = @reg_monitor.recv_io
19
+ io = @sock.recv_io
18
20
 
19
21
  data = JSON.parse(io.readline.chomp)
20
22
  type = data['type']
@@ -58,15 +60,20 @@ module Zeus
58
60
  end
59
61
  end
60
62
 
61
- def find_acceptor_for_command(command)
62
- @acceptors.detect { |acceptor|
63
- acceptor.commands.include?(command)
64
- }
65
- end
66
63
 
67
- def acceptor_registration_socket
68
- @reg_acceptor
69
- end
64
+ module ChildProcessApi
65
+
66
+ def __CHILD__find_acceptor_for_command(command)
67
+ @acceptors.detect { |acceptor|
68
+ acceptor.commands.include?(command)
69
+ }
70
+ end
71
+
72
+ def __CHILD__register_acceptor(io)
73
+ @__CHILD__sock.send_io(io)
74
+ end
75
+
76
+ end ; include ChildProcessApi
70
77
 
71
78
  end
72
79
 
@@ -24,21 +24,23 @@ module Zeus
24
24
  # 4. ClientHandler forwards the pid to the client over S_CLI.
25
25
  #
26
26
  class ClientHandler
27
- def datasource ; @server ; end
27
+ def datasource ; @listener ; end
28
28
  def on_datasource_event ; handle_server_connection ; end
29
+ def close_child_socket ; end
30
+ def close_parent_socket ; @listener.close ; end
29
31
 
30
- def initialize(acceptor_commands, registration_monitor)
31
- @reg_monitor = registration_monitor
32
+ def initialize(acceptor_commands, server)
33
+ @server = server
32
34
  @acceptor_commands = acceptor_commands
33
- @server = UNIXServer.new(Zeus::SOCKET_NAME)
34
- @server.listen(10)
35
+ @listener = UNIXServer.new(Zeus::SOCKET_NAME)
36
+ @listener.listen(10)
35
37
  rescue Errno::EADDRINUSE
36
38
  Zeus.ui.error "Zeus appears to be already running in this project. If not, remove .zeus.sock and try again."
37
39
  exit 1
38
40
  end
39
41
 
40
42
  def handle_server_connection
41
- s_client = @server.accept
43
+ s_client = @listener.accept
42
44
 
43
45
  # 1
44
46
  data = JSON.parse(s_client.readline.chomp)
@@ -76,7 +78,8 @@ module Zeus
76
78
  regmsg = {type: 'wait', command: command}
77
79
 
78
80
  s, r = UNIXSocket.pair
79
- @reg_monitor.acceptor_registration_socket.send_io(r)
81
+
82
+ @server.__CHILD__register_acceptor(r)
80
83
  s << "#{regmsg.to_json}\n"
81
84
 
82
85
  s.readline # wait
@@ -99,7 +102,7 @@ module Zeus
99
102
  s_client, client_terminal,
100
103
  "no such command `#{command}`.")
101
104
  end
102
- acceptor = @reg_monitor.find_acceptor_for_command(command)
105
+ acceptor = @server.__CHILD__find_acceptor_for_command(command)
103
106
  unless acceptor
104
107
  wait_for_acceptor(
105
108
  s_client, client_terminal, command,
@@ -6,8 +6,10 @@ module Zeus
6
6
  class FSEvent
7
7
  WRAPPER_PATH = File.expand_path("../../../../../ext/fsevents-wrapper/fsevents-wrapper", __FILE__)
8
8
 
9
- def datasource ; @io_out ; end
9
+ def datasource ; @io_out ; end
10
10
  def on_datasource_event ; handle_changed_files ; end
11
+ def close_child_socket ; end
12
+ def close_parent_socket ; [@io_in, @io_out].each(&:close) ; end
11
13
 
12
14
  def initialize(&change_callback)
13
15
  @change_callback = change_callback
@@ -17,13 +19,8 @@ module Zeus
17
19
  end
18
20
 
19
21
  def handle_changed_files
20
- 50.times {
21
- begin
22
- read_and_notify_files
23
- rescue Errno::EAGAIN
24
- break
25
- end
26
- }
22
+ 50.times { read_and_notify_files }
23
+ rescue Errno::EAGAIN
27
24
  end
28
25
 
29
26
  def read_and_notify_files
@@ -42,7 +39,6 @@ module Zeus
42
39
  def watch(file)
43
40
  return false if @watched_files[file]
44
41
  @watched_files[file] = true
45
- File.open('a.log', 'a') { |f| f.puts file }
46
42
  @io_in.puts file
47
43
  true
48
44
  end
@@ -0,0 +1,78 @@
1
+ module Zeus
2
+ class Server
3
+ class ProcessTree
4
+ class Node
5
+ attr_accessor :pid, :children, :features
6
+ def initialize(pid)
7
+ @pid, @children, @features = pid, [], {}
8
+ end
9
+
10
+ def add_child(node)
11
+ self.children << node
12
+ end
13
+
14
+ def add_feature(feature)
15
+ self.features[feature] = true
16
+ end
17
+
18
+ def has_feature?(feature)
19
+ self.features[feature] == true
20
+ end
21
+
22
+ def inspect
23
+ "(#{pid}:#{features.size}:[#{children.map(&:inspect).join(",")}])"
24
+ end
25
+
26
+ end
27
+
28
+ def inspect
29
+ @root.inspect
30
+ end
31
+
32
+ def initialize
33
+ @root = Node.new(Process.pid)
34
+ @nodes_by_pid = {Process.pid => @root}
35
+ end
36
+
37
+ def node_for_pid(pid)
38
+ @nodes_by_pid[pid.to_i] ||= Node.new(pid.to_i)
39
+ end
40
+
41
+ def process_has_parent(pid, ppid)
42
+ curr = node_for_pid(pid)
43
+ base = node_for_pid(ppid)
44
+ base.add_child(curr)
45
+ end
46
+
47
+ def process_has_feature(pid, feature)
48
+ node = node_for_pid(pid)
49
+ node.add_feature(feature)
50
+ end
51
+
52
+ def kill_node(node)
53
+ @nodes_by_pid.delete(node.pid)
54
+ # recall that this process explicitly traps INT -> exit 0
55
+ Process.kill("INT", node.pid)
56
+ end
57
+
58
+ def kill_nodes_with_feature(file, base = @root)
59
+ if base.has_feature?(file)
60
+ if base == @root.children[0] || base == @root
61
+ Zeus.ui.error "One of zeus's dependencies changed. Not killing zeus. You may have to restart the server."
62
+ return false
63
+ end
64
+ kill_node(base)
65
+ return true
66
+ else
67
+ base.children.dup.each do |node|
68
+ if kill_nodes_with_feature(file, node)
69
+ base.children.delete(node)
70
+ end
71
+ end
72
+ return false
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -1,97 +1,71 @@
1
1
  module Zeus
2
2
  class Server
3
3
  class ProcessTreeMonitor
4
+ PID_TYPE = "P"
5
+ FEATURE_TYPE = "F"
4
6
 
5
- def initialize
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)
6
13
  @tree = ProcessTree.new
14
+ @file_monitor = file_monitor
15
+
16
+ @sock, @__CHILD__sock = Socket.pair(:UNIX, :DGRAM)
7
17
  end
8
18
 
9
19
  def kill_nodes_with_feature(file)
10
20
  @tree.kill_nodes_with_feature(file)
11
21
  end
12
22
 
13
- def process_has_feature(pid, file)
14
- @tree.process_has_feature(pid, file)
23
+ def handle_messages
24
+ 50.times { handle_message }
25
+ rescue Errno::EAGAIN
15
26
  end
16
27
 
17
- def process_has_parent(pid, ppid)
18
- @tree.process_has_parent(pid, ppid)
19
- end
20
-
21
- class ProcessTree
22
- class Node
23
- attr_accessor :pid, :children, :features
24
- def initialize(pid)
25
- @pid, @children, @features = pid, [], {}
26
- end
27
-
28
- def add_child(node)
29
- self.children << node
30
- end
31
-
32
- def add_feature(feature)
33
- self.features[feature] = true
34
- end
35
-
36
- def has_feature?(feature)
37
- self.features[feature] == true
38
- end
39
-
40
- def inspect
41
- "(#{pid}:#{features.size}:[#{children.map(&:inspect).join(",")}])"
42
- end
43
-
44
- end
45
-
46
- def inspect
47
- @root.inspect
48
- end
49
-
50
- def initialize
51
- @root = Node.new(Process.pid)
52
- @nodes_by_pid = {Process.pid => @root}
28
+ def handle_message
29
+ data = @sock.recv_nonblock(1024)
30
+ case data[0]
31
+ when FEATURE_TYPE
32
+ handle_feature_message(data[1..-1])
33
+ when PID_TYPE
34
+ handle_pid_message(data[1..-1])
35
+ else
36
+ raise "Unrecognized message"
53
37
  end
38
+ end
54
39
 
55
- def node_for_pid(pid)
56
- @nodes_by_pid[pid.to_i] ||= Node.new(pid.to_i)
57
- end
40
+ def handle_pid_message(data)
41
+ data =~ /(\d+):(\d+)/
42
+ pid, ppid = $1.to_i, $2.to_i
43
+ @tree.process_has_parent(pid, ppid)
44
+ end
58
45
 
59
- def process_has_parent(pid, ppid)
60
- curr = node_for_pid(pid)
61
- base = node_for_pid(ppid)
62
- base.add_child(curr)
63
- end
46
+ def handle_feature_message(data)
47
+ data =~ /(\d+):(.*)/
48
+ pid, file = $1.to_i, $2
49
+ @tree.process_has_feature(pid, file)
50
+ @file_monitor.watch(file)
51
+ end
64
52
 
65
- def process_has_feature(pid, feature)
66
- node = node_for_pid(pid)
67
- node.add_feature(feature)
68
- end
69
53
 
70
- def kill_node(node)
71
- @nodes_by_pid.delete(node.pid)
72
- # recall that this process explicitly traps INT -> exit 0
73
- Process.kill("INT", node.pid)
54
+ module ChildProcessApi
55
+ def __CHILD__send_pid(message)
56
+ @__CHILD__sock.send(PID_TYPE + message, 0)
57
+ rescue Errno::ENOBUFS
58
+ sleep 0.2
59
+ retry
74
60
  end
75
61
 
76
- def kill_nodes_with_feature(file, base = @root)
77
- if base.has_feature?(file)
78
- if base == @root.children[0] || base == @root
79
- Zeus.ui.error "One of zeus's dependencies changed. Not killing zeus. You may have to restart the server."
80
- return false
81
- end
82
- kill_node(base)
83
- return true
84
- else
85
- base.children.dup.each do |node|
86
- if kill_nodes_with_feature(file, node)
87
- base.children.delete(node)
88
- end
89
- end
90
- return false
91
- end
62
+ def __CHILD__send_feature(message)
63
+ @__CHILD__sock.send(FEATURE_TYPE + message, 0)
64
+ rescue Errno::ENOBUFS
65
+ sleep 0.2
66
+ retry
92
67
  end
93
-
94
- end
68
+ end ; include ChildProcessApi
95
69
 
96
70
  end
97
71
  end
@@ -1,5 +1,7 @@
1
1
  module Zeus
2
2
  class Server
3
+ # NONE of the code in the module is run in the master process,
4
+ # so every communication to the master must be done with IPC.
3
5
  class Stage
4
6
  HasNoChildren = Class.new(Exception)
5
7
 
@@ -10,7 +12,7 @@ module Zeus
10
12
  end
11
13
 
12
14
  def notify_feature(feature)
13
- @server.w_feature "#{Process.pid}:#{feature}"
15
+ @server.__CHILD__pid_has_feature(Process.pid, feature)
14
16
  end
15
17
 
16
18
  def descendent_acceptors
@@ -43,11 +45,13 @@ module Zeus
43
45
  sleep
44
46
  end
45
47
 
46
- def run
48
+ def run(close_parent_sockets = false)
47
49
  @pid = fork {
50
+ # This is only passed to the top-level stage, from Server#run, not sub-stages.
51
+ @server.__CHILD__close_parent_sockets if close_parent_sockets
52
+
48
53
  $0 = "zeus spawner: #{@name}"
49
- pid = Process.pid
50
- @server.w_pid "#{pid}:#{Process.ppid}"
54
+ @server.__CHILD__pid_has_ppid(Process.pid, Process.ppid)
51
55
 
52
56
  Zeus.ui.as_zeus("starting spawner `#{@name}`")
53
57
  trap("INT") {
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -39,6 +39,7 @@ files:
39
39
  - lib/zeus/server/client_handler.rb
40
40
  - lib/zeus/server/file_monitor.rb
41
41
  - lib/zeus/server/file_monitor/fsevent.rb
42
+ - lib/zeus/server/process_tree.rb
42
43
  - lib/zeus/server/process_tree_monitor.rb
43
44
  - lib/zeus/server/stage.rb
44
45
  - lib/zeus/templates/rails.rb