zeus 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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