zeus 0.3.1 → 0.4.0

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.
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+
data/Gemfile CHANGED
@@ -3,4 +3,6 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in zeus.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'rspec'
7
+ gem 'rake'
6
8
  gem 'pry'
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Zeus
1
+ # Zeus [![Build Status](https://secure.travis-ci.org/burke/zeus.png?branch=master)](http://travis-ci.org/burke/zeus) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/burke/zeus)
2
2
 
3
3
  ## What is Zeus?
4
4
 
5
- Zeus preloads your app so that your normal development tasks such as `console`, `server`, `generate`, and tests take **less than one second**.
5
+ Zeus preloads your app so that your normal development tasks such as `console`, `server`, `generate`, and specs/tests take **less than one second**.
6
6
 
7
7
  This screencast gives a quick overview of how to use zeus:
8
8
 
@@ -17,7 +17,7 @@ Pretty specific:
17
17
  * Rails 3.0+ (Support for other versions is not difficult and is planned.)
18
18
  * Backported GC from Ruby 2.0.
19
19
 
20
- You can install the GC-patched ruby from [this gist](https://gist.github.com/1688857) or from RVM.
20
+ You can install the GC-patched ruby from [this gist](https://gist.github.com/1688857) or from RVM. This is not actually 100% necessary, especially if you have a lot of memory. Feel free to give it a shot first without, but if you're suddenly out of RAM, switching to the GC-patched ruby will fix it.
21
21
 
22
22
  ## Installation
23
23
 
@@ -39,7 +39,8 @@ Run some commands:
39
39
 
40
40
  zeus console
41
41
  zeus server
42
- zeus testrb -Itest -I. test/unit/omg_test.rb
42
+ zeus testrb test/unit/widget_test.rb
43
+ zeus rspec spec/widget_spec.rb
43
44
  zeus generate model omg
44
45
  zeus rake -T
45
46
  zeus runner omg.rb
data/Rakefile CHANGED
@@ -1,2 +1,7 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+
4
+ require 'rspec'
5
+ task :default do
6
+ RSpec::Core::Runner.run(Dir.glob("spec/**/*_spec.rb"))
7
+ end
@@ -9,7 +9,7 @@ module Zeus
9
9
  attr_accessor :pid
10
10
 
11
11
  SIGNALS = {
12
- "\x03" => "TERM",
12
+ "\x03" => "INT",
13
13
  "\x1C" => "QUIT"
14
14
  }
15
15
  SIGNAL_REGEX = Regexp.union(SIGNALS.keys)
@@ -1,18 +1,83 @@
1
+ require 'set'
2
+
1
3
  module Zeus
2
4
  module DSL
3
5
 
4
6
  class Evaluator
5
7
  def stage(name, &b)
6
8
  stage = DSL::Stage.new(name)
9
+ stage.root = true
7
10
  stage.instance_eval(&b)
11
+ stage
12
+ end
13
+ end
14
+
15
+ class Node
16
+ attr_reader :name, :stages, :features
17
+ attr_accessor :pid, :root
18
+
19
+ def initialize(name)
20
+ @name = name
21
+ @stages = []
22
+ @features = Set.new # hash might be faster than ruby's inane impl of set.
23
+ end
24
+
25
+ def stage_has_feature(name, file)
26
+ node_for_name(name).features << file
27
+ end
28
+
29
+ def stage_has_pid(name, pid)
30
+ node_for_name(name).pid = pid
8
31
  end
32
+
33
+ def kill_nodes_with_feature(file)
34
+ if features.include?(file)
35
+ if root
36
+ Zeus.ui.error "One of zeus's dependencies changed. Not killing zeus. You may have to restart the server."
37
+ else
38
+ kill!
39
+ end
40
+ else
41
+ stages.each do |child|
42
+ child.kill_nodes_with_feature(file)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # We send STOP before actually killing the processes here.
50
+ # This is to prevent parents from respawning before all the children
51
+ # are killed. This prevents a race condition.
52
+ def kill!
53
+ Process.kill("STOP", pid) if pid
54
+ # Protected methods don't work with each(&:m) notation.
55
+ stages.each { |stage| stage.kill! }
56
+ old_pid = pid
57
+ self.pid = nil
58
+ Process.kill("KILL", old_pid) if old_pid
59
+ end ; protected :kill!
60
+
61
+ def node_for_name(name)
62
+ @nodes_by_name ||= __nodes_by_name
63
+ @nodes_by_name[name]
64
+ end
65
+
66
+ def __nodes_by_name
67
+ nodes = {name => self}
68
+ stages.each do |child|
69
+ nodes.merge!(child.__nodes_by_name)
70
+ end
71
+ nodes
72
+ end ; protected :__nodes_by_name
73
+
9
74
  end
10
75
 
11
- class Acceptor
76
+ class Acceptor < Node
12
77
 
13
78
  attr_reader :name, :aliases, :description, :action
14
79
  def initialize(name, aliases, description, &b)
15
- @name = name
80
+ super(name)
16
81
  @description = description
17
82
  @aliases = aliases
18
83
  @action = b
@@ -29,7 +94,7 @@ module Zeus
29
94
  self
30
95
  end
31
96
 
32
- def to_domain_object(server)
97
+ def to_process_object(server)
33
98
  Zeus::Server::Acceptor.new(server).tap do |stage|
34
99
  stage.name = @name
35
100
  stage.aliases = @aliases
@@ -40,12 +105,12 @@ module Zeus
40
105
 
41
106
  end
42
107
 
43
- class Stage
108
+ class Stage < Node
44
109
 
45
- attr_reader :pid, :stages, :actions
110
+ attr_reader :actions
46
111
  def initialize(name)
47
- @name = name
48
- @stages, @actions = [], []
112
+ super(name)
113
+ @actions = []
49
114
  end
50
115
 
51
116
  def action(&b)
@@ -75,10 +140,10 @@ module Zeus
75
140
  stages.map(&:acceptors).flatten
76
141
  end
77
142
 
78
- def to_domain_object(server)
143
+ def to_process_object(server)
79
144
  Zeus::Server::Stage.new(server).tap do |stage|
80
145
  stage.name = @name
81
- stage.stages = @stages.map { |stage| stage.to_domain_object(server) }
146
+ stage.stages = @stages.map { |stage| stage.to_process_object(server) }
82
147
  stage.actions = @actions
83
148
  end
84
149
  end
@@ -9,7 +9,7 @@ module Zeus
9
9
  autoload :Stage, 'zeus/server/stage'
10
10
  autoload :Acceptor, 'zeus/server/acceptor'
11
11
  autoload :FileMonitor, 'zeus/server/file_monitor'
12
- autoload :ProcessTree, 'zeus/server/process_tree'
12
+ autoload :LoadTracking, 'zeus/server/load_tracking'
13
13
  autoload :ForkedProcess, 'zeus/server/forked_process'
14
14
  autoload :ClientHandler, 'zeus/server/client_handler'
15
15
  autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
@@ -26,11 +26,10 @@ module Zeus
26
26
  def initialize
27
27
  @file_monitor = FileMonitor::FSEvent.new(&method(:dependency_did_change))
28
28
  @acceptor_registration_monitor = AcceptorRegistrationMonitor.new
29
- @process_tree_monitor = ProcessTreeMonitor.new(@file_monitor)
30
- acceptor_commands = self.class.acceptors.map(&:commands).flatten
29
+ @process_tree_monitor = ProcessTreeMonitor.new(@file_monitor, @@definition)
31
30
  @client_handler = ClientHandler.new(acceptor_commands, self)
32
31
 
33
- @plan = @@definition.to_domain_object(self)
32
+ @plan = @@definition.to_process_object(self)
34
33
  end
35
34
 
36
35
  def dependency_did_change(file)
@@ -43,22 +42,32 @@ module Zeus
43
42
 
44
43
  def run
45
44
  $0 = "zeus master"
46
- trap("INT") { exit 0 }
45
+ trap("TERM") { exit 0 }
46
+ trap("INT") {
47
+ puts "\n\x1b[31mExiting\x1b[0m"
48
+ exit 0
49
+ }
50
+
51
+ LoadTracking.inject!(self)
47
52
 
48
53
  @plan.run(true) # boot the actual app
49
54
  monitors.each(&:close_child_socket)
50
55
 
51
- loop do
52
- ready, = IO.select(monitors.map(&:datasource), [], [], 1)
53
- next unless ready
54
- monitors.each do |m|
55
- m.on_datasource_event if ready.include?(m.datasource)
56
- end
57
- end
56
+ runloop!
58
57
  ensure
59
58
  File.unlink(Zeus::SOCKET_NAME)
60
59
  end
61
60
 
61
+ # this is used in conjunction with Zeus::LoadTracking to track files loaded
62
+ # using `load` rather than `require`.
63
+ def add_extra_feature(full_expanded_path)
64
+ @extra_loaded_features ||= []
65
+ @extra_loaded_features << full_expanded_path
66
+ end
67
+
68
+ def extra_features
69
+ @extra_loaded_features || []
70
+ end
62
71
 
63
72
  # Child process API
64
73
  def __CHILD__close_parent_sockets
@@ -70,8 +79,24 @@ module Zeus
70
79
  :__CHILD__find_acceptor_for_command
71
80
 
72
81
  def_delegators :@process_tree_monitor,
73
- :__CHILD__pid_has_ppid,
74
- :__CHILD__pid_has_feature
82
+ :__CHILD__stage_starting_with_pid,
83
+ :__CHILD__stage_has_feature
84
+
85
+ private
86
+
87
+ def acceptor_commands
88
+ self.class.acceptors.map(&:commands).flatten
89
+ end
90
+
91
+ def runloop!
92
+ loop do
93
+ ready, = IO.select(monitors.map(&:datasource), [], [], 1)
94
+ next unless ready
95
+ monitors.each do |m|
96
+ m.on_datasource_event if ready.include?(m.datasource)
97
+ end
98
+ end
99
+ end
75
100
 
76
101
  end
77
102
  end
@@ -28,6 +28,7 @@ module Zeus
28
28
 
29
29
  def __RUNNER__run(terminal, arguments)
30
30
  $0 = "zeus runner: #{@name}"
31
+ Process.setsid
31
32
  postfork_action!
32
33
  @s_acceptor << $$ << "\n"
33
34
  $stdin.reopen(terminal)
@@ -37,11 +38,14 @@ module Zeus
37
38
 
38
39
  @action.call
39
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.
40
43
  dnw, dnr = File.open("/dev/null", "w+"), File.open("/dev/null", "r+")
41
- $stdin.reopen(dnw)
42
- $stdout.reopen(dnr)
43
44
  $stderr.reopen(dnr)
45
+ $stdout.reopen(dnr)
44
46
  terminal.close
47
+ $stdin.reopen(dnw)
48
+ Process.kill(9, $$)
45
49
  exit 0
46
50
  end
47
51
 
@@ -1,5 +1,6 @@
1
1
  module Zeus
2
2
  class Server
3
+ # base class for Stage and Acceptor
3
4
  class ForkedProcess
4
5
  HasNoChildren = Class.new(Exception)
5
6
 
@@ -10,7 +11,7 @@ module Zeus
10
11
  end
11
12
 
12
13
  def notify_feature(feature)
13
- @server.__CHILD__pid_has_feature(Process.pid, feature)
14
+ @server.__CHILD__stage_has_feature(@name, feature)
14
15
  end
15
16
 
16
17
  def descendent_acceptors
@@ -21,27 +22,41 @@ module Zeus
21
22
  raise "NotImplementedError"
22
23
  end
23
24
 
25
+ def notify_started
26
+ @server.__CHILD__stage_starting_with_pid(@name, Process.pid)
27
+ Zeus.ui.info("starting #{process_type} `#{@name}`")
28
+ end
29
+
30
+ def notify_terminated
31
+ # @server.__CHILD__stage_terminating(@name)
32
+ Zeus.ui.info("killing #{process_type} `#{@name}`")
33
+ end
34
+
24
35
  def setup_forked_process(close_parent_sockets)
25
36
  @server.__CHILD__close_parent_sockets if close_parent_sockets
26
- @server.__CHILD__pid_has_ppid(Process.pid, Process.ppid)
37
+
38
+ notify_started
27
39
 
28
40
  $0 = "zeus #{process_type}: #{@name}"
29
41
 
30
- Zeus.ui.info("starting #{process_type} `#{@name}`")
31
- trap("INT") {
32
- Zeus.ui.info("killing #{process_type} `#{@name}`")
33
- exit 0
42
+ trap("INT") { exit }
43
+ trap("TERM") {
44
+ notify_terminating
45
+ exit
34
46
  }
35
47
 
36
- new_features = $LOADED_FEATURES - previously_loaded_features
37
- $previously_loaded_features = $LOADED_FEATURES.dup
48
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.clear_all_connections!
49
+
50
+ new_features = newly_loaded_features()
51
+ $previously_loaded_features = new_features
38
52
  Thread.new {
39
53
  new_features.each { |f| notify_feature(f) }
40
54
  }
41
55
  end
42
56
 
43
- def previously_loaded_features
44
- defined?($previously_loaded_features) ? $previously_loaded_features : []
57
+ def newly_loaded_features
58
+ old_features = defined?($previously_loaded_features) ? $previously_loaded_features : []
59
+ ($LOADED_FEATURES + @server.extra_features) - old_features
45
60
  end
46
61
 
47
62
  def kill_pid_on_exit(pid)
@@ -0,0 +1,43 @@
1
+ module Zeus
2
+ class Server
3
+ class LoadTracking
4
+
5
+ def self.inject!(server)
6
+ mod = module_for_server(server)
7
+ Object.class_eval { include mod }
8
+ end
9
+
10
+ def self.module_for_server(server)
11
+ Module.new.tap do |load_tracking|
12
+ load_tracking.send(:define_method, :load) { |file, *a|
13
+ if ret = super(file, *a)
14
+ LoadTracking.add_feature(server, file)
15
+ end
16
+ ret
17
+ }
18
+ end
19
+ end
20
+
21
+ def self.add_feature(server, file)
22
+ if absolute_path?(file)
23
+ server.add_extra_feature(file)
24
+ elsif File.exist?("./#{file}")
25
+ server.add_extra_feature(File.expand_path("./#{file}"))
26
+ else
27
+ path = find_in_load_path(file)
28
+ server.add_extra_feature(path) if path
29
+ end
30
+ end
31
+
32
+ def self.find_in_load_path(file)
33
+ path = $LOAD_PATH.detect { |path| File.exist?("#{path}/#{file}") }
34
+ "#{path}/#{file}" if path
35
+ end
36
+
37
+ def self.absolute_path?(file)
38
+ file =~ /^\// && File.exist?(file)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -1,41 +1,44 @@
1
1
  module Zeus
2
2
  class Server
3
3
  class ProcessTreeMonitor
4
- PID_TYPE = "P"
5
- FEATURE_TYPE = "F"
4
+ STARTING_MARKER = "P"
5
+ FEATURE_MARKER = "F"
6
6
 
7
7
  def datasource ; @sock ; end
8
8
  def on_datasource_event ; handle_messages ; end
9
9
  def close_child_socket ; @__CHILD__sock.close ; end
10
10
  def close_parent_socket ; @sock.close ; end
11
11
 
12
- def initialize(file_monitor, tree=ProcessTree.new)
13
- @tree = tree
12
+ def initialize(file_monitor, tree)
13
+ @root = tree
14
14
  @file_monitor = file_monitor
15
15
 
16
16
  @sock, @__CHILD__sock = open_socketpair
17
17
  end
18
18
 
19
19
  def kill_nodes_with_feature(file)
20
- @tree.kill_nodes_with_feature(file)
20
+ @root.kill_nodes_with_feature(file)
21
21
  end
22
22
 
23
23
  module ChildProcessApi
24
- def __CHILD__pid_has_ppid(pid, ppid)
25
- @__CHILD__sock.send("#{PID_TYPE}:#{pid}:#{ppid}", 0)
26
- rescue Errno::ENOBUFS
27
- sleep 0.2
28
- retry
24
+ def __CHILD__stage_starting_with_pid(name, pid)
25
+ buffer_send("#{STARTING_MARKER}#{name}:#{pid}")
26
+ end
27
+
28
+ def __CHILD__stage_has_feature(name, feature)
29
+ buffer_send("#{FEATURE_MARKER}#{name}:#{feature}")
29
30
  end
30
31
 
31
- def __CHILD__pid_has_feature(pid, feature)
32
- @__CHILD__sock.send("#{FEATURE_TYPE}:#{pid}:#{feature}", 0)
32
+ private
33
+
34
+ def buffer_send(msg)
35
+ @__CHILD__sock.send(msg, 0)
33
36
  rescue Errno::ENOBUFS
34
37
  sleep 0.2
35
38
  retry
36
39
  end
37
- end ; include ChildProcessApi
38
40
 
41
+ end ; include ChildProcessApi
39
42
 
40
43
  private
41
44
 
@@ -47,10 +50,10 @@ module Zeus
47
50
  def handle_message
48
51
  data = @sock.recv_nonblock(4096)
49
52
  case data[0]
50
- when FEATURE_TYPE
53
+ when STARTING_MARKER
54
+ handle_starting_message(data[1..-1])
55
+ when FEATURE_MARKER
51
56
  handle_feature_message(data[1..-1])
52
- when PID_TYPE
53
- handle_pid_message(data[1..-1])
54
57
  end
55
58
  end
56
59
 
@@ -58,20 +61,19 @@ module Zeus
58
61
  Socket.pair(:UNIX, :DGRAM)
59
62
  end
60
63
 
61
- def handle_pid_message(data)
62
- data =~ /(\d+):(\d+)/
63
- pid, ppid = $1.to_i, $2.to_i
64
- @tree.process_has_parent(pid, ppid)
64
+ def handle_starting_message(data)
65
+ data =~ /(.+):(\d+)/
66
+ name, pid = $1.to_sym, $2.to_i
67
+ @root.stage_has_pid(name, pid)
65
68
  end
66
69
 
67
70
  def handle_feature_message(data)
68
- data =~ /(\d+):(.*)/
69
- pid, file = $1.to_i, $2
70
- @tree.process_has_feature(pid, file)
71
+ data =~ /(.+?):(.*)/
72
+ name, file = $1.to_sym, $2
73
+ @root.stage_has_feature(name, file)
71
74
  @file_monitor.watch(file)
72
75
  end
73
76
 
74
-
75
77
  end
76
78
  end
77
79
  end
@@ -1,10 +1,11 @@
1
1
  require 'socket'
2
2
 
3
+ ROOT_PATH = File.expand_path(Dir.pwd)
4
+
3
5
  Zeus::Server.define! do
4
6
  stage :boot do
5
7
 
6
8
  action do
7
- ROOT_PATH = File.expand_path(Dir.pwd)
8
9
  ENV_PATH = File.expand_path('config/environment', ROOT_PATH)
9
10
  BOOT_PATH = File.expand_path('config/boot', ROOT_PATH)
10
11
  APP_PATH = File.expand_path('config/application', ROOT_PATH)
@@ -25,6 +26,11 @@ Zeus::Server.define! do
25
26
  end
26
27
 
27
28
  command :generate, :g do
29
+ begin
30
+ require 'rails/generators'
31
+ Rails.application.load_generators
32
+ rescue LoadError # Rails 3.0 doesn't require this block to be run, but 3.2+ does
33
+ end
28
34
  require 'rails/commands/generate'
29
35
  end
30
36
 
@@ -67,19 +73,39 @@ Zeus::Server.define! do
67
73
  $rails_rake_task = 'yup' # lie to skip eager loading
68
74
  Rails.application.require_environment!
69
75
  $rails_rake_task = nil
70
-
71
- test = File.join(ROOT_PATH, 'test')
72
- $LOAD_PATH.unshift(test) unless $LOAD_PATH.include?(test)
73
76
  $LOAD_PATH.unshift(ROOT_PATH) unless $LOAD_PATH.include?(ROOT_PATH)
77
+
78
+ if Dir.exist?(ROOT_PATH + "/test")
79
+ test = File.join(ROOT_PATH, 'test')
80
+ $LOAD_PATH.unshift(test) unless $LOAD_PATH.include?(test)
81
+ end
82
+
83
+ if Dir.exist?(ROOT_PATH + "/spec")
84
+ spec = File.join(ROOT_PATH, 'spec')
85
+ $LOAD_PATH.unshift(spec) unless $LOAD_PATH.include?(spec)
86
+ end
87
+
88
+ end
89
+
90
+ if Dir.exist?(ROOT_PATH + "/test")
91
+ stage :test_helper do
92
+ action { require 'test_helper' }
93
+
94
+ command :testrb do
95
+ (r = Test::Unit::AutoRunner.new(true)).process_args(ARGV) or
96
+ abort r.options.banner + " tests..."
97
+ exit r.run
98
+ end
99
+ end
74
100
  end
75
101
 
76
- stage :test_helper do
77
- action { require 'test_helper' }
102
+ if Dir.exist?(ROOT_PATH + "/spec")
103
+ stage :spec_helper do
104
+ action { require 'spec_helper' }
78
105
 
79
- command :testrb do
80
- (r = Test::Unit::AutoRunner.new(true)).process_args(ARGV) or
81
- abort r.options.banner + " tests..."
82
- exit r.run
106
+ command :rspec do
107
+ exit RSpec::Core::Runner.run(ARGV)
108
+ end
83
109
  end
84
110
  end
85
111
 
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -28,17 +28,17 @@ class Zeus::Server
28
28
 
29
29
  it "passes process inheritance information to the tree" do
30
30
  IO.select([monitor.datasource], [], [], 0).should be_nil
31
- monitor.__CHILD__pid_has_ppid(1, 2)
31
+ monitor.__CHILD__stage_starting_with_pid(:name, 1)
32
32
  IO.select([monitor.datasource], [], [], 0.5).should_not be_nil
33
- tree.should_receive(:process_has_parent).with(1, 2)
33
+ tree.should_receive(:stage_has_pid).with(:name, 1)
34
34
  monitor.on_datasource_event
35
35
  end
36
36
 
37
37
  it "passes process feature information to the tree" do
38
38
  IO.select([monitor.datasource], [], [], 0).should be_nil
39
- monitor.__CHILD__pid_has_feature(1, "rails")
39
+ monitor.__CHILD__stage_has_feature(:name, "rails")
40
40
  IO.select([monitor.datasource], [], [], 0.5).should_not be_nil
41
- tree.should_receive(:process_has_feature).with(1, "rails")
41
+ tree.should_receive(:stage_has_feature).with(:name, "rails")
42
42
  file_monitor.should_receive(:watch).with("rails")
43
43
  monitor.on_datasource_event
44
44
  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.3.1
4
+ version: 0.4.0
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-08 00:00:00.000000000 Z
12
+ date: 2012-08-09 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Boot any rails app in under a second
15
15
  email:
@@ -20,6 +20,7 @@ extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
22
  - .gitignore
23
+ - .travis.yml
23
24
  - .zeus.rb
24
25
  - Gemfile
25
26
  - LICENSE
@@ -40,7 +41,7 @@ files:
40
41
  - lib/zeus/server/file_monitor.rb
41
42
  - lib/zeus/server/file_monitor/fsevent.rb
42
43
  - lib/zeus/server/forked_process.rb
43
- - lib/zeus/server/process_tree.rb
44
+ - lib/zeus/server/load_tracking.rb
44
45
  - lib/zeus/server/process_tree_monitor.rb
45
46
  - lib/zeus/server/stage.rb
46
47
  - lib/zeus/templates/rails.rb
@@ -49,7 +50,6 @@ files:
49
50
  - spec/cli_spec.rb
50
51
  - spec/server/file_monitor/fsevent_spec.rb
51
52
  - spec/server/process_tree_monitor_spec.rb
52
- - spec/server/process_tree_spec.rb
53
53
  - spec/ui_spec.rb
54
54
  - zeus.gemspec
55
55
  homepage: http://github.com/burke/zeus
@@ -81,6 +81,5 @@ test_files:
81
81
  - spec/cli_spec.rb
82
82
  - spec/server/file_monitor/fsevent_spec.rb
83
83
  - spec/server/process_tree_monitor_spec.rb
84
- - spec/server/process_tree_spec.rb
85
84
  - spec/ui_spec.rb
86
85
  has_rdoc:
@@ -1,76 +0,0 @@
1
- module Zeus
2
- class Server
3
- class ProcessTree
4
-
5
- def initialize
6
- @root = Node.new(Process.pid)
7
- @nodes_by_pid = {Process.pid => @root}
8
- end
9
-
10
- def process_has_parent(pid, ppid)
11
- curr = node_for_pid(pid)
12
- base = node_for_pid(ppid)
13
- base.add_child(curr)
14
- end
15
-
16
- def process_has_feature(pid, feature)
17
- node = node_for_pid(pid)
18
- node.add_feature(feature)
19
- end
20
-
21
- def kill_nodes_with_feature(file, base = @root)
22
- if base.has_feature?(file)
23
- kill_node(base)
24
- else
25
- base.children.dup.each do |node|
26
- if kill_nodes_with_feature(file, node)
27
- base.children.delete(node)
28
- end
29
- end
30
- return false
31
- end
32
- end
33
-
34
- private
35
-
36
- def node_for_pid(pid)
37
- @nodes_by_pid[pid.to_i] ||= Node.new(pid.to_i)
38
- end
39
-
40
- def kill_node(node)
41
- if node == @root.children[0] || node == @root
42
- Zeus.ui.error "One of zeus's dependencies changed. Not killing zeus. You may have to restart the server."
43
- return false
44
- end
45
- @nodes_by_pid.delete(node.pid)
46
- node.kill
47
- end
48
-
49
- class Node
50
- attr_accessor :pid, :children, :features
51
- def initialize(pid)
52
- @pid, @children, @features = pid, [], {}
53
- end
54
-
55
- def kill
56
- # recall that this process explicitly traps INT -> exit 0
57
- Process.kill("INT", pid)
58
- end
59
-
60
- def add_child(node)
61
- self.children << node
62
- end
63
-
64
- def add_feature(feature)
65
- self.features[feature] = true
66
- end
67
-
68
- def has_feature?(feature)
69
- self.features[feature] == true
70
- end
71
-
72
- end
73
-
74
- end
75
- end
76
- end
@@ -1,65 +0,0 @@
1
- require 'zeus'
2
-
3
- class Zeus::Server
4
-
5
- describe ProcessTree do
6
-
7
- ROOT_PID = Process.pid
8
- CHILD_1 = ROOT_PID + 1
9
- CHILD_2 = ROOT_PID + 2
10
- GRANDCHILD_1 = ROOT_PID + 3
11
- GRANDCHILD_2 = ROOT_PID + 4
12
-
13
- let(:process_tree) { ProcessTree.new }
14
-
15
- before do
16
- build_tree
17
- add_features
18
- end
19
-
20
- it "doesn't kill the root node" do
21
- Zeus.ui.should_receive(:error).with(/not killing zeus/i)
22
- Process.should_not_receive(:kill)
23
- process_tree.kill_nodes_with_feature("zeus")
24
- end
25
-
26
- it "kills a node that has a feature" do
27
- expect_kill(CHILD_2)
28
- process_tree.kill_nodes_with_feature("rails")
29
- end
30
-
31
- it "kills multiple nodes at the same level with a feature" do
32
- expect_kill(GRANDCHILD_1)
33
- expect_kill(GRANDCHILD_2)
34
- process_tree.kill_nodes_with_feature("model")
35
- end
36
-
37
- private
38
-
39
- def expect_kill(pid)
40
- Process.should_receive(:kill).with("INT", pid)
41
- end
42
-
43
- def build_tree
44
- process_tree.process_has_parent(CHILD_1, ROOT_PID)
45
- process_tree.process_has_parent(CHILD_2, CHILD_1)
46
- process_tree.process_has_parent(GRANDCHILD_1, CHILD_2)
47
- process_tree.process_has_parent(GRANDCHILD_2, CHILD_2)
48
- end
49
-
50
- def add_features
51
- [CHILD_2, GRANDCHILD_1, GRANDCHILD_2].each do |pid|
52
- process_tree.process_has_feature(pid, "rails")
53
- end
54
-
55
- process_tree.process_has_feature(GRANDCHILD_1, "model")
56
- process_tree.process_has_feature(GRANDCHILD_2, "model")
57
-
58
- [ROOT_PID, CHILD_1, CHILD_2, GRANDCHILD_1, GRANDCHILD_2].each do |pid|
59
- process_tree.process_has_feature(pid, "zeus")
60
- end
61
- end
62
-
63
- end
64
-
65
- end