zeus 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/README.md +5 -4
- data/Rakefile +5 -0
- data/lib/zeus/client.rb +1 -1
- data/lib/zeus/dsl.rb +74 -9
- data/lib/zeus/server.rb +39 -14
- data/lib/zeus/server/acceptor.rb +6 -2
- data/lib/zeus/server/forked_process.rb +25 -10
- data/lib/zeus/server/load_tracking.rb +43 -0
- data/lib/zeus/server/process_tree_monitor.rb +26 -24
- data/lib/zeus/templates/rails.rb +36 -10
- data/lib/zeus/version.rb +1 -1
- data/spec/server/process_tree_monitor_spec.rb +4 -4
- metadata +4 -5
- data/lib/zeus/server/process_tree.rb +0 -76
- data/spec/server/process_tree_spec.rb +0 -65
data/Gemfile
CHANGED
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
|
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
data/lib/zeus/client.rb
CHANGED
data/lib/zeus/dsl.rb
CHANGED
@@ -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
|
-
|
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
|
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 :
|
110
|
+
attr_reader :actions
|
46
111
|
def initialize(name)
|
47
|
-
|
48
|
-
@
|
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
|
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.
|
146
|
+
stage.stages = @stages.map { |stage| stage.to_process_object(server) }
|
82
147
|
stage.actions = @actions
|
83
148
|
end
|
84
149
|
end
|
data/lib/zeus/server.rb
CHANGED
@@ -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 :
|
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.
|
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("
|
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
|
-
|
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
|
-
:
|
74
|
-
:
|
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
|
data/lib/zeus/server/acceptor.rb
CHANGED
@@ -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.
|
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
|
-
|
37
|
+
|
38
|
+
notify_started
|
27
39
|
|
28
40
|
$0 = "zeus #{process_type}: #{@name}"
|
29
41
|
|
30
|
-
|
31
|
-
trap("
|
32
|
-
|
33
|
-
exit
|
42
|
+
trap("INT") { exit }
|
43
|
+
trap("TERM") {
|
44
|
+
notify_terminating
|
45
|
+
exit
|
34
46
|
}
|
35
47
|
|
36
|
-
|
37
|
-
|
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
|
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
|
-
|
5
|
-
|
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
|
13
|
-
@
|
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
|
-
@
|
20
|
+
@root.kill_nodes_with_feature(file)
|
21
21
|
end
|
22
22
|
|
23
23
|
module ChildProcessApi
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
32
|
-
|
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
|
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
|
62
|
-
data =~ /(
|
63
|
-
|
64
|
-
@
|
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 =~ /(
|
69
|
-
|
70
|
-
@
|
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
|
data/lib/zeus/templates/rails.rb
CHANGED
@@ -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
|
-
|
77
|
-
|
102
|
+
if Dir.exist?(ROOT_PATH + "/spec")
|
103
|
+
stage :spec_helper do
|
104
|
+
action { require 'spec_helper' }
|
78
105
|
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
|
data/lib/zeus/version.rb
CHANGED
@@ -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.
|
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(:
|
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.
|
39
|
+
monitor.__CHILD__stage_has_feature(:name, "rails")
|
40
40
|
IO.select([monitor.datasource], [], [], 0.5).should_not be_nil
|
41
|
-
tree.should_receive(:
|
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.
|
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-
|
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/
|
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
|