zeus 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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