zeus 0.2.3 → 0.2.4

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