zeus 0.2.5 → 0.2.6

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/lib/zeus/server.rb CHANGED
@@ -8,7 +8,9 @@ module Zeus
8
8
  autoload :Acceptor, 'zeus/server/acceptor'
9
9
  autoload :FileMonitor, 'zeus/server/file_monitor'
10
10
  autoload :ProcessTree, 'zeus/server/process_tree'
11
+ autoload :ForkedProcess, 'zeus/server/forked_process'
11
12
  autoload :ClientHandler, 'zeus/server/client_handler'
13
+ autoload :AcceptorErrorState, 'zeus/server/acceptor_error_state'
12
14
  autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
13
15
  autoload :AcceptorRegistrationMonitor, 'zeus/server/acceptor_registration_monitor'
14
16
 
@@ -4,18 +4,45 @@ require 'socket'
4
4
  # See Zeus::Server::ClientHandler for relevant documentation
5
5
  module Zeus
6
6
  class Server
7
- class Acceptor
7
+ class Acceptor < ForkedProcess
8
+ attr_accessor :aliases, :description, :action
8
9
 
9
- attr_accessor :name, :aliases, :description, :action
10
- def initialize(server)
11
- @server = server
10
+ def descendent_acceptors
11
+ self
12
+ end
13
+
14
+ def before_setup
15
+ register_with_client_handler(Process.pid)
16
+ end
17
+
18
+ def runloop!
19
+ loop do
20
+ prefork_action!
21
+ terminal, arguments = accept_connection # blocking
22
+ child = fork { __RUNNER__run(terminal, arguments) }
23
+ terminal.close
24
+
25
+ Process.detach(child)
26
+ end
27
+ end
28
+
29
+ def __RUNNER__run(terminal, arguments)
30
+ $0 = "zeus runner: #{@name}"
31
+ postfork_action!
32
+ @s_acceptor << $$ << "\n"
33
+ $stdin.reopen(terminal)
34
+ $stdout.reopen(terminal)
35
+ $stderr.reopen(terminal)
36
+ ARGV.replace(arguments)
37
+
38
+ @action.call
12
39
  end
13
40
 
41
+ private
42
+
14
43
  def register_with_client_handler(pid)
15
44
  @s_client_handler, @s_acceptor = UNIXSocket.pair
16
-
17
45
  @s_acceptor.puts registration_data(pid)
18
-
19
46
  @server.__CHILD__register_acceptor(@s_client_handler)
20
47
  end
21
48
 
@@ -23,91 +50,28 @@ module Zeus
23
50
  {type: 'registration', pid: pid, commands: [name, *aliases], description: description}.to_json
24
51
  end
25
52
 
26
- def descendent_acceptors
27
- self
28
- end
53
+ def accept_connection
54
+ terminal = @s_acceptor.recv_io # blocking
55
+ arguments = JSON.parse(@s_acceptor.readline.chomp)
29
56
 
30
- def print_error(io, error)
31
- io.puts "#{error.backtrace[0]}: #{error.message} (#{error.class})"
32
- error.backtrace[1..-1].each do |line|
33
- io.puts "\tfrom #{line}"
34
- end
57
+ [terminal, arguments]
35
58
  end
36
59
 
37
- def run_as_error(e)
38
- register_with_client_handler(Process.pid)
39
- Zeus.ui.as_zeus "starting error-state acceptor `#{@name}`"
40
-
41
- Thread.new do
42
- loop do
43
- terminal = @s_acceptor.recv_io
44
- _ = @s_acceptor.readline
45
- @s_acceptor << 0 << "\n"
46
- print_error(terminal, e)
47
- terminal.close
48
- end
49
- end
60
+ def process_type
61
+ "acceptor"
50
62
  end
51
63
 
52
- def run
53
- pid = fork {
54
- $0 = "zeus acceptor: #{@name}"
55
- pid = Process.pid
56
-
57
- register_with_client_handler(pid)
58
-
59
- @server.__CHILD__pid_has_ppid(pid, Process.ppid)
60
-
61
- Zeus.ui.as_zeus "starting acceptor `#{@name}`"
62
- trap("INT") {
63
- Zeus.ui.as_zeus "killing acceptor `#{@name}`"
64
- exit 0
65
- }
66
-
67
- # Apparently threads don't continue in forks.
68
- Thread.new {
69
- $LOADED_FEATURES.each do |f|
70
- @server.__CHILD__pid_has_feature(pid, f)
71
- end
72
- }
73
-
74
- loop do
75
- prefork_action!
76
- terminal = @s_acceptor.recv_io
77
- arguments = JSON.parse(@s_acceptor.readline.chomp)
78
- child = fork do
79
- $0 = "zeus runner: #{@name}"
80
- postfork_action!
81
- @s_acceptor << $$ << "\n"
82
- $stdin.reopen(terminal)
83
- $stdout.reopen(terminal)
84
- $stderr.reopen(terminal)
85
- ARGV.replace(arguments)
86
-
87
- @action.call
88
- end
89
- Process.detach(child)
90
- terminal.close
91
- end
92
-
93
- }
94
- currpid = Process.pid
95
- at_exit {
96
- if Process.pid == currpid
97
- Process.kill(9, pid) rescue nil
98
- end
99
- }
100
- pid
101
- end
102
64
 
65
+ # these two methods should be part of the configuration DSL.
66
+ # They're here for now, but I want them out.
103
67
  def prefork_action! # TODO : refactor
104
- ActiveRecord::Base.clear_all_connections!
68
+ ActiveRecord::Base.clear_all_connections! rescue nil
105
69
  end
106
70
 
107
71
  def postfork_action! # TODO :refactor
108
- ActiveRecord::Base.establish_connection
109
- ActiveSupport::DescendantsTracker.clear
110
- ActiveSupport::Dependencies.clear
72
+ ActiveRecord::Base.establish_connection rescue nil
73
+ ActiveSupport::DescendantsTracker.clear rescue nil
74
+ ActiveSupport::Dependencies.clear rescue nil
111
75
  end
112
76
 
113
77
  end
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+ require 'socket'
3
+
4
+ # See Zeus::Server::ClientHandler for relevant documentation
5
+ module Zeus
6
+ class Server
7
+ module ErrorStateAcceptor
8
+ attr_accessor :error
9
+
10
+ def print_error(io, error = @error)
11
+ io.puts "#{error.backtrace[0]}: #{error.message} (#{error.class})"
12
+ error.backtrace[1..-1].each do |line|
13
+ io.puts "\tfrom #{line}"
14
+ end
15
+ end
16
+
17
+ def run
18
+ register_with_client_handler(Process.pid)
19
+ Zeus.ui.as_zeus "starting error-state acceptor `#{@name}`"
20
+
21
+ Thread.new do
22
+ loop do
23
+ terminal = @s_acceptor.recv_io
24
+ _ = @s_acceptor.readline
25
+ @s_acceptor << 0 << "\n"
26
+ print_error(terminal)
27
+ terminal.close
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+
@@ -4,7 +4,9 @@ module Zeus
4
4
 
5
5
  def datasource ; @sock ; end
6
6
  def on_datasource_event ; handle_message ; end
7
- def close_child_socket ; @__CHILD__sock.close ; end
7
+ # @__CHILD__sock is not closed here, as it's used by the master to respond
8
+ # for unbooted acceptors
9
+ def close_child_socket ; end
8
10
  def close_parent_socket ; @sock.close ; end
9
11
 
10
12
  def initialize
@@ -0,0 +1,71 @@
1
+ module Zeus
2
+ class Server
3
+ class ForkedProcess
4
+ HasNoChildren = Class.new(Exception)
5
+
6
+ attr_accessor :name
7
+ attr_reader :pid
8
+ def initialize(server)
9
+ @server = server
10
+ end
11
+
12
+ def notify_feature(feature)
13
+ @server.__CHILD__pid_has_feature(Process.pid, feature)
14
+ end
15
+
16
+ def descendent_acceptors
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def process_type
21
+ raise "NotImplementedError"
22
+ end
23
+
24
+ def setup_forked_process(close_parent_sockets)
25
+ @server.__CHILD__close_parent_sockets if close_parent_sockets
26
+ @server.__CHILD__pid_has_ppid(Process.pid, Process.ppid)
27
+
28
+ $0 = "zeus #{process_type}: #{@name}"
29
+
30
+ Zeus.ui.as_zeus("starting #{process_type} `#{@name}`")
31
+ trap("INT") {
32
+ Zeus.ui.as_zeus("killing #{process_type} `#{@name}`")
33
+ exit 0
34
+ }
35
+
36
+ Thread.new {
37
+ $LOADED_FEATURES.each { |f| notify_feature(f) }
38
+ }
39
+ end
40
+
41
+ def kill_pid_on_exit(pid)
42
+ currpid = Process.pid
43
+ at_exit { Process.kill(9, pid) if Process.pid == currpid rescue nil }
44
+ end
45
+
46
+ def runloop!
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def before_setup
51
+ end
52
+
53
+ def after_setup
54
+ end
55
+
56
+ def run(close_parent_sockets = false)
57
+ @pid = fork {
58
+ before_setup
59
+ setup_forked_process(close_parent_sockets)
60
+ after_setup
61
+ runloop!
62
+ }
63
+ kill_pid_on_exit(@pid)
64
+ @pid
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+ end
71
+
@@ -2,40 +2,68 @@ module Zeus
2
2
  class Server
3
3
  # NONE of the code in the module is run in the master process,
4
4
  # so every communication to the master must be done with IPC.
5
- class Stage
6
- HasNoChildren = Class.new(Exception)
5
+ class Stage < ForkedProcess
6
+ attr_accessor :stages, :actions
7
7
 
8
- attr_accessor :name, :stages, :actions
9
- attr_reader :pid
10
- def initialize(server)
11
- @server = server
8
+ def descendent_acceptors
9
+ @stages.map(&:descendent_acceptors).flatten
12
10
  end
13
11
 
14
- def notify_feature(feature)
15
- @server.__CHILD__pid_has_feature(Process.pid, feature)
12
+
13
+ def after_setup
14
+ begin
15
+ @actions.each(&:call)
16
+ rescue => e
17
+ handle_load_error(e)
18
+ end
19
+
20
+ @pids = {}
21
+ @stages.each do |stage|
22
+ @pids[stage.run] = stage
23
+ end
16
24
  end
17
25
 
18
- def descendent_acceptors
19
- @stages.map(&:descendent_acceptors).flatten
26
+ def runloop!
27
+ loop do
28
+ begin
29
+ pid = Process.wait
30
+ rescue Errno::ECHILD
31
+ raise HasNoChildren.new("Stage `#{@name}` - All terminal nodes must be acceptors")
32
+ end
33
+ stage = @pids[pid]
34
+ @pids[stage.run] = stage
35
+ end
20
36
  end
21
37
 
38
+
39
+ private
40
+
22
41
  def register_acceptors_as_errors(e)
23
42
  descendent_acceptors.each do |acc|
24
- acc.run_as_error(e)
43
+ acc = acc.extend(AcceptorErrorState)
44
+ acc.error = e
45
+ acc.run
25
46
  end
26
47
  end
27
48
 
49
+ def process_type
50
+ "spawner"
51
+ end
28
52
 
29
- def handle_load_error(e)
53
+ def full_path_of_file_from_error(e)
30
54
  errored_file = e.backtrace[0].scan(/(.+?):\d+:in/)[0][0]
31
55
 
32
56
  # handle relative paths
33
57
  unless errored_file =~ /^\//
34
58
  errored_file = File.expand_path(errored_file, Dir.pwd)
35
59
  end
60
+ end
61
+
62
+ def handle_load_error(e)
63
+ errored_file = full_path_of_file_from_error(e)
36
64
 
37
- register_acceptors_as_errors(e)
38
65
  # register all the decendent acceptors as stubs with errors
66
+ register_acceptors_as_errors(e)
39
67
 
40
68
  notify_feature(errored_file)
41
69
  $LOADED_FEATURES.each { |f| notify_feature(f) }
@@ -45,51 +73,6 @@ module Zeus
45
73
  sleep
46
74
  end
47
75
 
48
- def run(close_parent_sockets = false)
49
- @pid = fork {
50
- # This is only passed to the top-level stage, from Server#run, not sub-stages.
51
- @server.__CHILD__close_parent_sockets if close_parent_sockets
52
-
53
- $0 = "zeus spawner: #{@name}"
54
- @server.__CHILD__pid_has_ppid(Process.pid, Process.ppid)
55
-
56
- Zeus.ui.as_zeus("starting spawner `#{@name}`")
57
- trap("INT") {
58
- Zeus.ui.as_zeus("killing spawner `#{@name}`")
59
- exit 0
60
- }
61
-
62
- begin
63
- @actions.each(&:call)
64
- rescue => e
65
- handle_load_error(e)
66
- end
67
-
68
- pids = {}
69
- @stages.each do |stage|
70
- pids[stage.run] = stage
71
- end
72
-
73
- Thread.new {
74
- $LOADED_FEATURES.each { |f| notify_feature(f) }
75
- }
76
-
77
- loop do
78
- begin
79
- pid = Process.wait
80
- rescue Errno::ECHILD
81
- raise HasNoChildren.new("Stage `#{@name}` - All terminal nodes must be acceptors")
82
- end
83
- stage = pids[pid]
84
- pids[stage.run] = stage
85
- end
86
- }
87
- currpid = Process.pid
88
- at_exit { Process.kill(9, @pid) if Process.pid == currpid rescue nil }
89
- @pid
90
- end
91
-
92
76
  end
93
-
94
77
  end
95
78
  end
data/lib/zeus/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.2.5"
2
+ VERSION = "0.2.6"
3
3
  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.5
4
+ version: 0.2.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -35,10 +35,12 @@ files:
35
35
  - lib/zeus/dsl.rb
36
36
  - lib/zeus/server.rb
37
37
  - lib/zeus/server/acceptor.rb
38
+ - lib/zeus/server/acceptor_error_state.rb
38
39
  - lib/zeus/server/acceptor_registration_monitor.rb
39
40
  - lib/zeus/server/client_handler.rb
40
41
  - lib/zeus/server/file_monitor.rb
41
42
  - lib/zeus/server/file_monitor/fsevent.rb
43
+ - lib/zeus/server/forked_process.rb
42
44
  - lib/zeus/server/process_tree.rb
43
45
  - lib/zeus/server/process_tree_monitor.rb
44
46
  - lib/zeus/server/stage.rb