zeus 0.4.6 → 0.10.0.pre

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.
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