zeus 0.4.6 → 0.10.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +0 -4
  2. data/Rakefile +52 -8
  3. data/bin/zeus +14 -6
  4. data/build/fsevents-wrapper +0 -0
  5. data/build/zeus-darwin-amd64 +0 -0
  6. data/build/zeus-linux-386 +0 -0
  7. data/build/zeus-linux-amd64 +0 -0
  8. data/examples/zeus.json +22 -0
  9. data/ext/fsevents-wrapper/fsevents-wrapper +0 -0
  10. data/ext/inotify-wrapper/extconf.rb +24 -0
  11. data/ext/inotify-wrapper/inotify-wrapper.cpp +86 -0
  12. data/lib/zeus.rb +123 -35
  13. data/lib/zeus/{server/load_tracking.rb → load_tracking.rb} +1 -3
  14. data/lib/zeus/rails.rb +141 -0
  15. data/man/build/zeus +49 -0
  16. data/man/build/zeus-init +13 -0
  17. data/man/build/zeus-init.txt +17 -0
  18. data/man/build/zeus-start +16 -0
  19. data/man/build/zeus-start.txt +18 -0
  20. data/man/build/zeus.txt +50 -0
  21. data/zeus.gemspec +17 -6
  22. metadata +27 -58
  23. data/.gitignore +0 -17
  24. data/.travis.yml +0 -5
  25. data/.zeus.rb +0 -11
  26. data/README.md +0 -73
  27. data/docs/acceptor_registration.md +0 -14
  28. data/docs/client_server_handshake.md +0 -25
  29. data/ext/fsevents-wrapper/main.m +0 -118
  30. data/lib/thrud.rb +0 -97
  31. data/lib/zeus/cli.rb +0 -80
  32. data/lib/zeus/client.rb +0 -114
  33. data/lib/zeus/client/winsize.rb +0 -28
  34. data/lib/zeus/error_printer.rb +0 -16
  35. data/lib/zeus/plan.rb +0 -18
  36. data/lib/zeus/plan/acceptor.rb +0 -38
  37. data/lib/zeus/plan/node.rb +0 -66
  38. data/lib/zeus/plan/stage.rb +0 -50
  39. data/lib/zeus/server.rb +0 -103
  40. data/lib/zeus/server/acceptor.rb +0 -79
  41. data/lib/zeus/server/acceptor_registration_monitor.rb +0 -75
  42. data/lib/zeus/server/client_handler.rb +0 -106
  43. data/lib/zeus/server/command_runner.rb +0 -70
  44. data/lib/zeus/server/file_monitor.rb +0 -8
  45. data/lib/zeus/server/file_monitor/fsevent.rb +0 -102
  46. data/lib/zeus/server/process_tree_monitor.rb +0 -89
  47. data/lib/zeus/server/stage.rb +0 -88
  48. data/lib/zeus/server/stage/error_state.rb +0 -42
  49. data/lib/zeus/server/stage/feature_notifier.rb +0 -38
  50. data/lib/zeus/templates/rails.rb +0 -133
  51. data/lib/zeus/ui.rb +0 -57
  52. data/lib/zeus/version.rb +0 -3
  53. data/spec/cli_spec.rb +0 -95
  54. data/spec/error_printer_spec.rb +0 -27
  55. data/spec/integration_spec.rb +0 -106
  56. data/spec/server/file_monitor/fsevent_spec.rb +0 -88
  57. data/spec/server/load_tracking_spec.rb +0 -67
  58. data/spec/server/process_tree_monitor_spec.rb +0 -50
  59. data/spec/spec_helper.rb +0 -38
  60. data/spec/ui_spec.rb +0 -54
data/Gemfile CHANGED
@@ -2,7 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in zeus.gemspec
4
4
  gemspec
5
-
6
- gem 'rspec'
7
- gem 'rake'
8
- gem 'pry'
data/Rakefile CHANGED
@@ -1,13 +1,57 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require 'fileutils'
4
+ require 'pathname'
3
5
 
4
- require 'rspec/core/rake_task'
5
- task :spec do
6
- raise "tests do not work with bundle exec" if defined?(Bundler)
7
- desc "Run specs under spec/"
8
- RSpec::Core::RakeTask.new do |t|
9
- t.pattern = 'spec/**/*_spec.rb'
10
- end
6
+ ROOT_PATH = Pathname.new(File.expand_path("../../", __FILE__))
7
+ RUBYGEM_PATH = Pathname.new(File.expand_path("../", __FILE__))
8
+
9
+ task build: [:import_externals, :version, :manifest]
10
+ task default: :build
11
+
12
+ task :import_externals do
13
+ puts "building rubygem"
14
+
15
+ Rake::Task[:clean].invoke
16
+
17
+ FileUtils.rm_r(RUBYGEM_PATH + "man") rescue nil
18
+ FileUtils.rm_r(RUBYGEM_PATH + "build") rescue nil
19
+
20
+ # manpages
21
+ FileUtils.mkdir(RUBYGEM_PATH + "man")
22
+ FileUtils.cp_r(ROOT_PATH + "man/build", RUBYGEM_PATH + "man")
23
+
24
+ # multi-arch binaries
25
+ FileUtils.cp_r(ROOT_PATH + "build", RUBYGEM_PATH + "build")
26
+
27
+ Rake::Task[:manifest].invoke
28
+ Rake::Task[:build].invoke
29
+ end
30
+
31
+ task :version do
32
+ version = File.read('../VERSION').chomp
33
+ File.open('lib/zeus/version.rb', 'w') { |f| f.puts <<END
34
+ module Zeus
35
+ VERSION = "#{version}"
36
+ end
37
+ END
38
+ }
39
+ end
40
+
41
+ task :manifest do
42
+ files = `find . -type file | sed 's|^\./||'`.lines.map(&:chomp)
43
+ exceptions = [
44
+ /.gitignore$/,
45
+ /^MANIFEST$/,
46
+ /^pkg\//,
47
+ ]
48
+ files.reject! { |f| exceptions.any? {|ex| f =~ ex }}
49
+ File.open('MANIFEST', 'w') {|f| f.puts files.join("\n") }
50
+ end
51
+
52
+ task :clean do
53
+ FileUtils.rm(RUBYGEM_PATH + "lib/zeus/version.rb") rescue nil
54
+ FileUtils.rm_r(RUBYGEM_PATH + "man") rescue nil
55
+ FileUtils.rm_r(RUBYGEM_PATH + "build") rescue nil
11
56
  end
12
57
 
13
- task default: :spec
data/bin/zeus CHANGED
@@ -1,8 +1,16 @@
1
- #!/usr/bin/env ruby
1
+ platform = `uname -sm`
2
2
 
3
- if defined?(Bundler)
4
- puts "\x1b[34mDon't run Zeus with `bundle exec`. It's unnecessarily slow.\x1b[0m"
5
- end
3
+ exe = case platform
4
+ when /^Darwin/ ; "zeus-darwin-amd64"
5
+ when /^Linux.*64/ ; "zeus-linux-amd64"
6
+ when /^Linux.*/ ; "zeus-linux-386"
7
+ else
8
+ puts "Zeus is not supported on your platform."
9
+ puts "It's not likely to ever be possible on Windows."
10
+ puts "If you're using another platform that you think should work easily, open an issue at:"
11
+ puts "https://github.com/burke/zeus/issues"
12
+ exit 1
13
+ end
6
14
 
7
- require 'zeus'
8
- Zeus::CLI.start
15
+ zeusgemdir = File.expand_path("../../", __FILE__)
16
+ exec "#{zeusgemdir}/build/#{exe}", *ARGV
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,22 @@
1
+ {
2
+ "command": "ruby -rzeus/rails -eZeus.go",
3
+
4
+ "plan": {
5
+ "boot": {
6
+ "default_bundle": {
7
+ "development_environment": {
8
+ "prerake": {"rake": []},
9
+ "runner": ["r"],
10
+ "console": ["c"],
11
+ "server": ["s"],
12
+ "generate": ["g"]
13
+ },
14
+ "test_environment": {
15
+ "testtask": [],
16
+ "test_helper": {"testrb": []},
17
+ "spec_helper": {"rspec": []}
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,24 @@
1
+ if /linux/ =~ RUBY_PLATFORM
2
+ open("Makefile", "wb") do |f|
3
+ f.write <<-EOF
4
+ CXX = g++
5
+ CXXFLAGS = -O3 -g -Wall
6
+
7
+ inotify-wrapper: inotify-wrapper.o
8
+ $(CXX) $(CXXFLAGS) $< -o $@
9
+
10
+ %.o: %.cpp
11
+ $(CXX) $(CXXFLAGS) -c $< -o $@
12
+
13
+ install:
14
+ # do nothing
15
+ EOF
16
+ end
17
+ else
18
+ open("Makefile", "wb") do |f|
19
+ f.write <<-EOF
20
+ install:
21
+ # do nothing
22
+ EOF
23
+ end
24
+ end
@@ -0,0 +1,86 @@
1
+ #include <map>
2
+ #include <string>
3
+
4
+ #include <stdio.h>
5
+ #include <stdlib.h>
6
+ #include <string.h>
7
+ #include <errno.h>
8
+ #include <unistd.h>
9
+ #include <sys/types.h>
10
+ #include <sys/inotify.h>
11
+
12
+ #define EVENT_SIZE (sizeof (struct inotify_event))
13
+ #define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))
14
+
15
+ using namespace std;
16
+
17
+ static int _inotify_fd;
18
+ static map<int, char*> _WatchedFiles;
19
+ static map<char*, bool> _FileIsWatched;
20
+
21
+ static int inotifyFlags = IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF;
22
+
23
+ void maybeAddFileToWatchList(char *file)
24
+ {
25
+ if (_FileIsWatched[file]) return;
26
+
27
+ _FileIsWatched[file] = true;
28
+ int wd = inotify_add_watch(_inotify_fd, file, inotifyFlags);
29
+ _WatchedFiles[wd] = file;
30
+ }
31
+
32
+ void handleStdin()
33
+ {
34
+ char line[2048];
35
+ if (fgets(line, sizeof(line), stdin) == NULL) return;
36
+ line[strlen(line)-1] = 0;
37
+
38
+ maybeAddFileToWatchList(line);
39
+ }
40
+
41
+ void handleInotify()
42
+ {
43
+ int length;
44
+ int i = 0;
45
+ char buffer[EVENT_BUF_LEN];
46
+ string filename;
47
+
48
+ length = read(_inotify_fd, buffer, EVENT_BUF_LEN);
49
+ if (length < 0) return;
50
+
51
+ while (i < length) {
52
+ struct inotify_event *event = (struct inotify_event *) &buffer[i];
53
+ printf("%s\n", _WatchedFiles[event->wd]);
54
+ fflush(stdout);
55
+
56
+ i += EVENT_SIZE + event->len;
57
+ }
58
+ }
59
+
60
+ void go()
61
+ {
62
+ fd_set rfds;
63
+ int retval;
64
+
65
+ for (;;) {
66
+ FD_ZERO(&rfds);
67
+ FD_SET(0, &rfds);
68
+ FD_SET(_inotify_fd, &rfds);
69
+
70
+ retval = select(_inotify_fd+1, &rfds, NULL, NULL, NULL);
71
+
72
+ if (retval == -1) {
73
+ // perror("select");
74
+ } else if (retval) {
75
+ if (FD_ISSET(0, &rfds)) handleStdin();
76
+ if (FD_ISSET(_inotify_fd, &rfds)) handleInotify();
77
+ }
78
+ }
79
+ }
80
+
81
+
82
+ int main(int argc, const char *argv[])
83
+ {
84
+ _inotify_fd = inotify_init();
85
+ go();
86
+ }
@@ -1,47 +1,135 @@
1
- require 'zeus/version'
2
- require 'zeus/ui'
3
- require 'zeus/plan'
4
- require 'zeus/server'
5
- require 'zeus/client'
6
- require 'zeus/error_printer'
7
- require 'zeus/cli'
1
+ require 'socket'
2
+ require 'json'
3
+
4
+ require 'zeus/load_tracking'
8
5
 
9
6
  module Zeus
10
- SOCKET_NAME = '.zeus.sock'
7
+ class << self
11
8
 
12
- class ZeusError < StandardError
13
- def self.status_code(code)
14
- define_method(:status_code) { code }
9
+ def add_extra_feature(path)
10
+ $untracked_features ||= []
11
+ $untracked_features << path
15
12
  end
16
- end
17
13
 
18
- def self.ui
19
- @ui ||= UI.new
20
- end
14
+ attr_accessor :plan
21
15
 
22
- def self.ui=(ui)
23
- @ui = ui
24
- end
16
+ def report_error_to_master(local, error)
17
+ str = "R:"
18
+ str << "#{error.backtrace[0]}: #{error.message} (#{error.class})\n"
19
+ error.backtrace[1..-1].each do |line|
20
+ str << "\tfrom #{line}\n"
21
+ end
22
+ str << "\0"
23
+ local.write str
24
+ end
25
25
 
26
- def self.after_fork(&b)
27
- @after_fork ||= []
28
- @after_fork << b
29
- end
26
+ def run_action(socket, identifier)
27
+ plan.send(identifier)
28
+ socket.write "R:OK\0"
29
+ rescue Exception => e
30
+ report_error_to_master(socket, e)
31
+ end
30
32
 
31
- def self.run_after_fork!
32
- @after_fork.map(&:call) if @after_fork
33
- @after_fork = []
34
- end
33
+ def notify_features(sock, features)
34
+ features.each do |t|
35
+ begin
36
+ sock.write "F:#{t}\0"
37
+ rescue Errno::ENOBUFS
38
+ sleep 0.2
39
+ retry
40
+ end
41
+ end
42
+ end
35
43
 
36
- def self.thread_with_backtrace_on_error(&b)
37
- Thread.new {
38
- begin
39
- b.call
40
- rescue => e
41
- ErrorPrinter.new(e).write_to($stdout)
44
+ def handle_dead_children(sock)
45
+ # TODO: It would be nice if it were impossible for this
46
+ # to interfere with the identifer -> IO thing.
47
+ loop do
48
+ pid = Process.wait(-1, Process::WNOHANG)
49
+ break if pid.nil?
50
+ # sock.send("D:#{pid}")
42
51
  end
43
- }
44
- end
52
+ rescue Errno::ECHILD
53
+ end
45
54
 
46
- end
55
+ def go(identifier=:boot)
56
+ identifier = identifier.to_sym
57
+ $0 = "zeus slave: #{identifier}"
58
+ # okay, so I ahve this FD that I can use to send data to the master.
59
+ fd = ENV['ZEUS_MASTER_FD'].to_i
60
+ master = UNIXSocket.for_fd(fd)
61
+
62
+ # I need to give the master a way to talk to me exclusively
63
+ local, remote = UNIXSocket.pair(:DGRAM)
64
+ master.send_io(remote)
65
+
66
+ # Now I need to tell the master about my PID and ID
67
+ local.write "P:#{Process.pid}:#{identifier}\0"
68
+
69
+ # Now we run the action and report its success/fail status to the master.
70
+ features = features_loaded_by {
71
+ run_action(local, identifier)
72
+ }
73
+
74
+ # the master wants to know about the files that running the action caused us to load.
75
+ Thread.new { notify_features(local, features) }
47
76
 
77
+ # We are now 'connected'. From this point, we may receive requests to fork.
78
+ loop do
79
+ new_identifier = local.recv(1024)
80
+ new_identifier.chomp!("\0")
81
+ if new_identifier =~ /^S:/
82
+ fork { plan.after_fork ; go(new_identifier.sub(/^S:/,'')) }
83
+ else
84
+ fork { plan.after_fork ; command(new_identifier.sub(/^C:/,''), local) }
85
+ end
86
+ end
87
+ end
88
+
89
+ def all_features
90
+ untracked = defined?($untracked_features) ? $untracked_features : []
91
+ $LOADED_FEATURES + untracked
92
+ end
93
+
94
+ def features_loaded_by(&block)
95
+ old_features = all_features()
96
+ yield
97
+ new_features = all_features() - old_features
98
+ return new_features
99
+ end
100
+
101
+ def command(identifier, sock)
102
+ $0 = "zeus runner: #{identifier}"
103
+ Process.setsid
104
+
105
+ local, remote = UNIXSocket.pair(:DGRAM)
106
+ sock.send_io(remote)
107
+ remote.close
108
+ sock.close
109
+
110
+ arguments = local.recv(1024)
111
+ arguments.chomp!("\0")
112
+
113
+ pid = fork {
114
+ plan.after_fork
115
+ client_terminal = local.recv_io
116
+ local.write "P:#{Process.pid}:\0"
117
+ local.close
118
+
119
+ $stdin.reopen(client_terminal)
120
+ $stdout.reopen(client_terminal)
121
+ $stderr.reopen(client_terminal)
122
+ ARGV.replace(JSON.parse(arguments))
123
+
124
+ plan.send(identifier)
125
+ }
126
+
127
+ Process.wait(pid)
128
+ code = $?.exitstatus
129
+
130
+ local.write "#{code}\0"
131
+ local.close
132
+ end
133
+
134
+ end
135
+ end
@@ -2,16 +2,14 @@ module Zeus
2
2
  class Server
3
3
  class LoadTracking
4
4
  class << self
5
- attr_accessor :server
6
5
 
7
6
  def add_feature(file)
8
- return unless server
9
7
  path = if File.exist?(File.expand_path(file))
10
8
  File.expand_path(file)
11
9
  else
12
10
  find_in_load_path(file)
13
11
  end
14
- server.add_extra_feature path if path
12
+ Zeus.add_extra_feature(path) if path
15
13
  end
16
14
 
17
15
  private
@@ -0,0 +1,141 @@
1
+ ROOT_PATH = File.expand_path(Dir.pwd)
2
+ ENV_PATH = File.expand_path('config/environment', ROOT_PATH)
3
+ BOOT_PATH = File.expand_path('config/boot', ROOT_PATH)
4
+ APP_PATH = File.expand_path('config/application', ROOT_PATH)
5
+
6
+ require 'zeus'
7
+
8
+ module Zeus ; module Rails ; end ; end
9
+ Zeus.plan ||= Zeus::Rails
10
+
11
+ module Zeus
12
+ module Rails
13
+ class << self
14
+
15
+ def after_fork
16
+ reconnect_activerecord
17
+ restart_girl_friday
18
+ end
19
+
20
+ def boot
21
+ require BOOT_PATH
22
+ require 'rails/all'
23
+ end
24
+
25
+ def default_bundle
26
+ Bundler.require(:default)
27
+ end
28
+
29
+ def development_environment
30
+ Bundler.require(:development)
31
+ ::Rails.env = ENV['RAILS_ENV'] = "development"
32
+ require APP_PATH
33
+ ::Rails.application.require_environment!
34
+ end
35
+
36
+ def prerake
37
+ require 'rake'
38
+ load 'Rakefile'
39
+ end
40
+
41
+ def rake
42
+ Rake.application.run
43
+ end
44
+
45
+ def generate
46
+ begin
47
+ require 'rails/generators'
48
+ ::Rails.application.load_generators
49
+ rescue LoadError # Rails 3.0 doesn't require this block to be run, but 3.2+ does
50
+ end
51
+ require 'rails/commands/generate'
52
+ end
53
+
54
+ def runner
55
+ require 'rails/commands/runner'
56
+ end
57
+
58
+ def console
59
+ require 'rails/commands/console'
60
+ ::Rails::Console.start(::Rails.application)
61
+ end
62
+
63
+ def server
64
+ require 'rails/commands/server'
65
+ server = ::Rails::Server.new
66
+ Dir.chdir(::Rails.application.root)
67
+ server.start
68
+ end
69
+
70
+ def test_environment
71
+ Bundler.require(:test)
72
+
73
+ ::Rails.env = ENV['RAILS_ENV'] = 'test'
74
+ require APP_PATH
75
+
76
+ $rails_rake_task = 'yup' # lie to skip eager loading
77
+ ::Rails.application.require_environment!
78
+ $rails_rake_task = nil
79
+ $LOAD_PATH.unshift(ROOT_PATH) unless $LOAD_PATH.include?(ROOT_PATH)
80
+
81
+ if Dir.exist?(ROOT_PATH + "/test")
82
+ test = File.join(ROOT_PATH, 'test')
83
+ $LOAD_PATH.unshift(test) unless $LOAD_PATH.include?(test)
84
+ end
85
+
86
+ if Dir.exist?(ROOT_PATH + "/spec")
87
+ spec = File.join(ROOT_PATH, 'spec')
88
+ $LOAD_PATH.unshift(spec) unless $LOAD_PATH.include?(spec)
89
+ end
90
+ end
91
+
92
+ def test_helper
93
+ require 'test_helper'
94
+ end
95
+
96
+ def testrb
97
+ argv = ARGV
98
+
99
+ # try to find pattern by line using testrbl
100
+ if defined?(Testrbl) && argv.size == 1 and argv.first =~ /^\S+:\d+$/
101
+ file, line = argv.first.split(':')
102
+ argv = [file, '-n', "/#{Testrbl.send(:pattern_from_file, File.readlines(file), line)}/"]
103
+ puts "using -n '#{argv[2]}'" # let users copy/paste or adjust the pattern
104
+ end
105
+
106
+ runner = Test::Unit::AutoRunner.new(true)
107
+ if runner.process_args(argv)
108
+ exit runner.run
109
+ else
110
+ abort runner.options.banner + " tests..."
111
+ end
112
+ end
113
+
114
+ def spec_helper
115
+ require 'spec_helper'
116
+ end
117
+
118
+ def rspec
119
+ exit RSpec::Core::Runner.run(ARGV)
120
+ end
121
+
122
+
123
+ private
124
+
125
+ def restart_girl_friday
126
+ return unless defined?(GirlFriday::WorkQueue)
127
+ # The Actor is run in a thread, and threads don't persist post-fork.
128
+ # We just need to restart each one in the newly-forked process.
129
+ ObjectSpace.each_object(GirlFriday::WorkQueue) do |obj|
130
+ obj.send(:start)
131
+ end
132
+ end
133
+
134
+ def reconnect_activerecord
135
+ ActiveRecord::Base.clear_all_connections! rescue nil
136
+ ActiveRecord::Base.establish_connection rescue nil
137
+ end
138
+
139
+ end
140
+ end
141
+ end