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.
- 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 [](http://travis-ci.org/burke/zeus) [](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
|