zeus 0.2.3 → 0.2.4

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.
data/TODO.md CHANGED
@@ -1,13 +1,8 @@
1
1
  ## TODO (roughly prioritized)
2
2
 
3
- * After an acceptor is killed, attempting to request that command while it is reloading causes a server error.
4
3
  * Make sure that when a command process's connection is dropped, it is killed
5
4
  * less leaky handling of at_exit pid killing
6
- * Instead of exiting when requesting an as-yet-unbooted acceptor, wait until it's available then run.
7
5
  * Refactor, refactor, refactor...
8
- * Make sure client connection requests are handled immediately (Chunk the select loop)
9
- * Don't fork to handshake client to acceptor
10
- * Eliminate the client-side exit lag for zeus commands.
11
6
  * Support other frameworks?
12
7
  * Figure out how to run full test suites without multiple env loads
13
8
 
data/lib/zeus/cli.rb CHANGED
@@ -17,6 +17,8 @@ module Zeus
17
17
  D
18
18
  # method_option "rails", type: :string, banner: "Use the rails template instead of auto-detecting based on project contents"
19
19
  def init
20
+ require 'fileutils'
21
+
20
22
  if File.exist?(".zeus.rb")
21
23
  Zeus.ui.error ".zeus.rb already exists at #{Dir.pwd}/.zeus.rb"
22
24
  exit 1
data/lib/zeus/client.rb CHANGED
@@ -69,7 +69,12 @@ module Zeus
69
69
  def handle_stdin(buffer)
70
70
  input = $stdin.readpartial(4096, buffer)
71
71
  input.scan(SIGNAL_REGEX).each { |signal|
72
- Process.kill(SIGNALS[signal], pid)
72
+ begin
73
+ Process.kill(SIGNALS[signal], pid)
74
+ rescue Errno::ESRCH
75
+ # we're trying to kill a process that died. Just quit.
76
+ exit
77
+ end
73
78
  }
74
79
  @master << input
75
80
  end
data/lib/zeus/server.rb CHANGED
@@ -79,9 +79,9 @@ module Zeus
79
79
  end
80
80
 
81
81
  def handle_messages
82
- loop do
82
+ 50.times {
83
83
  handle_message
84
- end
84
+ }
85
85
  rescue Errno::EAGAIN
86
86
  end
87
87
 
@@ -22,7 +22,7 @@ module Zeus
22
22
  end
23
23
 
24
24
  def registration_data(pid)
25
- {pid: pid, commands: [name, *aliases], description: description}.to_json
25
+ {type: 'registration', pid: pid, commands: [name, *aliases], description: description}.to_json
26
26
  end
27
27
 
28
28
  def descendent_acceptors
@@ -66,9 +66,12 @@ module Zeus
66
66
  exit 0
67
67
  }
68
68
 
69
- $LOADED_FEATURES.each do |f|
70
- @server.w_feature "#{pid}:#{f}"
71
- end
69
+ # Apparently threads don't continue in forks.
70
+ Thread.new {
71
+ $LOADED_FEATURES.each do |f|
72
+ @server.w_feature "#{pid}:#{f}"
73
+ end
74
+ }
72
75
 
73
76
  loop do
74
77
  prefork_action!
@@ -88,9 +91,14 @@ module Zeus
88
91
  Process.detach(child)
89
92
  terminal.close
90
93
  end
94
+
91
95
  }
92
96
  currpid = Process.pid
93
- at_exit { Process.kill(9, pid) if Process.pid == currpid rescue nil }
97
+ at_exit {
98
+ if Process.pid == currpid
99
+ Process.kill(9, pid) rescue nil
100
+ end
101
+ }
94
102
  pid
95
103
  end
96
104
 
@@ -3,25 +3,59 @@ module Zeus
3
3
  class AcceptorRegistrationMonitor
4
4
 
5
5
  def datasource ; @reg_monitor ; end
6
- def on_datasource_event ; handle_registration ; end
6
+ def on_datasource_event ; handle_message ; end
7
7
 
8
8
  def initialize
9
9
  @reg_monitor, @reg_acceptor = UNIXSocket.pair
10
10
  @acceptors = []
11
+ @pings = {}
11
12
  end
12
13
 
13
14
  AcceptorStub = Struct.new(:pid, :socket, :commands, :description)
14
15
 
15
- def handle_registration
16
+ def handle_message
16
17
  io = @reg_monitor.recv_io
17
18
 
18
19
  data = JSON.parse(io.readline.chomp)
20
+ type = data['type']
21
+
22
+ case type
23
+ when 'wait' ; handle_wait(io, data)
24
+ when 'registration' ; handle_registration(io, data)
25
+ when 'deregistration' ; handle_deregistration(io, data)
26
+ else raise "invalid message"
27
+ end
28
+ end
29
+
30
+ def handle_wait(io, data)
31
+ command = data['command'].to_s
32
+ @pings[command] ||= []
33
+ @pings[command] << io
34
+ end
35
+
36
+ def handle_deregistration(io, data)
37
+ pid = data['pid'].to_i
38
+ @acceptors.reject!{|acc|acc.pid == pid}
39
+ end
40
+
41
+ def handle_registration(io, data)
19
42
  pid = data['pid'].to_i
20
43
  commands = data['commands']
21
44
  description = data['description']
22
45
 
23
46
  @acceptors.reject!{|ac|ac.commands == commands}
24
47
  @acceptors << AcceptorStub.new(pid, io, commands, description)
48
+ notify_pings_for_commands(commands)
49
+ end
50
+
51
+ def notify_pings_for_commands(commands)
52
+ (commands || []).each do |command|
53
+ (@pings[command.to_s] || []).each do |ping|
54
+ ping.puts "ready\n"
55
+ ping.close
56
+ end
57
+ @pings[command.to_s] = nil
58
+ end
25
59
  end
26
60
 
27
61
  def find_acceptor_for_command(command)
@@ -39,9 +39,25 @@ module Zeus
39
39
 
40
40
  def handle_server_connection
41
41
  s_client = @server.accept
42
- fork { handshake_client_to_acceptor(s_client) ; exit }
42
+
43
+ # 1
44
+ data = JSON.parse(s_client.readline.chomp)
45
+ command, arguments = data.values_at('command', 'arguments')
46
+
47
+ # 2
48
+ client_terminal = s_client.recv_io
49
+
50
+ Thread.new {
51
+ loop do
52
+ pid = fork { handshake_client_to_acceptor(s_client, command, arguments, client_terminal) ; exit }
53
+ Process.wait(pid)
54
+ break unless $?.exitstatus == REATTEMPT_HANDSHAKE
55
+ end
56
+ }
43
57
  end
44
58
 
59
+ REATTEMPT_HANDSHAKE = 204
60
+
45
61
  NoSuchCommand = Class.new(Exception)
46
62
  AcceptorNotBooted = Class.new(Exception)
47
63
  ApplicationLoadFailed = Class.new(Exception)
@@ -53,6 +69,22 @@ module Zeus
53
69
  s_client.close
54
70
  end
55
71
 
72
+ def wait_for_acceptor(s_client, client_terminal, command, msg)
73
+ s_client << "0\n"
74
+ client_terminal << "[zeus] #{msg}\n"
75
+
76
+ regmsg = {type: 'wait', command: command}
77
+
78
+ s, r = UNIXSocket.pair
79
+ @reg_monitor.acceptor_registration_socket.send_io(r)
80
+ s << "#{regmsg.to_json}\n"
81
+
82
+ s.readline # wait
83
+ s.close
84
+
85
+ exit REATTEMPT_HANDSHAKE
86
+ end
87
+
56
88
  # client clienthandler acceptor
57
89
  # 1 ----------> | {command: String, arguments: [String]}
58
90
  # 2 ----------> | Terminal IO
@@ -60,14 +92,7 @@ module Zeus
60
92
  # 4 -----------> | Arguments (json array)
61
93
  # 5 <----------- | pid
62
94
  # 6 <--------- | pid
63
- def handshake_client_to_acceptor(s_client)
64
- # 1
65
- data = JSON.parse(s_client.readline.chomp)
66
- command, arguments = data.values_at('command', 'arguments')
67
-
68
- # 2
69
- client_terminal = s_client.recv_io
70
-
95
+ def handshake_client_to_acceptor(s_client, command, arguments, client_terminal)
71
96
  # 3
72
97
  unless @acceptor_commands.include?(command.to_s)
73
98
  return exit_with_message(
@@ -76,17 +101,24 @@ module Zeus
76
101
  end
77
102
  acceptor = @reg_monitor.find_acceptor_for_command(command)
78
103
  unless acceptor
79
- return exit_with_message(
80
- s_client, client_terminal,
81
- "not yet ready to process `#{command}`. Try again right away.")
104
+ wait_for_acceptor(
105
+ s_client, client_terminal, command,
106
+ "waiting for `#{command}` to finish booting...")
82
107
  end
83
108
  usock = UNIXSocket.for_fd(acceptor.socket.fileno)
84
109
  if usock.closed?
85
- return exit_with_message(
86
- s_client, client_terminal,
87
- "`#{command}` handler is reloading a dependency. Try again right away.")
110
+ wait_for_acceptor(
111
+ s_client, client_terminal, command,
112
+ "waiting for `#{command}` to finish reloading dependencies...")
113
+ end
114
+ begin
115
+ usock.send_io(client_terminal)
116
+ rescue Errno::EPIPE
117
+ wait_for_acceptor(
118
+ s_client, client_terminal, command,
119
+ "waiting for `#{command}` to finish reloading dependencies...")
88
120
  end
89
- usock.send_io(client_terminal)
121
+
90
122
 
91
123
  Zeus.ui.info "accepting connection for #{command}"
92
124
 
@@ -17,13 +17,13 @@ module Zeus
17
17
  end
18
18
 
19
19
  def handle_changed_files
20
- loop do
20
+ 50.times {
21
21
  begin
22
22
  read_and_notify_files
23
23
  rescue Errno::EAGAIN
24
24
  break
25
25
  end
26
- end
26
+ }
27
27
  end
28
28
 
29
29
  def read_and_notify_files
@@ -66,7 +66,9 @@ module Zeus
66
66
  pids[stage.run] = stage
67
67
  end
68
68
 
69
- $LOADED_FEATURES.each { |f| notify_feature(f) }
69
+ Thread.new {
70
+ $LOADED_FEATURES.each { |f| notify_feature(f) }
71
+ }
70
72
 
71
73
  loop do
72
74
  begin
data/lib/zeus/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
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.3
4
+ version: 0.2.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-02 00:00:00.000000000 Z
12
+ date: 2012-08-03 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Zeus preloads pretty much everything you'll ever want to use in development.
15
15
  email: