zeus 0.2.0.beta2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -14,10 +14,15 @@ Because waiting 25 seconds sucks, but waiting 0.4 seconds doesn't.
14
14
 
15
15
  Soon? You can use Zeus now, but don't expect it to be perfect. I'm working hard on it.
16
16
 
17
- ## Ugly bits
17
+ ## Requirements
18
18
 
19
- * Not battle-tested yet
20
- * Uses an obscene number of file descriptors
19
+ Pretty specific:
20
+
21
+ * OS X 10.7+
22
+ * Ruby 1.9+
23
+ * Backported GC from Ruby 2.0.
24
+
25
+ You can install the GC-patched ruby from [this gist](https://gist.github.com/1688857) or from RVM.
21
26
 
22
27
  ## Installation
23
28
 
@@ -52,22 +57,22 @@ Run some commands:
52
57
  zeus rake -T
53
58
  zeus runner omg.rb
54
59
 
60
+
55
61
  ## TODO (roughly prioritized)
56
62
 
57
- * Make sure client connection requests are handled immediately
58
- * Acceptors booting should not be dependent on passing all loaded features to the file monitor
59
- * Route all logging output through Zeus.ui
60
- * Handle connections for not-yet-started sockets
63
+ * Make sure that when a command process's connection is dropped, it is killed
64
+ * less leaky handling of at_exit pid killing
65
+ * Instead of exiting when requesting an as-yet-unbooted acceptor, wait until it's available then run.
61
66
  * Refactor, refactor, refactor...
67
+ * Make sure client connection requests are handled immediately (Chunk the select loop)
68
+ * Don't fork to handshake client to acceptor
69
+ * Eliminate the client-side exit lag for zeus commands.
62
70
  * Support other frameworks?
63
- * Periodically re-attempt to add lost files to KQueue
64
71
  * Figure out how to run full test suites without multiple env loads
65
- * Don't replace a socket with changed deps until the new one is ready
66
72
 
67
73
  ## Ideas (not quite TODOs)
68
74
 
69
75
  * (maybe) Start the preloader as a daemon transparently when any command is run, then wait for it to finish
70
- * Use fsevent instead of kqueue to reduce the obscene number of file descriptors.
71
76
  * Support inotify on linux
72
77
 
73
78
  ## Contributing
@@ -25,7 +25,7 @@ module Zeus
25
25
  set_winsize
26
26
 
27
27
  @winch = make_winch_channel
28
- pid = connect_to_server(command, args, slave)
28
+ @pid = connect_to_server(command, args, slave)
29
29
 
30
30
  buffer = ""
31
31
  begin
@@ -21,6 +21,10 @@ module Zeus
21
21
  # ^ configuration
22
22
  # V later use
23
23
 
24
+ def commands
25
+ [name, *aliases].map(&:to_s)
26
+ end
27
+
24
28
  def acceptors
25
29
  self
26
30
  end
@@ -9,11 +9,17 @@ module Process
9
9
  end
10
10
  end
11
11
 
12
+ def self.pids_to_ppids
13
+ Hash[*`ps -eo pid,ppid`.scan(/\d+/).map(&:to_i)]
14
+ end
15
+
12
16
  def self.descendants(base=Process.pid)
13
17
  descendants = Hash.new{|ht,k| ht[k]=[k]}
14
- Hash[*`ps -eo pid,ppid`.scan(/\d+/).map{|x|x.to_i}].each{|pid,ppid|
18
+
19
+ pids_to_ppids.each do |pid,ppid|
15
20
  descendants[ppid] << descendants[pid]
16
- }
21
+ end
22
+
17
23
  descendants[base].flatten - [base]
18
24
  end
19
25
  end
@@ -1,7 +1,6 @@
1
1
  require 'json'
2
2
  require 'socket'
3
3
 
4
- require 'rb-kqueue'
5
4
  require 'zeus/process'
6
5
 
7
6
  module Zeus
@@ -27,7 +26,8 @@ module Zeus
27
26
  @file_monitor = FileMonitor::FSEvent.new(&method(:dependency_did_change))
28
27
  @acceptor_registration_monitor = AcceptorRegistrationMonitor.new
29
28
  @process_tree_monitor = ProcessTreeMonitor.new
30
- @client_handler = ClientHandler.new(acceptor_registration_monitor)
29
+ acceptor_commands = self.class.acceptors.map(&:commands).flatten
30
+ @client_handler = ClientHandler.new(acceptor_commands, acceptor_registration_monitor)
31
31
 
32
32
  # TODO: deprecate Zeus::Server.define! maybe. We can do that better...
33
33
  @plan = @@definition.to_domain_object(self)
@@ -56,7 +56,6 @@ module Zeus
56
56
  def run
57
57
  $0 = "zeus master"
58
58
  trap("INT") { exit 0 }
59
- at_exit { Process.killall_descendants(9) }
60
59
 
61
60
  @r_msg, @w_msg = Socket.pair(:UNIX, :DGRAM)
62
61
 
@@ -14,9 +14,7 @@ module Zeus
14
14
  end
15
15
 
16
16
  def register_with_client_handler(pid)
17
- @a, @b = Socket.pair(:UNIX, :STREAM)
18
- @s_client_handler = UNIXSocket.for_fd(@a.fileno)
19
- @s_acceptor = UNIXSocket.for_fd(@b.fileno)
17
+ @s_client_handler, @s_acceptor = UNIXSocket.pair
20
18
 
21
19
  @s_acceptor.puts registration_data(pid)
22
20
 
@@ -27,8 +25,34 @@ module Zeus
27
25
  {pid: pid, commands: [name, *aliases], description: description}.to_json
28
26
  end
29
27
 
28
+ def descendent_acceptors
29
+ self
30
+ end
31
+
32
+ def print_error(io, error)
33
+ io.puts "#{error.backtrace[0]}: #{error.message} (#{error.class})"
34
+ error.backtrace[1..-1].each do |line|
35
+ io.puts "\tfrom #{line}"
36
+ end
37
+ end
38
+
39
+ def run_as_error(e)
40
+ register_with_client_handler(Process.pid)
41
+ Zeus.ui.as_zeus "starting error-state acceptor `#{@name}`"
42
+
43
+ Thread.new do
44
+ loop do
45
+ terminal = @s_acceptor.recv_io
46
+ _ = @s_acceptor.readline
47
+ @s_acceptor << 0 << "\n"
48
+ print_error(terminal, e)
49
+ terminal.close
50
+ end
51
+ end
52
+ end
53
+
30
54
  def run
31
- fork {
55
+ pid = fork {
32
56
  $0 = "zeus acceptor: #{@name}"
33
57
  pid = Process.pid
34
58
 
@@ -51,6 +75,7 @@ module Zeus
51
75
  terminal = @s_acceptor.recv_io
52
76
  arguments = JSON.parse(@s_acceptor.readline.chomp)
53
77
  child = fork do
78
+ $0 = "zeus runner: #{@name}"
54
79
  postfork_action!
55
80
  @s_acceptor << $$ << "\n"
56
81
  $stdin.reopen(terminal)
@@ -64,6 +89,9 @@ module Zeus
64
89
  terminal.close
65
90
  end
66
91
  }
92
+ currpid = Process.pid
93
+ at_exit { Process.kill(9, pid) if Process.pid == currpid rescue nil }
94
+ pid
67
95
  end
68
96
 
69
97
  def prefork_action! # TODO : refactor
@@ -6,11 +6,7 @@ module Zeus
6
6
  def on_datasource_event ; handle_registration ; end
7
7
 
8
8
  def initialize
9
- # note: if these aren't ivars, they go out of scope, get GC'd,
10
- # and cause the UNIXSockets to quit working... often in perplexing ways.
11
- @s, @r = Socket.pair(:UNIX, :DGRAM)
12
- @reg_monitor = UNIXSocket.for_fd(@s.fileno)
13
- @reg_acceptor = UNIXSocket.for_fd(@r.fileno)
9
+ @reg_monitor, @reg_acceptor = UNIXSocket.pair
14
10
  @acceptors = []
15
11
  end
16
12
 
@@ -27,8 +27,9 @@ module Zeus
27
27
  def datasource ; @server ; end
28
28
  def on_datasource_event ; handle_server_connection ; end
29
29
 
30
- def initialize(registration_monitor)
30
+ def initialize(acceptor_commands, registration_monitor)
31
31
  @reg_monitor = registration_monitor
32
+ @acceptor_commands = acceptor_commands
32
33
  @server = UNIXServer.new(Zeus::SOCKET_NAME)
33
34
  @server.listen(10)
34
35
  rescue Errno::EADDRINUSE
@@ -38,7 +39,18 @@ module Zeus
38
39
 
39
40
  def handle_server_connection
40
41
  s_client = @server.accept
41
- fork { handshake_client_to_acceptor(s_client) }
42
+ fork { handshake_client_to_acceptor(s_client) ; exit }
43
+ end
44
+
45
+ NoSuchCommand = Class.new(Exception)
46
+ AcceptorNotBooted = Class.new(Exception)
47
+ ApplicationLoadFailed = Class.new(Exception)
48
+
49
+ def exit_with_message(s_client, client_terminal, msg)
50
+ s_client << "0\n"
51
+ client_terminal << "[zeus] #{msg}\n"
52
+ client_terminal.close
53
+ s_client.close
42
54
  end
43
55
 
44
56
  # client clienthandler acceptor
@@ -57,9 +69,23 @@ module Zeus
57
69
  client_terminal = s_client.recv_io
58
70
 
59
71
  # 3
72
+ unless @acceptor_commands.include?(command.to_s)
73
+ return exit_with_message(
74
+ s_client, client_terminal,
75
+ "no such command `#{command}`.")
76
+ end
60
77
  acceptor = @reg_monitor.find_acceptor_for_command(command)
78
+ unless acceptor
79
+ return exit_with_message(
80
+ s_client, client_terminal,
81
+ "not yet ready to process `#{command}`. Try again right away.")
82
+ end
61
83
  usock = UNIXSocket.for_fd(acceptor.socket.fileno)
62
- # TODO handle nothing found
84
+ if usock.closed?
85
+ return exit_with_message(
86
+ s_client, client_terminal,
87
+ "`#{command}` handler is reloading a dependency. Try again right away.")
88
+ end
63
89
  usock.send_io(client_terminal)
64
90
 
65
91
  Zeus.ui.info "accepting connection for #{command}"
@@ -1,5 +1,3 @@
1
- require 'rb-kqueue'
2
-
3
1
  module Zeus
4
2
  class Server
5
3
  module FileMonitor
@@ -9,10 +9,40 @@ module Zeus
9
9
  @server = server
10
10
  end
11
11
 
12
- # There are a few things we want to accomplish:
13
- # 1. Running all the actions (each time this stage is killed and restarted)
14
- # 2. Starting all the substages (and restarting them when necessary)
15
- # 3. Starting all the acceptors (and restarting them when necessary)
12
+ def notify_feature(feature)
13
+ @server.w_feature "#{Process.pid}:#{feature}"
14
+ end
15
+
16
+ def descendent_acceptors
17
+ @stages.map(&:descendent_acceptors).flatten
18
+ end
19
+
20
+ def register_acceptors_as_errors(e)
21
+ descendent_acceptors.each do |acc|
22
+ acc.run_as_error(e)
23
+ end
24
+ end
25
+
26
+
27
+ def handle_load_error(e)
28
+ errored_file = e.backtrace[0].scan(/(.+?):\d+:in/)[0][0]
29
+
30
+ # handle relative paths
31
+ unless errored_file =~ /^\//
32
+ errored_file = File.expand_path(errored_file, Dir.pwd)
33
+ end
34
+
35
+ register_acceptors_as_errors(e)
36
+ # register all the decendent acceptors as stubs with errors
37
+
38
+ notify_feature(errored_file)
39
+ $LOADED_FEATURES.each { |f| notify_feature(f) }
40
+
41
+ # we do not need to do anything. We wait, until a dependency changes.
42
+ # At that point, we get killed and restarted.
43
+ sleep
44
+ end
45
+
16
46
  def run
17
47
  @pid = fork {
18
48
  $0 = "zeus spawner: #{@name}"
@@ -25,16 +55,18 @@ module Zeus
25
55
  exit 0
26
56
  }
27
57
 
28
- @actions.each(&:call)
58
+ begin
59
+ @actions.each(&:call)
60
+ rescue => e
61
+ handle_load_error(e)
62
+ end
29
63
 
30
64
  pids = {}
31
65
  @stages.each do |stage|
32
66
  pids[stage.run] = stage
33
67
  end
34
68
 
35
- $LOADED_FEATURES.each do |f|
36
- @server.w_feature "#{pid}:#{f}"
37
- end
69
+ $LOADED_FEATURES.each { |f| notify_feature(f) }
38
70
 
39
71
  loop do
40
72
  begin
@@ -42,15 +74,13 @@ module Zeus
42
74
  rescue Errno::ECHILD
43
75
  raise HasNoChildren.new("Stage `#{@name}` - All terminal nodes must be acceptors")
44
76
  end
45
- if (status = $?.exitstatus) > 0
46
- exit status
47
- else # restart the stage that died.
48
- stage = pids[pid]
49
- pids[stage.run] = stage
50
- end
77
+ stage = pids[pid]
78
+ pids[stage.run] = stage
51
79
  end
52
-
53
80
  }
81
+ currpid = Process.pid
82
+ at_exit { Process.kill(9, @pid) if Process.pid == currpid rescue nil }
83
+ @pid
54
84
  end
55
85
 
56
86
  end
@@ -52,7 +52,11 @@ module Zeus
52
52
  when :purple ; "\x1b[35m#{msg}\x1b[0m"
53
53
  else ; msg
54
54
  end
55
- puts msg
55
+ if msg[-1] == "\n"
56
+ puts msg
57
+ else
58
+ puts "#{msg}\n"
59
+ end
56
60
  end
57
61
 
58
62
 
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.2.0.beta2"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.beta2
5
- prerelease: 6
4
+ version: 0.2.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Burke Libbey
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-31 00:00:00.000000000 Z
12
+ date: 2012-08-02 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:
@@ -25,7 +25,6 @@ files:
25
25
  - README.md
26
26
  - Rakefile
27
27
  - bin/zeus
28
- - ext/.DS_Store
29
28
  - ext/fsevents-wrapper/fsevents-wrapper
30
29
  - ext/fsevents-wrapper/main.m
31
30
  - lib/thrud.rb
@@ -61,9 +60,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
60
  required_rubygems_version: !ruby/object:Gem::Requirement
62
61
  none: false
63
62
  requirements:
64
- - - ! '>'
63
+ - - ! '>='
65
64
  - !ruby/object:Gem::Version
66
- version: 1.3.1
65
+ version: '0'
67
66
  requirements: []
68
67
  rubyforge_project:
69
68
  rubygems_version: 1.8.11
Binary file