zeus 0.4.5 → 0.4.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/README.md +2 -1
- data/Rakefile +9 -3
- data/bin/zeus +4 -0
- data/docs/acceptor_registration.md +14 -0
- data/docs/client_server_handshake.md +25 -0
- data/lib/zeus.rb +19 -7
- data/lib/zeus/client.rb +36 -36
- data/lib/zeus/client/winsize.rb +28 -0
- data/lib/zeus/error_printer.rb +16 -0
- data/lib/zeus/plan.rb +18 -0
- data/lib/zeus/plan/acceptor.rb +38 -0
- data/lib/zeus/plan/node.rb +66 -0
- data/lib/zeus/plan/stage.rb +50 -0
- data/lib/zeus/server.rb +17 -17
- data/lib/zeus/server/acceptor.rb +7 -24
- data/lib/zeus/server/acceptor_registration_monitor.rb +2 -10
- data/lib/zeus/server/client_handler.rb +14 -37
- data/lib/zeus/server/command_runner.rb +36 -16
- data/lib/zeus/server/file_monitor.rb +2 -1
- data/lib/zeus/server/file_monitor/fsevent.rb +6 -1
- data/lib/zeus/server/stage.rb +49 -40
- data/lib/zeus/server/stage/error_state.rb +42 -0
- data/lib/zeus/server/stage/feature_notifier.rb +38 -0
- data/lib/zeus/version.rb +1 -1
- data/spec/error_printer_spec.rb +27 -0
- data/spec/integration_spec.rb +31 -12
- metadata +14 -4
- data/lib/zeus/dsl.rb +0 -154
- data/lib/zeus/server/forked_process.rb +0 -98
data/lib/zeus/server.rb
CHANGED
@@ -2,22 +2,21 @@ require 'json'
|
|
2
2
|
require 'socket'
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
|
+
require 'zeus/server/stage'
|
6
|
+
require 'zeus/server/acceptor'
|
7
|
+
require 'zeus/server/file_monitor'
|
8
|
+
require 'zeus/server/load_tracking'
|
9
|
+
require 'zeus/server/client_handler'
|
10
|
+
require 'zeus/server/command_runner'
|
11
|
+
require 'zeus/server/process_tree_monitor'
|
12
|
+
require 'zeus/server/acceptor_registration_monitor'
|
13
|
+
|
5
14
|
module Zeus
|
6
15
|
class Server
|
7
16
|
extend Forwardable
|
8
17
|
|
9
|
-
autoload :Stage, 'zeus/server/stage'
|
10
|
-
autoload :Acceptor, 'zeus/server/acceptor'
|
11
|
-
autoload :FileMonitor, 'zeus/server/file_monitor'
|
12
|
-
autoload :LoadTracking, 'zeus/server/load_tracking'
|
13
|
-
autoload :ForkedProcess, 'zeus/server/forked_process'
|
14
|
-
autoload :ClientHandler, 'zeus/server/client_handler'
|
15
|
-
autoload :CommandRunner, 'zeus/server/command_runner'
|
16
|
-
autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
|
17
|
-
autoload :AcceptorRegistrationMonitor, 'zeus/server/acceptor_registration_monitor'
|
18
|
-
|
19
18
|
def self.define!(&b)
|
20
|
-
@@definition = Zeus::
|
19
|
+
@@definition = Zeus::Plan::Evaluator.new.instance_eval(&b)
|
21
20
|
end
|
22
21
|
|
23
22
|
def self.acceptors
|
@@ -44,16 +43,12 @@ module Zeus
|
|
44
43
|
def run
|
45
44
|
$0 = "zeus master"
|
46
45
|
trap("TERM") { exit 0 }
|
47
|
-
trap("INT") {
|
48
|
-
puts "\n\x1b[31mExiting\x1b[0m"
|
49
|
-
exit 0
|
50
|
-
}
|
51
|
-
|
46
|
+
trap("INT") { puts "\n\x1b[31mExiting\x1b[0m" ; exit }
|
52
47
|
LoadTracking.server = self
|
53
48
|
|
54
49
|
@plan.run(true) # boot the actual app
|
55
50
|
master = Process.pid
|
56
|
-
at_exit {
|
51
|
+
at_exit { cleanup_all_children if Process.pid == master }
|
57
52
|
monitors.each(&:close_child_socket)
|
58
53
|
|
59
54
|
runloop!
|
@@ -61,6 +56,11 @@ module Zeus
|
|
61
56
|
File.unlink(Zeus::SOCKET_NAME)
|
62
57
|
end
|
63
58
|
|
59
|
+
def cleanup_all_children
|
60
|
+
@process_tree_monitor.kill_all_nodes
|
61
|
+
@file_monitor.kill_wrapper
|
62
|
+
end
|
63
|
+
|
64
64
|
def add_extra_feature(full_expanded_path)
|
65
65
|
$extra_loaded_features ||= []
|
66
66
|
$extra_loaded_features << full_expanded_path
|
data/lib/zeus/server/acceptor.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'socket'
|
3
3
|
|
4
|
-
# See Zeus::Server::ClientHandler for relevant documentation
|
5
4
|
module Zeus
|
6
5
|
class Server
|
7
6
|
class Acceptor
|
@@ -17,8 +16,7 @@ module Zeus
|
|
17
16
|
def run
|
18
17
|
register_with_client_handler(Process.pid)
|
19
18
|
Zeus.ui.info("starting #{process_type} `#{@name}`")
|
20
|
-
|
21
|
-
thread_with_backtrace_on_error { runloop! }
|
19
|
+
Zeus.thread_with_backtrace_on_error { runloop! }
|
22
20
|
end
|
23
21
|
|
24
22
|
private
|
@@ -30,6 +28,7 @@ module Zeus
|
|
30
28
|
def register_with_client_handler(pid)
|
31
29
|
@s_client_handler, @s_acceptor = UNIXSocket.pair
|
32
30
|
@s_acceptor.puts registration_data(pid)
|
31
|
+
at_exit { @s_client_handler.close ; @s_acceptor.close }
|
33
32
|
@server.__CHILD__register_acceptor(@s_client_handler)
|
34
33
|
end
|
35
34
|
|
@@ -39,36 +38,20 @@ module Zeus
|
|
39
38
|
|
40
39
|
def accept_connection
|
41
40
|
terminal = @s_acceptor.recv_io # blocking
|
41
|
+
exit_status_socket = @s_acceptor.recv_io
|
42
42
|
arguments = JSON.parse(@s_acceptor.readline.chomp)
|
43
43
|
|
44
|
-
[terminal, arguments]
|
44
|
+
[terminal, exit_status_socket, arguments]
|
45
45
|
end
|
46
46
|
|
47
47
|
def process_type
|
48
48
|
"acceptor"
|
49
49
|
end
|
50
50
|
|
51
|
-
def print_error(io, error)
|
52
|
-
io.puts "#{error.backtrace[0]}: #{error.message} (#{error.class})"
|
53
|
-
error.backtrace[1..-1].each do |line|
|
54
|
-
io.puts "\tfrom #{line}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def thread_with_backtrace_on_error(&b)
|
59
|
-
Thread.new {
|
60
|
-
begin
|
61
|
-
b.call
|
62
|
-
rescue => e
|
63
|
-
print_error($stdout, e)
|
64
|
-
end
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
51
|
def runloop!
|
69
52
|
loop do
|
70
|
-
terminal, arguments = accept_connection # blocking
|
71
|
-
command_runner.run(terminal, arguments)
|
53
|
+
terminal, exit_status_socket, arguments = accept_connection # blocking
|
54
|
+
command_runner.run(terminal, exit_status_socket, arguments)
|
72
55
|
end
|
73
56
|
end
|
74
57
|
|
@@ -85,7 +68,7 @@ module Zeus
|
|
85
68
|
terminal = @s_acceptor.recv_io
|
86
69
|
_ = @s_acceptor.readline
|
87
70
|
@s_acceptor << NOT_A_PID << "\n"
|
88
|
-
|
71
|
+
ErrorPrinter.new(@error).write_to(terminal)
|
89
72
|
terminal.close
|
90
73
|
end
|
91
74
|
end
|
@@ -5,8 +5,8 @@ module Zeus
|
|
5
5
|
def datasource ; @sock ; end
|
6
6
|
def on_datasource_event ; handle_message ; end
|
7
7
|
# @__CHILD__sock is not closed here, as it's used by the master to respond
|
8
|
-
#
|
9
|
-
def close_child_socket ; end
|
8
|
+
# on behalf of unbooted acceptors
|
9
|
+
def close_child_socket ; end
|
10
10
|
def close_parent_socket ; @sock.close ; end
|
11
11
|
|
12
12
|
def initialize
|
@@ -26,7 +26,6 @@ module Zeus
|
|
26
26
|
case type
|
27
27
|
when 'wait' ; handle_wait(io, data)
|
28
28
|
when 'registration' ; handle_registration(io, data)
|
29
|
-
when 'deregistration' ; handle_deregistration(io, data)
|
30
29
|
else raise "invalid message"
|
31
30
|
end
|
32
31
|
end
|
@@ -37,11 +36,6 @@ module Zeus
|
|
37
36
|
@pings[command] << io
|
38
37
|
end
|
39
38
|
|
40
|
-
def handle_deregistration(io, data)
|
41
|
-
pid = data['pid'].to_i
|
42
|
-
@acceptors.reject!{|acc|acc.pid == pid}
|
43
|
-
end
|
44
|
-
|
45
39
|
def handle_registration(io, data)
|
46
40
|
pid = data['pid'].to_i
|
47
41
|
commands = data['commands']
|
@@ -62,7 +56,6 @@ module Zeus
|
|
62
56
|
end
|
63
57
|
end
|
64
58
|
|
65
|
-
|
66
59
|
module ChildProcessApi
|
67
60
|
|
68
61
|
def __CHILD__find_acceptor_for_command(command)
|
@@ -78,6 +71,5 @@ module Zeus
|
|
78
71
|
end ; include ChildProcessApi
|
79
72
|
|
80
73
|
end
|
81
|
-
|
82
74
|
end
|
83
75
|
end
|
@@ -1,28 +1,9 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
+
# This class is a little confusing. See the docs/ directory for guidance.
|
4
5
|
module Zeus
|
5
6
|
class Server
|
6
|
-
# The model here is kind of convoluted, so here's an explanation of what's
|
7
|
-
# happening with all these sockets:
|
8
|
-
#
|
9
|
-
# #### Acceptor Registration
|
10
|
-
# 1. ClientHandler creates a socketpair for Acceptor registration (S_REG)
|
11
|
-
# 2. When an acceptor is spawned, it:
|
12
|
-
# 1. Creates a new socketpair for communication with clienthandler (S_ACC)
|
13
|
-
# 2. Sends one side of S_ACC over S_REG to clienthandler.
|
14
|
-
# 3. Sends a JSON-encoded hash of `pid`, `commands`, and `description`. over S_REG.
|
15
|
-
# 3. ClientHandler received first the IO and then the JSON hash, and stores them for later reference.
|
16
|
-
#
|
17
|
-
# #### Running a command
|
18
|
-
# 1. ClientHandler has a UNIXServer (SVR) listening.
|
19
|
-
# 2. ClientHandler has a socketpair with the acceptor referenced by the command (see Registration) (S_ACC)
|
20
|
-
# 3. When clienthandler received a connection (S_CLI) on SVR:
|
21
|
-
# 1. ClientHandler sends S_CLI over S_ACC, so the acceptor can communicate with the server's client.
|
22
|
-
# 2. ClientHandler sends a JSON-encoded array of `arguments` over S_ACC
|
23
|
-
# 3. Acceptor sends the newly-forked worker PID over S_ACC to clienthandler.
|
24
|
-
# 4. ClientHandler forwards the pid to the client over S_CLI.
|
25
|
-
#
|
26
7
|
class ClientHandler
|
27
8
|
def datasource ; @listener ; end
|
28
9
|
def on_datasource_event ; handle_server_connection ; end
|
@@ -43,20 +24,15 @@ module Zeus
|
|
43
24
|
|
44
25
|
private
|
45
26
|
|
46
|
-
#
|
47
|
-
# 1 ----------> | {command: String, arguments: [String]}
|
48
|
-
# 2 ----------> | Terminal IO
|
49
|
-
# 3 -----------> | Terminal IO
|
50
|
-
# 4 -----------> | Arguments (json array)
|
51
|
-
# 5 <----------- | pid
|
52
|
-
# 6 <--------- | pid
|
27
|
+
# See docs/client_server_handshake.md for details
|
53
28
|
def handle_server_connection
|
54
29
|
s_client = @listener.accept
|
55
30
|
|
56
|
-
data = JSON.parse(s_client.readline.chomp)
|
31
|
+
data = JSON.parse(s_client.readline.chomp)
|
57
32
|
command, arguments = data.values_at('command', 'arguments')
|
58
33
|
|
59
|
-
client_terminal = s_client.recv_io
|
34
|
+
client_terminal = s_client.recv_io
|
35
|
+
exit_status_socket = s_client.recv_io
|
60
36
|
|
61
37
|
Thread.new {
|
62
38
|
# This is a little ugly. Gist: Try to handshake the client to the acceptor.
|
@@ -64,7 +40,7 @@ module Zeus
|
|
64
40
|
# REATTEMPT_HANDSHAKE. We catch that exit code and try once more.
|
65
41
|
begin
|
66
42
|
loop do
|
67
|
-
pid = fork { handshake_client_to_acceptor(s_client, command, arguments, client_terminal) ; exit }
|
43
|
+
pid = fork { handshake_client_to_acceptor(s_client, command, arguments, client_terminal, exit_status_socket) ; exit }
|
68
44
|
Process.wait(pid)
|
69
45
|
break if $?.exitstatus != REATTEMPT_HANDSHAKE
|
70
46
|
end
|
@@ -75,22 +51,22 @@ module Zeus
|
|
75
51
|
}
|
76
52
|
end
|
77
53
|
|
78
|
-
def handshake_client_to_acceptor(s_client, command, arguments, client_terminal)
|
54
|
+
def handshake_client_to_acceptor(s_client, command, arguments, client_terminal, exit_status_socket)
|
79
55
|
unless @acceptor_commands.include?(command.to_s)
|
80
56
|
msg = "no such command `#{command}`."
|
81
57
|
return exit_with_message(s_client, client_terminal, msg)
|
82
58
|
end
|
83
59
|
|
84
|
-
unless acceptor = send_io_to_acceptor(client_terminal, command)
|
60
|
+
unless acceptor = send_io_to_acceptor(client_terminal, exit_status_socket, command)
|
85
61
|
wait_for_acceptor(s_client, client_terminal, command)
|
86
62
|
exit REATTEMPT_HANDSHAKE
|
87
63
|
end
|
88
64
|
|
89
65
|
Zeus.ui.info "accepting connection for #{command}"
|
90
66
|
|
91
|
-
acceptor.socket.puts arguments.to_json
|
92
|
-
pid = acceptor.socket.readline.chomp.to_i
|
93
|
-
s_client.puts pid
|
67
|
+
acceptor.socket.puts arguments.to_json
|
68
|
+
pid = acceptor.socket.readline.chomp.to_i
|
69
|
+
s_client.puts pid
|
94
70
|
s_client.close
|
95
71
|
end
|
96
72
|
|
@@ -113,17 +89,18 @@ module Zeus
|
|
113
89
|
s.readline # wait until acceptor is booted
|
114
90
|
end
|
115
91
|
|
116
|
-
def send_io_to_acceptor(io, command)
|
92
|
+
def send_io_to_acceptor(io, io2, command)
|
117
93
|
return false unless acceptor = @server.__CHILD__find_acceptor_for_command(command)
|
118
94
|
return false unless usock = UNIXSocket.for_fd(acceptor.socket.fileno)
|
119
95
|
usock.send_io(io)
|
96
|
+
usock.send_io(io2)
|
120
97
|
io.close
|
98
|
+
io2.close
|
121
99
|
return acceptor
|
122
100
|
rescue Errno::EPIPE
|
123
101
|
return false
|
124
102
|
end
|
125
103
|
|
126
|
-
|
127
104
|
end
|
128
105
|
end
|
129
106
|
end
|
@@ -8,36 +8,56 @@ module Zeus
|
|
8
8
|
@s_acceptor = s_acceptor
|
9
9
|
end
|
10
10
|
|
11
|
-
def run(terminal, arguments)
|
12
|
-
child = fork { _run(terminal, arguments) }
|
11
|
+
def run(terminal, exit_status_socket, arguments)
|
12
|
+
child = fork { _run(terminal, exit_status_socket, arguments) }
|
13
13
|
terminal.close
|
14
|
+
exit_status_socket.close
|
14
15
|
Process.detach(child)
|
15
16
|
child
|
16
17
|
end
|
17
18
|
|
18
19
|
private
|
19
20
|
|
20
|
-
def _run(terminal, arguments)
|
21
|
+
def _run(terminal, exit_status_socket, arguments)
|
21
22
|
$0 = "zeus runner: #{@name}"
|
23
|
+
@exit_status_socket = exit_status_socket
|
24
|
+
@terminal = terminal
|
22
25
|
Process.setsid
|
23
26
|
reconnect_activerecord!
|
24
27
|
@s_acceptor << $$ << "\n"
|
25
|
-
|
26
|
-
$stdout.reopen(terminal)
|
27
|
-
$stderr.reopen(terminal)
|
28
|
+
reopen_streams(terminal, terminal, terminal)
|
28
29
|
ARGV.replace(arguments)
|
29
30
|
|
31
|
+
return_process_exit_status
|
32
|
+
|
33
|
+
run_action
|
34
|
+
end
|
35
|
+
|
36
|
+
def return_process_exit_status
|
37
|
+
at_exit do
|
38
|
+
if $!.nil? || $!.is_a?(SystemExit) && $!.success?
|
39
|
+
@exit_status_socket.puts(0)
|
40
|
+
else
|
41
|
+
code = $!.is_a?(SystemExit) ? $!.status : 1
|
42
|
+
@exit_status_socket.puts(code)
|
43
|
+
end
|
44
|
+
|
45
|
+
@exit_status_socket.close
|
46
|
+
@terminal.close
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_action
|
30
51
|
@action.call
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
$
|
39
|
-
|
40
|
-
exit 0
|
52
|
+
rescue StandardError => error
|
53
|
+
ErrorPrinter.new(error).write_to($stderr)
|
54
|
+
raise
|
55
|
+
end
|
56
|
+
|
57
|
+
def reopen_streams(i, o, e)
|
58
|
+
$stdin.reopen(i)
|
59
|
+
$stdout.reopen(o)
|
60
|
+
$stderr.reopen(e)
|
41
61
|
end
|
42
62
|
|
43
63
|
def reconnect_activerecord!
|
@@ -14,7 +14,7 @@ module Zeus
|
|
14
14
|
|
15
15
|
def initialize(&change_callback)
|
16
16
|
@change_callback = change_callback
|
17
|
-
@io_in, @io_out,
|
17
|
+
@io_in, @io_out, @wrapper_thread = open_wrapper
|
18
18
|
@givenpath_to_realpath = {}
|
19
19
|
@realpath_to_givenpath = {}
|
20
20
|
@buffer = ""
|
@@ -38,6 +38,11 @@ module Zeus
|
|
38
38
|
true
|
39
39
|
end
|
40
40
|
|
41
|
+
def kill_wrapper
|
42
|
+
Process.kill(9, @wrapper_thread.pid)
|
43
|
+
rescue Errno::ESRCH # already dead. SIGINT to master causes this often.
|
44
|
+
end
|
45
|
+
|
41
46
|
private
|
42
47
|
|
43
48
|
def realpath(file)
|
data/lib/zeus/server/stage.rb
CHANGED
@@ -1,31 +1,63 @@
|
|
1
|
+
require 'zeus/server/stage/error_state'
|
2
|
+
require 'zeus/server/stage/feature_notifier'
|
3
|
+
|
1
4
|
module Zeus
|
2
5
|
class Server
|
3
6
|
# NONE of the code in the module is run in the master process,
|
4
7
|
# so every communication to the master must be done with IPC.
|
5
|
-
class Stage
|
6
|
-
|
8
|
+
class Stage
|
9
|
+
|
10
|
+
attr_accessor :name, :stages, :actions
|
11
|
+
def initialize(server)
|
12
|
+
@server = server
|
13
|
+
end
|
7
14
|
|
8
15
|
def descendent_acceptors
|
9
16
|
@stages.map(&:descendent_acceptors).flatten
|
10
17
|
end
|
11
18
|
|
19
|
+
def run(close_parent_sockets = false)
|
20
|
+
@pid = fork {
|
21
|
+
setup_fork(close_parent_sockets)
|
22
|
+
run_actions
|
23
|
+
feature_notifier.notify_new_features
|
24
|
+
start_child_stages
|
25
|
+
handle_child_exit_loop!
|
26
|
+
}
|
27
|
+
end
|
12
28
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
29
|
+
private
|
30
|
+
|
31
|
+
def setup_fork(close_parent_sockets)
|
32
|
+
$0 = "zeus #{process_type}: #{@name}"
|
33
|
+
@server.__CHILD__close_parent_sockets if close_parent_sockets
|
34
|
+
notify_started
|
35
|
+
trap("INT") { exit }
|
36
|
+
trap("TERM") { notify_terminated ; exit }
|
37
|
+
ActiveRecord::Base.clear_all_connections! rescue nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def feature_notifier
|
41
|
+
FeatureNotifier.new(@server, @name)
|
19
42
|
end
|
20
43
|
|
21
|
-
def
|
44
|
+
def start_child_stages
|
22
45
|
@pids = {}
|
23
46
|
@stages.each do |stage|
|
24
47
|
@pids[stage.run] = stage
|
25
48
|
end
|
26
49
|
end
|
27
50
|
|
28
|
-
def
|
51
|
+
def run_actions
|
52
|
+
begin
|
53
|
+
@actions.each(&:call)
|
54
|
+
rescue => e
|
55
|
+
extend(ErrorState)
|
56
|
+
handle_load_error(e)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_child_exit_loop!
|
29
61
|
loop do
|
30
62
|
begin
|
31
63
|
pid = Process.wait
|
@@ -37,41 +69,18 @@ module Zeus
|
|
37
69
|
end
|
38
70
|
end
|
39
71
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
descendent_acceptors.each do |acc|
|
44
|
-
acc = acc.extend(Acceptor::ErrorState)
|
45
|
-
acc.error = e
|
46
|
-
acc.run
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def process_type
|
51
|
-
"spawner"
|
72
|
+
def notify_started
|
73
|
+
@server.__CHILD__stage_starting_with_pid(@name, Process.pid)
|
74
|
+
Zeus.ui.info("starting #{process_type} `#{@name}`")
|
52
75
|
end
|
53
76
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
# handle relative paths
|
58
|
-
unless errored_file =~ /^\//
|
59
|
-
errored_file = File.expand_path(errored_file, Dir.pwd)
|
60
|
-
end
|
77
|
+
def notify_terminated
|
78
|
+
Zeus.ui.info("killing #{process_type} `#{@name}`")
|
61
79
|
end
|
62
80
|
|
63
|
-
def handle_load_error(e)
|
64
|
-
errored_file = full_path_of_file_from_error(e)
|
65
81
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
notify_feature(errored_file)
|
70
|
-
$LOADED_FEATURES.each { |f| notify_feature(f) }
|
71
|
-
|
72
|
-
# we do not need to do anything. We wait, until a dependency changes.
|
73
|
-
# At that point, we get killed and restarted.
|
74
|
-
sleep
|
82
|
+
def process_type
|
83
|
+
"spawner"
|
75
84
|
end
|
76
85
|
|
77
86
|
end
|