zeus 0.3.1 → 0.4.0

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