zeus 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -45,11 +45,16 @@ Run some commands:
45
45
  zeus rake -T
46
46
  zeus runner omg.rb
47
47
 
48
+ ## What's coming?
49
+
50
+ [See the roadmap](https://github.com/burke/zeus/wiki/Roadmap)
48
51
 
49
52
  ## Contributing
50
53
 
51
54
  Fork, Branch, Pull Request.
52
55
 
56
+ Check [Rails tests](https://github.com/burke/zeus-extended-testcases) if you change something rails related.
57
+
53
58
  ## Thanks...
54
59
 
55
60
  * To [Stefan Penner](http://github.com/stefanpenner) for discussion and various contributions.
@@ -64,3 +69,4 @@ Try these libraries instead:
64
69
  * [spin](https://github.com/jstorimer/spin)
65
70
  * [spork](https://github.com/sporkrb/spork)
66
71
  * [guard](https://github.com/guard/guard)
72
+
@@ -1,4 +1,12 @@
1
- require "io/console"
1
+ # encoding: utf-8
2
+ begin
3
+ require "io/console"
4
+ rescue LoadError
5
+ Zeus.ui.error "io/console not found. Please `gem install io-console` or, preferably, " +
6
+ "install ruby 1.9.3 by following the instructions at: " +
7
+ "https://gist.github.com/1688857"
8
+ exit 1
9
+ end
2
10
  require "json"
3
11
  require "pty"
4
12
  require "socket"
@@ -44,8 +44,6 @@ module Zeus
44
44
  end
45
45
  end
46
46
 
47
- private
48
-
49
47
  # We send STOP before actually killing the processes here.
50
48
  # This is to prevent parents from respawning before all the children
51
49
  # are killed. This prevents a race condition.
@@ -56,7 +54,9 @@ module Zeus
56
54
  old_pid = pid
57
55
  self.pid = nil
58
56
  Process.kill("KILL", old_pid) if old_pid
59
- end ; protected :kill!
57
+ end
58
+
59
+ private
60
60
 
61
61
  def node_for_name(name)
62
62
  @nodes_by_name ||= __nodes_by_name
@@ -12,6 +12,7 @@ module Zeus
12
12
  autoload :LoadTracking, 'zeus/server/load_tracking'
13
13
  autoload :ForkedProcess, 'zeus/server/forked_process'
14
14
  autoload :ClientHandler, 'zeus/server/client_handler'
15
+ autoload :CommandRunner, 'zeus/server/command_runner'
15
16
  autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
16
17
  autoload :AcceptorRegistrationMonitor, 'zeus/server/acceptor_registration_monitor'
17
18
 
@@ -48,9 +49,11 @@ module Zeus
48
49
  exit 0
49
50
  }
50
51
 
51
- LoadTracking.inject!(self)
52
+ LoadTracking.server = self
52
53
 
53
54
  @plan.run(true) # boot the actual app
55
+ master = Process.pid
56
+ at_exit { @process_tree_monitor.kill_all_nodes if Process.pid == master }
54
57
  monitors.each(&:close_child_socket)
55
58
 
56
59
  runloop!
@@ -4,53 +4,29 @@ require 'socket'
4
4
  # See Zeus::Server::ClientHandler for relevant documentation
5
5
  module Zeus
6
6
  class Server
7
- class Acceptor < ForkedProcess
8
- attr_accessor :aliases, :description, :action
7
+ class Acceptor
8
+ attr_accessor :aliases, :description, :action, :name
9
+ def initialize(server)
10
+ @server = server
11
+ end
9
12
 
10
13
  def descendent_acceptors
11
14
  self
12
15
  end
13
16
 
14
- def before_setup
17
+ def run
15
18
  register_with_client_handler(Process.pid)
16
- end
17
-
18
- def runloop!
19
- loop do
20
- prefork_action!
21
- terminal, arguments = accept_connection # blocking
22
- child = fork { __RUNNER__run(terminal, arguments) }
23
- terminal.close
19
+ Zeus.ui.info("starting #{process_type} `#{@name}`")
24
20
 
25
- Process.detach(child)
26
- end
27
- end
28
-
29
- def __RUNNER__run(terminal, arguments)
30
- $0 = "zeus runner: #{@name}"
31
- Process.setsid
32
- postfork_action!
33
- @s_acceptor << $$ << "\n"
34
- $stdin.reopen(terminal)
35
- $stdout.reopen(terminal)
36
- $stderr.reopen(terminal)
37
- ARGV.replace(arguments)
38
-
39
- @action.call
40
- ensure
41
- # TODO this is a whole lot of voodoo that I don't really understand.
42
- # I need to figure out how best to make the process disconenct cleanly.
43
- dnw, dnr = File.open("/dev/null", "w+"), File.open("/dev/null", "r+")
44
- $stderr.reopen(dnr)
45
- $stdout.reopen(dnr)
46
- terminal.close
47
- $stdin.reopen(dnw)
48
- Process.kill(9, $$)
49
- exit 0
21
+ thread_with_backtrace_on_error { runloop! }
50
22
  end
51
23
 
52
24
  private
53
25
 
26
+ def command_runner
27
+ CommandRunner.new(name, action, @s_acceptor)
28
+ end
29
+
54
30
  def register_with_client_handler(pid)
55
31
  @s_client_handler, @s_acceptor = UNIXSocket.pair
56
32
  @s_acceptor.puts registration_data(pid)
@@ -72,41 +48,45 @@ module Zeus
72
48
  "acceptor"
73
49
  end
74
50
 
51
+ def print_error(io, error)
52
+ io.puts "#{error.backtrace[0]}: #{error.message} (#{error.class})"
53
+ error.backtrace[1..-1].each do |line|
54
+ io.puts "\tfrom #{line}"
55
+ end
56
+ end
75
57
 
76
- # these two methods should be part of the configuration DSL.
77
- # They're here for now, but I want them out.
78
- def prefork_action! # TODO : refactor
79
- ActiveRecord::Base.clear_all_connections! rescue nil
58
+ def thread_with_backtrace_on_error(&b)
59
+ Thread.new {
60
+ begin
61
+ b.call
62
+ rescue => e
63
+ print_error($stdout, e)
64
+ end
65
+ }
80
66
  end
81
67
 
82
- def postfork_action! # TODO :refactor
83
- ActiveRecord::Base.establish_connection rescue nil
84
- # ActiveSupport::DescendantsTracker.clear rescue nil
85
- # ActiveSupport::Dependencies.clear rescue nil
68
+ def runloop!
69
+ loop do
70
+ terminal, arguments = accept_connection # blocking
71
+ command_runner.run(terminal, arguments)
72
+ end
86
73
  end
87
74
 
88
75
  module ErrorState
76
+ NOT_A_PID = 0
89
77
  attr_accessor :error
90
78
 
91
- def print_error(io, error = @error)
92
- io.puts "#{error.backtrace[0]}: #{error.message} (#{error.class})"
93
- error.backtrace[1..-1].each do |line|
94
- io.puts "\tfrom #{line}"
95
- end
79
+ def process_type
80
+ "error-state acceptor"
96
81
  end
97
82
 
98
- def run
99
- register_with_client_handler(Process.pid)
100
- Zeus.ui.info "starting error-state acceptor `#{@name}`"
101
-
102
- Thread.new do
103
- loop do
104
- terminal = @s_acceptor.recv_io
105
- _ = @s_acceptor.readline
106
- @s_acceptor << 0 << "\n"
107
- print_error(terminal)
108
- terminal.close
109
- end
83
+ def runloop!
84
+ loop do
85
+ terminal = @s_acceptor.recv_io
86
+ _ = @s_acceptor.readline
87
+ @s_acceptor << NOT_A_PID << "\n"
88
+ print_error(terminal, @error)
89
+ terminal.close
110
90
  end
111
91
  end
112
92
 
@@ -0,0 +1,50 @@
1
+ module Zeus
2
+ class Server
3
+ class CommandRunner
4
+
5
+ def initialize(name, action, s_acceptor)
6
+ @name = name
7
+ @action = action
8
+ @s_acceptor = s_acceptor
9
+ end
10
+
11
+ def run(terminal, arguments)
12
+ child = fork { _run(terminal, arguments) }
13
+ terminal.close
14
+ Process.detach(child)
15
+ child
16
+ end
17
+
18
+ private
19
+
20
+ def _run(terminal, arguments)
21
+ $0 = "zeus runner: #{@name}"
22
+ Process.setsid
23
+ reconnect_activerecord!
24
+ @s_acceptor << $$ << "\n"
25
+ $stdin.reopen(terminal)
26
+ $stdout.reopen(terminal)
27
+ $stderr.reopen(terminal)
28
+ ARGV.replace(arguments)
29
+
30
+ @action.call
31
+ ensure
32
+ # TODO this is a whole lot of voodoo that I don't really understand.
33
+ # I need to figure out how best to make the process disconenct cleanly.
34
+ dnw, dnr = File.open("/dev/null", "w+"), File.open("/dev/null", "r+")
35
+ $stderr.reopen(dnr)
36
+ $stdout.reopen(dnr)
37
+ terminal.close
38
+ $stdin.reopen(dnw)
39
+ Process.kill(9, $$)
40
+ exit 0
41
+ end
42
+
43
+ def reconnect_activerecord!
44
+ ActiveRecord::Base.clear_all_connections! rescue nil
45
+ ActiveRecord::Base.establish_connection rescue nil
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -66,7 +66,6 @@ module Zeus
66
66
  Zeus.ui.error("fsevents-wrapper crashed.")
67
67
  Process.kill("INT", 0)
68
68
  end
69
- File.open("fse-out.log", "a") { |f|f.write lines }
70
69
  files = lines.split("\n")
71
70
  files[0] = "#{@buffer}#{files[0]}" unless @buffer == ""
72
71
  unless lines[-1] == "\n"
@@ -2,7 +2,6 @@ module Zeus
2
2
  class Server
3
3
  # base class for Stage and Acceptor
4
4
  class ForkedProcess
5
- HasNoChildren = Class.new(Exception)
6
5
 
7
6
  attr_accessor :name
8
7
  attr_reader :pid
@@ -41,7 +40,7 @@ module Zeus
41
40
 
42
41
  trap("INT") { exit }
43
42
  trap("TERM") {
44
- notify_terminating
43
+ notify_terminated
45
44
  exit
46
45
  }
47
46
 
@@ -54,11 +53,6 @@ module Zeus
54
53
  ($LOADED_FEATURES + @server.extra_features) - old_features
55
54
  end
56
55
 
57
- def kill_pid_on_exit(pid)
58
- currpid = Process.pid
59
- at_exit { Process.kill(9, pid) if Process.pid == currpid rescue nil }
60
- end
61
-
62
56
  def runloop!
63
57
  raise NotImplementedError
64
58
  end
@@ -95,8 +89,6 @@ module Zeus
95
89
  after_notify
96
90
  runloop!
97
91
  }
98
- kill_pid_on_exit(@pid)
99
- @pid
100
92
  end
101
93
 
102
94
  end
@@ -1,31 +1,25 @@
1
1
  module Zeus
2
2
  class Server
3
3
  class LoadTracking
4
-
5
- def self.inject!(server)
6
- $zeus_file_monitor_server = server
7
- end
8
-
9
- def self.add_feature(server, file)
10
- if absolute_path?(file)
11
- server.add_extra_feature(file)
12
- elsif File.exist?("./#{file}")
13
- server.add_extra_feature(File.expand_path("./#{file}"))
14
- else
15
- path = find_in_load_path(file)
16
- server.add_extra_feature(path) if path
4
+ class << self
5
+ attr_accessor :server
6
+
7
+ def add_feature(file)
8
+ return unless server
9
+ path = if File.exist?(File.expand_path(file))
10
+ File.expand_path(file)
11
+ else
12
+ find_in_load_path(file)
13
+ end
14
+ server.add_extra_feature path if path
17
15
  end
18
- end
19
16
 
20
- def self.find_in_load_path(file)
21
- path = $LOAD_PATH.detect { |path| File.exist?("#{path}/#{file}") }
22
- "#{path}/#{file}" if path
23
- end
17
+ private
24
18
 
25
- def self.absolute_path?(file)
26
- file =~ /^\// && File.exist?(file)
19
+ def find_in_load_path(file)
20
+ $LOAD_PATH.map { |path| "#{path}/#{file}" }.detect{ |file| File.exist? file }
21
+ end
27
22
  end
28
-
29
23
  end
30
24
  end
31
25
  end
@@ -37,15 +31,10 @@ module Kernel
37
31
  end
38
32
 
39
33
  class << self
40
- alias_method :__original_load, :load
34
+ alias_method :__load_without_zeus, :load
41
35
  def load(file, *a)
42
- if defined?($zeus_file_monitor_server)
43
- Zeus::Server::LoadTracking.add_feature($zeus_file_monitor_server, file)
44
- end
45
- __original_load(file, *a)
36
+ Zeus::Server::LoadTracking.add_feature(file)
37
+ __load_without_zeus(file, *a)
46
38
  end
47
39
  end
48
-
49
40
  end
50
-
51
-
@@ -20,6 +20,10 @@ module Zeus
20
20
  @root.kill_nodes_with_feature(file)
21
21
  end
22
22
 
23
+ def kill_all_nodes
24
+ @root.kill!
25
+ end
26
+
23
27
  module ChildProcessApi
24
28
  def __CHILD__stage_starting_with_pid(name, pid)
25
29
  buffer_send("#{STARTING_MARKER}#{name}:#{pid}")
@@ -30,14 +30,13 @@ module Zeus
30
30
  begin
31
31
  pid = Process.wait
32
32
  rescue Errno::ECHILD
33
- raise HasNoChildren.new("Stage `#{@name}` - All terminal nodes must be acceptors")
33
+ sleep # if this is a terminal node, just let acceptors run...
34
34
  end
35
35
  stage = @pids[pid]
36
36
  @pids[stage.run] = stage
37
37
  end
38
38
  end
39
39
 
40
-
41
40
  private
42
41
 
43
42
  def register_acceptors_as_errors(e)
@@ -1,4 +1,8 @@
1
1
  require 'socket'
2
+ begin
3
+ require 'testrbl' # before bundler is setup so it does not need to be in the Gemfile
4
+ rescue LoadError
5
+ end
2
6
 
3
7
  ROOT_PATH = File.expand_path(Dir.pwd)
4
8
 
@@ -92,9 +96,21 @@ Zeus::Server.define! do
92
96
  action { require 'test_helper' }
93
97
 
94
98
  command :testrb do
95
- (r = Test::Unit::AutoRunner.new(true)).process_args(ARGV) or
96
- abort r.options.banner + " tests..."
97
- exit r.run
99
+ argv = ARGV
100
+
101
+ # try to find patter by line using testrbl
102
+ if defined?(Testrbl) && argv.size == 1 and argv.first =~ /^\S+:\d+$/
103
+ file, line = argv.first.split(':')
104
+ argv = [file, '-n', "/#{Testrbl.send(:pattern_from_file, File.readlines(file), line)}/"]
105
+ puts "using -n '#{argv[2]}'" # let users copy/paste or adjust the pattern
106
+ end
107
+
108
+ runner = Test::Unit::AutoRunner.new(true)
109
+ if runner.process_args(argv)
110
+ exit runner.run
111
+ else
112
+ abort runner.options.banner + " tests..."
113
+ end
98
114
  end
99
115
  end
100
116
  end
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.4.4"
2
+ VERSION = "0.4.5"
3
3
  end
@@ -5,21 +5,40 @@ describe "Integration" do
5
5
  kill_all_children
6
6
  end
7
7
 
8
- it "starts the zeus server in a non-rails project with a config and responds to commands" do
9
- write ".zeus.rb", <<-RUBY
10
- Zeus::Server.define! do
11
- stage :foo do
12
- command :bar do
13
- puts "YES"
8
+ context "in a non-rails project with a .zeus.rb" do
9
+ it "starts the zeus server and responds to commands" do
10
+ write ".zeus.rb", <<-RUBY
11
+ Zeus::Server.define! do
12
+ stage :foo do
13
+ command :bar do
14
+ puts "YES"
15
+ end
14
16
  end
15
17
  end
16
- end
17
- RUBY
18
+ RUBY
18
19
 
19
- start, run = start_and_run("bar")
20
- start.should include "spawner `foo`"
21
- start.should include "acceptor `bar`"
22
- run.should == ["YES\r\n"]
20
+ start, run = start_and_run("bar")
21
+ start.should include "spawner `foo`"
22
+ start.should include "acceptor `bar`"
23
+ run.should == ["YES\r\n"]
24
+ end
25
+
26
+ it "can run via command alias" do
27
+ write ".zeus.rb", <<-RUBY
28
+ Zeus::Server.define! do
29
+ stage :foo do
30
+ command :bar, :b do
31
+ puts "YES"
32
+ end
33
+ end
34
+ end
35
+ RUBY
36
+
37
+ start, run = start_and_run("b")
38
+ start.should include "spawner `foo`"
39
+ start.should include "acceptor `bar`"
40
+ run.should == ["YES\r\n"]
41
+ end
23
42
  end
24
43
 
25
44
  private
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zeus::Server::LoadTracking do
4
+ class Recorder
5
+ def recorded
6
+ @recorded ||= []
7
+ end
8
+
9
+ def add_extra_feature(*args)
10
+ recorded << [:add_extra_feature, *args]
11
+ end
12
+ end
13
+
14
+ let(:recorder){ Recorder.new }
15
+
16
+ around do |example|
17
+ $foo = nil
18
+ Zeus::Server::LoadTracking.server = recorder
19
+ example.call
20
+ Zeus::Server::LoadTracking.server = nil
21
+ end
22
+
23
+ let(:tmp_path) { File.expand_path(Dir.pwd) }
24
+
25
+ it "tracks loading of absolute paths" do
26
+ write "foo.rb", "$foo = 1"
27
+ load "#{Dir.pwd}/foo.rb"
28
+ $foo.should == 1
29
+ recorder.recorded.should == [[:add_extra_feature, tmp_path + "/foo.rb"]]
30
+ end
31
+
32
+ it "tracks loading of relative paths" do
33
+ write "foo.rb", "$foo = 1"
34
+ load "./foo.rb"
35
+ $foo.should == 1
36
+ recorder.recorded.should == [[:add_extra_feature, tmp_path + "/foo.rb"]]
37
+ end
38
+
39
+ it "tracks loading from library paths" do
40
+ write "lib/foo.rb", "$foo = 1"
41
+ restoring $LOAD_PATH do
42
+ $LOAD_PATH << File.expand_path("lib")
43
+ load "foo.rb"
44
+ end
45
+ $foo.should == 1
46
+ recorder.recorded.should == [[:add_extra_feature, tmp_path + "/lib/foo.rb"]]
47
+ end
48
+
49
+ it "does not add unfound files" do
50
+ write "lib/foo.rb", "$foo = 1"
51
+ begin
52
+ load "foo.rb"
53
+ rescue LoadError
54
+ end
55
+ $foo.should == nil
56
+ recorder.recorded.should == []
57
+ end
58
+
59
+ private
60
+
61
+ def restoring(thingy)
62
+ old = thingy.dup
63
+ yield
64
+ ensure
65
+ thingy.replace(old)
66
+ end
67
+ end
@@ -1,4 +1,4 @@
1
- require 'zeus'
1
+ require 'spec_helper'
2
2
 
3
3
  class Zeus::Server
4
4
  describe ProcessTreeMonitor do
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.4.4
4
+ version: 0.4.5
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-11 00:00:00.000000000 Z
12
+ date: 2012-08-16 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Boot any rails app in under a second
15
15
  email:
@@ -38,6 +38,7 @@ files:
38
38
  - lib/zeus/server/acceptor.rb
39
39
  - lib/zeus/server/acceptor_registration_monitor.rb
40
40
  - lib/zeus/server/client_handler.rb
41
+ - lib/zeus/server/command_runner.rb
41
42
  - lib/zeus/server/file_monitor.rb
42
43
  - lib/zeus/server/file_monitor/fsevent.rb
43
44
  - lib/zeus/server/forked_process.rb
@@ -50,6 +51,7 @@ files:
50
51
  - spec/cli_spec.rb
51
52
  - spec/integration_spec.rb
52
53
  - spec/server/file_monitor/fsevent_spec.rb
54
+ - spec/server/load_tracking_spec.rb
53
55
  - spec/server/process_tree_monitor_spec.rb
54
56
  - spec/spec_helper.rb
55
57
  - spec/ui_spec.rb
@@ -75,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
77
  version: '0'
76
78
  requirements: []
77
79
  rubyforge_project:
78
- rubygems_version: 1.8.11
80
+ rubygems_version: 1.8.23
79
81
  signing_key:
80
82
  specification_version: 3
81
83
  summary: Zeus is an intelligent preloader for ruby applications. It allows normal
@@ -84,7 +86,7 @@ test_files:
84
86
  - spec/cli_spec.rb
85
87
  - spec/integration_spec.rb
86
88
  - spec/server/file_monitor/fsevent_spec.rb
89
+ - spec/server/load_tracking_spec.rb
87
90
  - spec/server/process_tree_monitor_spec.rb
88
91
  - spec/spec_helper.rb
89
92
  - spec/ui_spec.rb
90
- has_rdoc: