zeus 0.4.4 → 0.4.5

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/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: