zeus 0.2.0.beta1 → 0.2.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/README.md +24 -13
- data/bin/zeus +1 -40
- data/ext/.DS_Store +0 -0
- data/ext/fsevents-wrapper/fsevents-wrapper +0 -0
- data/ext/fsevents-wrapper/main.m +133 -0
- data/lib/{tiny_thor.rb → thrud.rb} +2 -2
- data/lib/zeus.rb +9 -4
- data/lib/zeus/cli.rb +2 -5
- data/lib/zeus/client.rb +65 -36
- data/lib/zeus/dsl.rb +0 -3
- data/lib/zeus/server.rb +41 -55
- data/lib/zeus/server/acceptor.rb +2 -2
- data/lib/zeus/server/client_handler.rb +6 -15
- data/lib/zeus/server/file_monitor.rb +2 -50
- data/lib/zeus/server/file_monitor/fsevent.rb +63 -0
- data/lib/zeus/server/process_tree_monitor.rb +1 -1
- data/lib/zeus/server/stage.rb +5 -3
- data/lib/zeus/templates/rails.rb +15 -14
- data/lib/zeus/ui.rb +5 -0
- data/lib/zeus/version.rb +1 -1
- data/zeus.gemspec +1 -3
- metadata +8 -16
- data/lib/zeus/init.rb +0 -17
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -17,7 +17,6 @@ Soon? You can use Zeus now, but don't expect it to be perfect. I'm working hard
|
|
17
17
|
## Ugly bits
|
18
18
|
|
19
19
|
* Not battle-tested yet
|
20
|
-
* Creates a bunch of sockets
|
21
20
|
* Uses an obscene number of file descriptors
|
22
21
|
|
23
22
|
## Installation
|
@@ -26,7 +25,17 @@ Install the gem.
|
|
26
25
|
|
27
26
|
gem install zeus
|
28
27
|
|
29
|
-
|
28
|
+
Run the project initializer.
|
29
|
+
|
30
|
+
zeus init
|
31
|
+
|
32
|
+
## Upgrading from initial release
|
33
|
+
|
34
|
+
Since zeus is super-brand-new, the config file format changed already.
|
35
|
+
|
36
|
+
gem install zeus
|
37
|
+
rm .zeus.rb
|
38
|
+
zeus init
|
30
39
|
|
31
40
|
## Usage
|
32
41
|
|
@@ -45,23 +54,25 @@ Run some commands:
|
|
45
54
|
|
46
55
|
## TODO (roughly prioritized)
|
47
56
|
|
48
|
-
*
|
49
|
-
*
|
50
|
-
*
|
51
|
-
* Support other frameworks?
|
52
|
-
* Use fsevent instead of kqueue to reduce the obscene number of file descriptors.
|
53
|
-
* Support inotify on linux
|
57
|
+
* Make sure client connection requests are handled immediately
|
58
|
+
* Acceptors booting should not be dependent on passing all loaded features to the file monitor
|
59
|
+
* Route all logging output through Zeus.ui
|
54
60
|
* Handle connections for not-yet-started sockets
|
61
|
+
* Refactor, refactor, refactor...
|
62
|
+
* Support other frameworks?
|
63
|
+
* Periodically re-attempt to add lost files to KQueue
|
64
|
+
* Figure out how to run full test suites without multiple env loads
|
55
65
|
* Don't replace a socket with changed deps until the new one is ready
|
66
|
+
|
67
|
+
## Ideas (not quite TODOs)
|
68
|
+
|
56
69
|
* (maybe) Start the preloader as a daemon transparently when any command is run, then wait for it to finish
|
70
|
+
* Use fsevent instead of kqueue to reduce the obscene number of file descriptors.
|
71
|
+
* Support inotify on linux
|
57
72
|
|
58
73
|
## Contributing
|
59
74
|
|
60
|
-
|
61
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
63
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
64
|
-
5. Create new Pull Request
|
75
|
+
Fork, Branch, Pull Request.
|
65
76
|
|
66
77
|
## Thanks...
|
67
78
|
|
data/bin/zeus
CHANGED
@@ -1,43 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'zeus'
|
4
|
-
|
5
|
-
Zeus::CLI.start
|
6
|
-
|
7
|
-
begin
|
8
|
-
rescue Zeus::ZeusError => e
|
9
|
-
Zeus.ui.error e.message
|
10
|
-
Zeus.ui.debug e.backtrace.join("\n")
|
11
|
-
exit e.status_code
|
12
|
-
rescue Interrupt => e
|
13
|
-
Zeus.ui.error e.message
|
14
|
-
Zeus.ui.debug e.backtrace.join("\n")
|
15
|
-
exit 1
|
16
|
-
rescue SystemExit => e
|
17
|
-
exit e.status
|
18
|
-
rescue Exception => e
|
19
|
-
Zeus.ui.error("A fatal error has occurred. Please see the github issues at:\n" \
|
20
|
-
"http://github.com/burke/zeus/issues")
|
21
|
-
end
|
22
|
-
exit 0
|
23
|
-
|
24
|
-
|
25
|
-
case ARGV[0]
|
26
|
-
when "start"
|
27
|
-
require 'zeus/server'
|
28
|
-
begin
|
29
|
-
require './.zeus.rb'
|
30
|
-
rescue LoadError
|
31
|
-
puts "\x1b[31mYour project is missing a config file (.zeus.rb), or you are not in the project root."
|
32
|
-
puts "You can run `zeus init` to generate a config file.\x1b[0m"
|
33
|
-
exit 1
|
34
|
-
end
|
35
|
-
Zeus::Server.run
|
36
|
-
when "init"
|
37
|
-
require 'zeus/init'
|
38
|
-
ARGV.shift # get "init" off the list
|
39
|
-
Zeus::Init.run(ARGV)
|
40
|
-
else
|
41
|
-
require 'zeus/client'
|
42
|
-
Zeus::Client.run
|
43
|
-
end
|
4
|
+
Zeus::CLI.start
|
data/ext/.DS_Store
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,133 @@
|
|
1
|
+
//
|
2
|
+
// main.m
|
3
|
+
// fsevents-wrapper
|
4
|
+
//
|
5
|
+
// Created by Burke Libbey on 2012-07-30.
|
6
|
+
// Copyright (c) 2012 Burke Libbey. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#import <Foundation/Foundation.h>
|
10
|
+
#include <CoreServices/CoreServices.h>
|
11
|
+
#include <sys/stat.h>
|
12
|
+
|
13
|
+
|
14
|
+
static CFMutableArrayRef _watchedFiles;
|
15
|
+
static FSEventStreamRef _activeStream;
|
16
|
+
static NSMutableDictionary *_fileIsWatched;
|
17
|
+
|
18
|
+
static int flagsWorthReloadingFor = \
|
19
|
+
kFSEventStreamEventFlagItemRemoved | \
|
20
|
+
kFSEventStreamEventFlagItemRenamed | \
|
21
|
+
kFSEventStreamEventFlagItemModified;
|
22
|
+
|
23
|
+
void myCallbackFunction(
|
24
|
+
ConstFSEventStreamRef streamRef,
|
25
|
+
void *clientCallBackInfo,
|
26
|
+
size_t numEvents,
|
27
|
+
void *eventPaths,
|
28
|
+
const FSEventStreamEventFlags eventFlags[],
|
29
|
+
const FSEventStreamEventId eventIds[])
|
30
|
+
{
|
31
|
+
int i, flags;
|
32
|
+
char **paths = eventPaths;
|
33
|
+
|
34
|
+
for (i = 0; i < numEvents; i++) {
|
35
|
+
flags = eventFlags[i];
|
36
|
+
|
37
|
+
if (flags & (kFSEventStreamEventFlagItemIsFile | flagsWorthReloadingFor)) {
|
38
|
+
printf("%s\n", paths[i]);
|
39
|
+
fflush(stdout);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
void configureStream()
|
45
|
+
{
|
46
|
+
if (CFArrayGetCount(_watchedFiles) == 0) return;
|
47
|
+
|
48
|
+
if (_activeStream) {
|
49
|
+
FSEventStreamStop(_activeStream);
|
50
|
+
FSEventStreamUnscheduleFromRunLoop(_activeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
51
|
+
//CFRelease(_activeStream);
|
52
|
+
}
|
53
|
+
|
54
|
+
_activeStream = FSEventStreamCreate(NULL,
|
55
|
+
&myCallbackFunction,
|
56
|
+
NULL,
|
57
|
+
_watchedFiles,
|
58
|
+
kFSEventStreamEventIdSinceNow,
|
59
|
+
1.0, // latency
|
60
|
+
kFSEventStreamCreateFlagFileEvents);
|
61
|
+
|
62
|
+
FSEventStreamScheduleWithRunLoop(_activeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
63
|
+
|
64
|
+
FSEventStreamStart(_activeStream);
|
65
|
+
|
66
|
+
}
|
67
|
+
|
68
|
+
int maybeAddFileToWatchList(char *line)
|
69
|
+
{
|
70
|
+
CFStringRef file = CFStringCreateWithCString(NULL, line, kCFStringEncodingASCII);
|
71
|
+
struct stat buf;
|
72
|
+
|
73
|
+
if ([_fileIsWatched valueForKey:(__bridge NSString *)file]) {
|
74
|
+
return 0;
|
75
|
+
} else if (stat(line, &buf) == 0) {
|
76
|
+
[_fileIsWatched setValue:@"yes" forKey:(__bridge NSString *)file];
|
77
|
+
CFArrayAppendValue(_watchedFiles, file);
|
78
|
+
return 1;
|
79
|
+
} else {
|
80
|
+
return 0;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
void handleInputFiles()
|
85
|
+
{
|
86
|
+
int anyChanges = 0;
|
87
|
+
|
88
|
+
char line[2048];
|
89
|
+
|
90
|
+
fd_set fdset;
|
91
|
+
|
92
|
+
FD_ZERO(&fdset);
|
93
|
+
FD_SET(STDIN_FILENO, &fdset);
|
94
|
+
struct timeval timeout;
|
95
|
+
timeout.tv_sec = 0;
|
96
|
+
timeout.tv_usec = 0;
|
97
|
+
|
98
|
+
select(STDIN_FILENO+1, &fdset, NULL, NULL, &timeout);
|
99
|
+
while (FD_ISSET(STDIN_FILENO, &fdset) && fgets(line, 2048, stdin) != NULL) {
|
100
|
+
line[strlen(line)-1] = 0;
|
101
|
+
anyChanges |= maybeAddFileToWatchList(line);
|
102
|
+
select(STDIN_FILENO+1, &fdset, NULL, NULL, &timeout);
|
103
|
+
}
|
104
|
+
|
105
|
+
if (anyChanges) {
|
106
|
+
configureStream();
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
void configureTimerAndRun()
|
111
|
+
{
|
112
|
+
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL,
|
113
|
+
0,
|
114
|
+
0.5,
|
115
|
+
0,
|
116
|
+
0,
|
117
|
+
&handleInputFiles,
|
118
|
+
NULL);
|
119
|
+
|
120
|
+
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
|
121
|
+
CFRunLoopRun();
|
122
|
+
}
|
123
|
+
|
124
|
+
int main(int argc, const char * argv[])
|
125
|
+
{
|
126
|
+
@autoreleasepool {
|
127
|
+
_watchedFiles = CFArrayCreateMutable(NULL, 0, NULL);
|
128
|
+
_fileIsWatched = [[NSMutableDictionary alloc] initWithCapacity:500];
|
129
|
+
|
130
|
+
configureTimerAndRun();
|
131
|
+
}
|
132
|
+
return 0;
|
133
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class Thrud
|
2
2
|
|
3
3
|
class Task < Struct.new(:method_name, :desc, :long_desc, :method_options)
|
4
4
|
def arity(obj)
|
@@ -78,7 +78,7 @@ BANNER
|
|
78
78
|
taskname = ARGV.shift
|
79
79
|
arguments = ARGV
|
80
80
|
|
81
|
-
taskname
|
81
|
+
taskname ||= "help"
|
82
82
|
|
83
83
|
unless task = @tasks[taskname.to_s]
|
84
84
|
Zeus.ui.error "Could not find task \"#{taskname}\""
|
data/lib/zeus.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
require "zeus/version"
|
2
|
-
require 'zeus/server'
|
3
|
-
require "zeus/ui"
|
4
|
-
|
5
1
|
module Zeus
|
2
|
+
SOCKET_NAME = '.zeus.sock'
|
3
|
+
|
4
|
+
autoload :UI, 'zeus/ui'
|
5
|
+
autoload :CLI, 'zeus/cli'
|
6
|
+
autoload :DSL, 'zeus/dsl'
|
7
|
+
autoload :Server, 'zeus/server'
|
8
|
+
autoload :Client, 'zeus/client'
|
9
|
+
autoload :Version, 'zeus/version'
|
10
|
+
|
6
11
|
class ZeusError < StandardError
|
7
12
|
def self.status_code(code)
|
8
13
|
define_method(:status_code) { code }
|
data/lib/zeus/cli.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
require '
|
2
|
-
require 'tiny_thor'
|
1
|
+
require 'thrud'
|
3
2
|
|
4
3
|
module Zeus
|
5
|
-
class CLI <
|
4
|
+
class CLI < Thrud
|
6
5
|
|
7
6
|
def initialize(*)
|
8
7
|
super
|
@@ -34,7 +33,6 @@ module Zeus
|
|
34
33
|
use the zeus runner commands (see `zeus help` for a list of available commands).
|
35
34
|
D
|
36
35
|
def start
|
37
|
-
require 'zeus/server'
|
38
36
|
begin
|
39
37
|
require './.zeus.rb'
|
40
38
|
rescue LoadError
|
@@ -60,7 +58,6 @@ module Zeus
|
|
60
58
|
Zeus::Server.acceptors.each do |acc|
|
61
59
|
desc acc.name, (acc.description || "#{acc.name} task defined in .zeus.rb")
|
62
60
|
define_method(acc.name) { |*args|
|
63
|
-
require 'zeus/client'
|
64
61
|
Zeus::Client.run(acc.name, args)
|
65
62
|
}
|
66
63
|
map acc.aliases => acc.name
|
data/lib/zeus/client.rb
CHANGED
@@ -6,61 +6,90 @@ require "socket"
|
|
6
6
|
module Zeus
|
7
7
|
class Client
|
8
8
|
|
9
|
+
attr_accessor :pid
|
10
|
+
|
9
11
|
SIGNALS = {
|
10
12
|
"\x03" => "TERM",
|
11
13
|
"\x1C" => "QUIT"
|
12
14
|
}
|
13
15
|
SIGNAL_REGEX = Regexp.union(SIGNALS.keys)
|
14
16
|
|
15
|
-
def self.
|
16
|
-
|
17
|
-
$stdout.raw(&b)
|
18
|
-
else
|
19
|
-
b.call
|
20
|
-
end
|
17
|
+
def self.run(command, args)
|
18
|
+
new.run(command, args)
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
21
|
+
def run(command, args)
|
24
22
|
maybe_raw do
|
25
23
|
PTY.open do |master, slave|
|
26
|
-
|
27
|
-
|
28
|
-
trap("WINCH") { winch_ << "\0" }
|
24
|
+
@master = master
|
25
|
+
set_winsize
|
29
26
|
|
30
|
-
|
31
|
-
|
32
|
-
socket.send_io(slave)
|
33
|
-
slave.close
|
34
|
-
|
35
|
-
pid = socket.readline.chomp.to_i
|
27
|
+
@winch = make_winch_channel
|
28
|
+
pid = connect_to_server(command, args, slave)
|
36
29
|
|
30
|
+
buffer = ""
|
37
31
|
begin
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
if ready.include?(
|
42
|
-
winch.read(1)
|
43
|
-
$stdout.tty? and master.winsize = $stdout.winsize
|
44
|
-
Process.kill("WINCH", pid)
|
45
|
-
end
|
46
|
-
|
47
|
-
if ready.include?($stdin)
|
48
|
-
input = $stdin.readpartial(4096, buffer)
|
49
|
-
input.scan(SIGNAL_REGEX).each { |signal|
|
50
|
-
Process.kill(SIGNALS[signal], pid)
|
51
|
-
}
|
52
|
-
master << input
|
53
|
-
end
|
54
|
-
|
55
|
-
if ready.include?(master)
|
56
|
-
$stdout << master.readpartial(4096, buffer)
|
57
|
-
end
|
32
|
+
while ready = select([@winch, @master, $stdin])[0]
|
33
|
+
handle_winch if ready.include?(@winch)
|
34
|
+
handle_stdin(buffer) if ready.include?($stdin)
|
35
|
+
handle_master(buffer) if ready.include?(@master)
|
58
36
|
end
|
59
37
|
rescue EOFError
|
60
38
|
end
|
61
39
|
end
|
62
40
|
end
|
63
41
|
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def connect_to_server(command, arguments, slave, socket_path = Zeus::SOCKET_NAME)
|
46
|
+
socket = UNIXSocket.new(socket_path)
|
47
|
+
socket << {command: command, arguments: arguments}.to_json << "\n"
|
48
|
+
socket.send_io(slave)
|
49
|
+
slave.close
|
50
|
+
|
51
|
+
pid = socket.readline.chomp.to_i
|
52
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::ECONNRESET
|
53
|
+
Zeus.ui.error "Zeus doesn't seem to be running, try 'zeus start`"
|
54
|
+
abort
|
55
|
+
end
|
56
|
+
|
57
|
+
def make_winch_channel
|
58
|
+
winch, winch_ = IO.pipe
|
59
|
+
trap("WINCH") { winch_ << "\0" }
|
60
|
+
winch
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_winch
|
64
|
+
@winch.read(1)
|
65
|
+
set_winsize
|
66
|
+
Process.kill("WINCH", pid) if pid
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_stdin(buffer)
|
70
|
+
input = $stdin.readpartial(4096, buffer)
|
71
|
+
input.scan(SIGNAL_REGEX).each { |signal|
|
72
|
+
Process.kill(SIGNALS[signal], pid)
|
73
|
+
}
|
74
|
+
@master << input
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_master(buffer)
|
78
|
+
$stdout << @master.readpartial(4096, buffer)
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_winsize
|
82
|
+
$stdout.tty? and @master.winsize = $stdout.winsize
|
83
|
+
end
|
84
|
+
|
85
|
+
def maybe_raw(&b)
|
86
|
+
if $stdout.tty?
|
87
|
+
$stdout.raw(&b)
|
88
|
+
else
|
89
|
+
b.call
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
64
93
|
end
|
65
94
|
end
|
66
95
|
|
data/lib/zeus/dsl.rb
CHANGED
data/lib/zeus/server.rb
CHANGED
@@ -2,18 +2,18 @@ require 'json'
|
|
2
2
|
require 'socket'
|
3
3
|
|
4
4
|
require 'rb-kqueue'
|
5
|
-
|
6
5
|
require 'zeus/process'
|
7
|
-
require 'zeus/dsl'
|
8
|
-
require 'zeus/server/file_monitor'
|
9
|
-
require 'zeus/server/client_handler'
|
10
|
-
require 'zeus/server/process_tree_monitor'
|
11
|
-
require 'zeus/server/acceptor_registration_monitor'
|
12
|
-
require 'zeus/server/acceptor'
|
13
6
|
|
14
7
|
module Zeus
|
15
8
|
class Server
|
16
9
|
|
10
|
+
autoload :Stage, 'zeus/server/stage'
|
11
|
+
autoload :Acceptor, 'zeus/server/acceptor'
|
12
|
+
autoload :FileMonitor, 'zeus/server/file_monitor'
|
13
|
+
autoload :ClientHandler, 'zeus/server/client_handler'
|
14
|
+
autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
|
15
|
+
autoload :AcceptorRegistrationMonitor, 'zeus/server/acceptor_registration_monitor'
|
16
|
+
|
17
17
|
def self.define!(&b)
|
18
18
|
@@definition = Zeus::DSL::Evaluator.new.instance_eval(&b)
|
19
19
|
end
|
@@ -24,7 +24,7 @@ module Zeus
|
|
24
24
|
|
25
25
|
attr_reader :client_handler, :acceptor_registration_monitor
|
26
26
|
def initialize
|
27
|
-
@file_monitor = FileMonitor.new(&method(:dependency_did_change))
|
27
|
+
@file_monitor = FileMonitor::FSEvent.new(&method(:dependency_did_change))
|
28
28
|
@acceptor_registration_monitor = AcceptorRegistrationMonitor.new
|
29
29
|
@process_tree_monitor = ProcessTreeMonitor.new
|
30
30
|
@client_handler = ClientHandler.new(acceptor_registration_monitor)
|
@@ -39,22 +39,18 @@ module Zeus
|
|
39
39
|
|
40
40
|
PID_TYPE = "P"
|
41
41
|
def w_pid line
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
retry
|
47
|
-
end
|
42
|
+
@w_msg.send(PID_TYPE + line, 0)
|
43
|
+
rescue Errno::ENOBUFS
|
44
|
+
sleep 0.2
|
45
|
+
retry
|
48
46
|
end
|
49
47
|
|
50
48
|
FEATURE_TYPE = "F"
|
51
49
|
def w_feature line
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
retry
|
57
|
-
end
|
50
|
+
@w_msg.send(FEATURE_TYPE + line, 0)
|
51
|
+
rescue Errno::ENOBUFS
|
52
|
+
sleep 0.2
|
53
|
+
retry
|
58
54
|
end
|
59
55
|
|
60
56
|
def run
|
@@ -69,48 +65,38 @@ module Zeus
|
|
69
65
|
@w_msg.close
|
70
66
|
|
71
67
|
loop do
|
72
|
-
@file_monitor
|
73
|
-
|
74
|
-
datasources = [@r_msg,
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
rs, _, _ = IO.select(datasources, [], [], 1)
|
81
|
-
rescue Errno::EBADF
|
82
|
-
puts "EBADF" unless defined?($asdf)
|
83
|
-
sleep 1
|
84
|
-
$asdf = true
|
68
|
+
monitors = [@file_monitor, @acceptor_registration_monitor, @client_handler]
|
69
|
+
# TODO: Make @r_msg a Monitor instead. All that logic should be its own thing.
|
70
|
+
datasources = [@r_msg, *monitors.map(&:datasource)]
|
71
|
+
|
72
|
+
ready, _, _ = IO.select(datasources, [], [], 1)
|
73
|
+
next unless ready
|
74
|
+
monitors.each do |m|
|
75
|
+
m.on_datasource_event if ready.include?(m.datasource)
|
85
76
|
end
|
86
|
-
|
87
|
-
case r
|
88
|
-
when @acceptor_registration_monitor.datasource
|
89
|
-
@acceptor_registration_monitor.on_datasource_event
|
90
|
-
when @r_msg ; handle_messages
|
91
|
-
when @client_handler.datasource
|
92
|
-
@client_handler.on_datasource_event
|
93
|
-
end
|
94
|
-
end if rs
|
77
|
+
handle_messages if ready.include?(@r_msg)
|
95
78
|
end
|
96
79
|
|
80
|
+
ensure
|
81
|
+
File.unlink(Zeus::SOCKET_NAME)
|
97
82
|
end
|
98
83
|
|
99
84
|
def handle_messages
|
100
85
|
loop do
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
86
|
+
handle_message
|
87
|
+
end
|
88
|
+
rescue Errno::EAGAIN
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_message
|
92
|
+
data = @r_msg.recv_nonblock(1024)
|
93
|
+
case data[0]
|
94
|
+
when FEATURE_TYPE
|
95
|
+
handle_feature_message(data[1..-1])
|
96
|
+
when PID_TYPE
|
97
|
+
handle_pid_message(data[1..-1])
|
98
|
+
else
|
99
|
+
raise "Unrecognized message"
|
114
100
|
end
|
115
101
|
end
|
116
102
|
|
data/lib/zeus/server/acceptor.rb
CHANGED
@@ -36,9 +36,9 @@ module Zeus
|
|
36
36
|
|
37
37
|
@server.w_pid "#{pid}:#{Process.ppid}"
|
38
38
|
|
39
|
-
|
39
|
+
Zeus.ui.as_zeus "starting acceptor `#{@name}`"
|
40
40
|
trap("INT") {
|
41
|
-
|
41
|
+
Zeus.ui.as_zeus "killing acceptor `#{@name}`"
|
42
42
|
exit 0
|
43
43
|
}
|
44
44
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
-
require 'zeus/server/acceptor'
|
5
|
-
|
6
4
|
module Zeus
|
7
5
|
class Server
|
8
6
|
# The model here is kind of convoluted, so here's an explanation of what's
|
@@ -26,23 +24,16 @@ module Zeus
|
|
26
24
|
# 4. ClientHandler forwards the pid to the client over S_CLI.
|
27
25
|
#
|
28
26
|
class ClientHandler
|
29
|
-
SERVER_SOCK = ".zeus.sock"
|
30
|
-
|
31
27
|
def datasource ; @server ; end
|
32
28
|
def on_datasource_event ; handle_server_connection ; end
|
33
29
|
|
34
30
|
def initialize(registration_monitor)
|
35
31
|
@reg_monitor = registration_monitor
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
exit 1
|
42
|
-
# ensure
|
43
|
-
# @server.close rescue nil
|
44
|
-
# File.unlink(SERVER_SOCK)
|
45
|
-
end
|
32
|
+
@server = UNIXServer.new(Zeus::SOCKET_NAME)
|
33
|
+
@server.listen(10)
|
34
|
+
rescue Errno::EADDRINUSE
|
35
|
+
Zeus.ui.error "Zeus appears to be already running in this project. If not, remove .zeus.sock and try again."
|
36
|
+
exit 1
|
46
37
|
end
|
47
38
|
|
48
39
|
def handle_server_connection
|
@@ -71,7 +62,7 @@ module Zeus
|
|
71
62
|
# TODO handle nothing found
|
72
63
|
usock.send_io(client_terminal)
|
73
64
|
|
74
|
-
|
65
|
+
Zeus.ui.info "accepting connection for #{command}"
|
75
66
|
|
76
67
|
# 4
|
77
68
|
acceptor.socket.puts arguments.to_json
|
@@ -2,56 +2,8 @@ require 'rb-kqueue'
|
|
2
2
|
|
3
3
|
module Zeus
|
4
4
|
class Server
|
5
|
-
|
6
|
-
|
7
|
-
TARGET_FD_LIMIT = 8192
|
8
|
-
|
9
|
-
def initialize(&change_callback)
|
10
|
-
configure_file_descriptor_resource_limit
|
11
|
-
@queue = KQueue::Queue.new
|
12
|
-
@watched_files = {}
|
13
|
-
@deleted_files = []
|
14
|
-
@change_callback = change_callback
|
15
|
-
end
|
16
|
-
|
17
|
-
def process_events
|
18
|
-
@queue.poll
|
19
|
-
end
|
20
|
-
|
21
|
-
def watch(file)
|
22
|
-
return if @watched_files[file]
|
23
|
-
@watched_files[file] = true
|
24
|
-
@queue.watch_file(file, :write, :extend, :rename, :delete, &method(:file_did_change))
|
25
|
-
rescue Errno::ENOENT
|
26
|
-
Zeus.ui.debug("No file found at #{file}")
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def file_did_change(event)
|
32
|
-
Zeus.ui.info("Dependency change at #{event.watcher.path}")
|
33
|
-
resubscribe_deleted_file(event) if event.flags.include?(:delete)
|
34
|
-
@change_callback.call(event.watcher.path)
|
35
|
-
end
|
36
|
-
|
37
|
-
def configure_file_descriptor_resource_limit
|
38
|
-
limit = Process.getrlimit(Process::RLIMIT_NOFILE)
|
39
|
-
if limit[0] < TARGET_FD_LIMIT && limit[1] >= TARGET_FD_LIMIT
|
40
|
-
Process.setrlimit(Process::RLIMIT_NOFILE, TARGET_FD_LIMIT)
|
41
|
-
else
|
42
|
-
puts "\x1b[33m[zeus] Warning: increase the max number of file descriptors. If you have a large project, this max cause a crash in about 10 seconds.\x1b[0m"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def resubscribe_deleted_file(event)
|
47
|
-
event.watcher.disable!
|
48
|
-
begin
|
49
|
-
@queue.watch_file(event.watcher.path, :write, :extend, :rename, :delete, &method(:file_did_change))
|
50
|
-
rescue Errno::ENOENT
|
51
|
-
@deleted_files << event.watcher.path
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
5
|
+
module FileMonitor
|
6
|
+
autoload :FSEvent, 'zeus/server/file_monitor/fsevent'
|
55
7
|
end
|
56
8
|
end
|
57
9
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Zeus
|
4
|
+
class Server
|
5
|
+
module FileMonitor
|
6
|
+
class FSEvent
|
7
|
+
WRAPPER_PATH = File.expand_path("../../../../../ext/fsevents-wrapper/fsevents-wrapper", __FILE__)
|
8
|
+
|
9
|
+
def datasource ; @io_out ; end
|
10
|
+
def on_datasource_event ; handle_changed_files ; end
|
11
|
+
|
12
|
+
def initialize(&change_callback)
|
13
|
+
@change_callback = change_callback
|
14
|
+
@io_in, @io_out, _ = Open3.popen2e(WRAPPER_PATH)
|
15
|
+
@watched_files = {}
|
16
|
+
@buffer = ""
|
17
|
+
end
|
18
|
+
|
19
|
+
def handle_changed_files
|
20
|
+
loop do
|
21
|
+
begin
|
22
|
+
read_and_notify_files
|
23
|
+
rescue Errno::EAGAIN
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_and_notify_files
|
30
|
+
lines = @io_out.read_nonblock(30000)
|
31
|
+
files = lines.split("\n")
|
32
|
+
files[0] = "#{@buffer}#{files[0]}" unless @buffer == ""
|
33
|
+
unless lines[-1] == "\n"
|
34
|
+
@buffer = files.pop
|
35
|
+
end
|
36
|
+
|
37
|
+
files.each do |file|
|
38
|
+
file_did_change(file)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def watch(file)
|
43
|
+
return false if @watched_files[file]
|
44
|
+
@watched_files[file] = true
|
45
|
+
File.open('a.log', 'a') { |f| f.puts file }
|
46
|
+
@io_in.puts file
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def file_did_change(file)
|
53
|
+
Zeus.ui.info("Dependency change at #{file}")
|
54
|
+
@change_callback.call(file)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
@@ -76,7 +76,7 @@ module Zeus
|
|
76
76
|
def kill_nodes_with_feature(file, base = @root)
|
77
77
|
if base.has_feature?(file)
|
78
78
|
if base == @root.children[0] || base == @root
|
79
|
-
|
79
|
+
Zeus.ui.error "One of zeus's dependencies changed. Not killing zeus. You may have to restart the server."
|
80
80
|
return false
|
81
81
|
end
|
82
82
|
kill_node(base)
|
data/lib/zeus/server/stage.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Zeus
|
2
2
|
class Server
|
3
3
|
class Stage
|
4
|
+
HasNoChildren = Class.new(Exception)
|
4
5
|
|
5
6
|
attr_accessor :name, :stages, :actions
|
6
7
|
attr_reader :pid
|
@@ -17,9 +18,10 @@ module Zeus
|
|
17
18
|
$0 = "zeus spawner: #{@name}"
|
18
19
|
pid = Process.pid
|
19
20
|
@server.w_pid "#{pid}:#{Process.ppid}"
|
20
|
-
|
21
|
+
|
22
|
+
Zeus.ui.as_zeus("starting spawner `#{@name}`")
|
21
23
|
trap("INT") {
|
22
|
-
|
24
|
+
Zeus.ui.as_zeus("killing spawner `#{@name}`")
|
23
25
|
exit 0
|
24
26
|
}
|
25
27
|
|
@@ -38,7 +40,7 @@ module Zeus
|
|
38
40
|
begin
|
39
41
|
pid = Process.wait
|
40
42
|
rescue Errno::ECHILD
|
41
|
-
raise "Stage `#{@name}`
|
43
|
+
raise HasNoChildren.new("Stage `#{@name}` - All terminal nodes must be acceptors")
|
42
44
|
end
|
43
45
|
if (status = $?.exitstatus) > 0
|
44
46
|
exit status
|
data/lib/zeus/templates/rails.rb
CHANGED
@@ -19,7 +19,7 @@ Zeus::Server.define! do
|
|
19
19
|
stage :dev do
|
20
20
|
action do
|
21
21
|
Bundler.require(:development)
|
22
|
-
ENV['RAILS_ENV'] = "development"
|
22
|
+
Rails.env = ENV['RAILS_ENV'] = "development"
|
23
23
|
require APP_PATH
|
24
24
|
Rails.application.require_environment!
|
25
25
|
end
|
@@ -57,22 +57,23 @@ Zeus::Server.define! do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
stage :test do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
# stage :test do
|
61
|
+
# action do
|
62
|
+
# Rails.env = ENV['RAILS_ENV'] = "test"
|
63
|
+
# Bundler.require(:test)
|
64
|
+
# require APP_PATH
|
65
|
+
# Rails.application.require_environment!
|
66
|
+
# end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
68
|
+
# command :testrb do
|
69
|
+
# (r = Test::Unit::AutoRunner.new(true)).process_args(ARGV) or
|
70
|
+
# abort r.options.banner + " tests..."
|
71
|
+
# exit r.run
|
72
|
+
# end
|
73
73
|
|
74
|
-
end
|
74
|
+
# end
|
75
75
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
79
|
+
|
data/lib/zeus/ui.rb
CHANGED
@@ -6,6 +6,10 @@ module Zeus
|
|
6
6
|
@debug = ENV['DEBUG']
|
7
7
|
end
|
8
8
|
|
9
|
+
def as_zeus(msg)
|
10
|
+
tell_me("[zeus] #{msg}",:purple)
|
11
|
+
end
|
12
|
+
|
9
13
|
def info(msg)
|
10
14
|
tell_me(msg, nil) if !@quiet
|
11
15
|
end
|
@@ -45,6 +49,7 @@ module Zeus
|
|
45
49
|
when :red ; "\x1b[31m#{msg}\x1b[0m"
|
46
50
|
when :green ; "\x1b[32m#{msg}\x1b[0m"
|
47
51
|
when :yellow ; "\x1b[33m#{msg}\x1b[0m"
|
52
|
+
when :purple ; "\x1b[35m#{msg}\x1b[0m"
|
48
53
|
else ; msg
|
49
54
|
end
|
50
55
|
puts msg
|
data/lib/zeus/version.rb
CHANGED
data/zeus.gemspec
CHANGED
@@ -8,12 +8,10 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.summary = %q{Zeus is an alpha-quality application preloader with terrible documentation.}
|
9
9
|
gem.homepage = "http://github.com/burke/zeus"
|
10
10
|
|
11
|
-
gem.files = `git ls-files`.split(
|
11
|
+
gem.files = `git ls-files`.split("\n").reject{ |f| f =~ /xcodeproj/ }
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
14
|
gem.name = "zeus"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Zeus::VERSION
|
17
|
-
|
18
|
-
gem.add_dependency "rb-kqueue-burke", "~> 0.1.0"
|
19
17
|
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.2.0.
|
4
|
+
version: 0.2.0.beta2
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,19 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
13
|
-
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: rb-kqueue-burke
|
16
|
-
requirement: &70116037263980 !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: 0.1.0
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: *70116037263980
|
12
|
+
date: 2012-07-31 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
25
14
|
description: Zeus preloads pretty much everything you'll ever want to use in development.
|
26
15
|
email:
|
27
16
|
- burke@libbey.me
|
@@ -36,18 +25,21 @@ files:
|
|
36
25
|
- README.md
|
37
26
|
- Rakefile
|
38
27
|
- bin/zeus
|
39
|
-
-
|
28
|
+
- ext/.DS_Store
|
29
|
+
- ext/fsevents-wrapper/fsevents-wrapper
|
30
|
+
- ext/fsevents-wrapper/main.m
|
31
|
+
- lib/thrud.rb
|
40
32
|
- lib/zeus.rb
|
41
33
|
- lib/zeus/cli.rb
|
42
34
|
- lib/zeus/client.rb
|
43
35
|
- lib/zeus/dsl.rb
|
44
|
-
- lib/zeus/init.rb
|
45
36
|
- lib/zeus/process.rb
|
46
37
|
- lib/zeus/server.rb
|
47
38
|
- lib/zeus/server/acceptor.rb
|
48
39
|
- lib/zeus/server/acceptor_registration_monitor.rb
|
49
40
|
- lib/zeus/server/client_handler.rb
|
50
41
|
- lib/zeus/server/file_monitor.rb
|
42
|
+
- lib/zeus/server/file_monitor/fsevent.rb
|
51
43
|
- lib/zeus/server/process_tree_monitor.rb
|
52
44
|
- lib/zeus/server/stage.rb
|
53
45
|
- lib/zeus/templates/rails.rb
|
data/lib/zeus/init.rb
DELETED