sanford 0.8.0 → 0.9.0
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.
- checksums.yaml +7 -0
- data/Gemfile +1 -0
- data/lib/sanford/cli.rb +1 -270
- data/lib/sanford/host.rb +12 -14
- data/lib/sanford/host_data.rb +7 -7
- data/lib/sanford/manager.rb +275 -0
- data/lib/sanford/runner.rb +39 -32
- data/lib/sanford/server.rb +1 -0
- data/lib/sanford/service_handler.rb +54 -41
- data/lib/sanford/test_helpers.rb +19 -0
- data/lib/sanford/test_runner.rb +17 -12
- data/lib/sanford/version.rb +1 -1
- data/lib/sanford.rb +19 -17
- data/sanford.gemspec +5 -5
- data/test/support/service_handlers.rb +69 -43
- data/test/support/services.rb +2 -2
- data/test/support/simple_client.rb +2 -2
- data/test/system/request_handling_tests.rb +2 -2
- data/test/unit/error_handler_tests.rb +13 -12
- data/test/unit/host_data_tests.rb +26 -14
- data/test/unit/host_tests.rb +105 -16
- data/test/unit/manager_tests.rb +103 -50
- data/test/unit/runner_tests.rb +6 -3
- data/test/unit/sanford_tests.rb +70 -0
- data/test/unit/server_tests.rb +8 -6
- data/test/unit/service_handler_tests.rb +179 -68
- data/test/unit/worker_tests.rb +6 -5
- metadata +32 -74
- data/test/unit/config_tests.rb +0 -12
- data/test/unit/host_configuration_tests.rb +0 -37
- data/test/unit/hosts_tests.rb +0 -56
- data/test/unit/manager_pid_file_tests.rb +0 -60
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9e76d785a8dc8ecf399b69798c6671ea3cc59661
|
4
|
+
data.tar.gz: 9ffe75e092d9672b18f796e3702751f3b7b8f4dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8ab100e15c7772f82d3a5aebb52418c80af556596cc186b13a7559b99fcc02085fdb91c2504ef637957bedae4e661e9248ebf7ea25b3eed74fc0630816689651
|
7
|
+
data.tar.gz: fa68f9c299344ef106bd52c836c7d60d28c2c41bd6d46342c238e1a256d79528c651e0ae2df1a1419d97ddb1c5e7c18b88258468c2f2eda37a1cdb42d363b25c
|
data/Gemfile
CHANGED
data/lib/sanford/cli.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
require 'sanford'
|
2
|
-
require 'sanford/host_data'
|
3
|
-
require 'sanford/server'
|
4
2
|
require 'sanford/version'
|
5
3
|
|
6
4
|
module Sanford
|
@@ -26,6 +24,7 @@ module Sanford
|
|
26
24
|
@command = @cli.args.first || 'run'
|
27
25
|
Sanford.config.services_file = @cli.opts['config'] if @cli.opts['config']
|
28
26
|
Sanford.init
|
27
|
+
require 'sanford/manager'
|
29
28
|
Sanford::Manager.call(@command, @cli.opts)
|
30
29
|
rescue CLIRB::HelpExit
|
31
30
|
puts help
|
@@ -52,251 +51,6 @@ module Sanford
|
|
52
51
|
|
53
52
|
end
|
54
53
|
|
55
|
-
module Manager
|
56
|
-
|
57
|
-
def self.call(action, options = nil)
|
58
|
-
get_handler_class(action).new(options).tap{ |manager| manager.send(action) }
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.get_handler_class(action)
|
62
|
-
case action.to_sym
|
63
|
-
when :start, :run
|
64
|
-
ServerHandler
|
65
|
-
when :stop, :restart
|
66
|
-
SignalHandler
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
class ServerHandler
|
71
|
-
|
72
|
-
def initialize(options = nil)
|
73
|
-
@config = Config.new(options)
|
74
|
-
raise Sanford::NoHostError.new(@config.host_name) if !@config.found_host?
|
75
|
-
raise Sanford::InvalidHostError.new(@config.host) if !@config.has_listen_args?
|
76
|
-
@host = @config.host
|
77
|
-
@logger = @host.logger
|
78
|
-
|
79
|
-
@server_options = {}
|
80
|
-
# FUTURE allow passing through dat-tcp options (min/max workers)
|
81
|
-
# FUTURE merge in host options for verbose / keep_alive
|
82
|
-
|
83
|
-
@restart_cmd = RestartCmd.new(@config)
|
84
|
-
end
|
85
|
-
|
86
|
-
def run
|
87
|
-
self.run! false
|
88
|
-
end
|
89
|
-
|
90
|
-
def start
|
91
|
-
self.run! true
|
92
|
-
end
|
93
|
-
|
94
|
-
protected
|
95
|
-
|
96
|
-
def run!(daemonize = false)
|
97
|
-
daemonize!(true) if daemonize && !ENV['SANFORD_SKIP_DAEMONIZE']
|
98
|
-
Sanford::Server.new(@host, @server_options).tap do |server|
|
99
|
-
log "Starting #{@host.name} server..."
|
100
|
-
|
101
|
-
server.listen(*@config.listen_args)
|
102
|
-
$0 = ProcessName.new(@host.name, server.ip, server.port)
|
103
|
-
log "Listening on #{server.ip}:#{server.port}"
|
104
|
-
|
105
|
-
@config.pid_file.write
|
106
|
-
log "PID: #{Process.pid}"
|
107
|
-
|
108
|
-
Signal.trap("TERM"){ self.stop!(server) }
|
109
|
-
Signal.trap("INT"){ self.halt!(server) }
|
110
|
-
Signal.trap("USR2"){ self.restart!(server) }
|
111
|
-
|
112
|
-
server_thread = server.run(@config.client_file_descriptors)
|
113
|
-
log "#{@host.name} server started and ready."
|
114
|
-
server_thread.join
|
115
|
-
end
|
116
|
-
rescue RuntimeError => err
|
117
|
-
log "Error: #{err.message}"
|
118
|
-
log "#{@host.name} server never started."
|
119
|
-
ensure
|
120
|
-
@config.pid_file.remove
|
121
|
-
end
|
122
|
-
|
123
|
-
def restart!(server)
|
124
|
-
log "Restarting #{@host.name} server..."
|
125
|
-
server.pause
|
126
|
-
log "server paused"
|
127
|
-
|
128
|
-
ENV['SANFORD_HOST'] = @host.name
|
129
|
-
ENV['SANFORD_SERVER_FD'] = server.file_descriptor.to_s
|
130
|
-
ENV['SANFORD_CLIENT_FDS'] = server.client_file_descriptors.join(',')
|
131
|
-
ENV['SANFORD_SKIP_DAEMONIZE'] = 'yes'
|
132
|
-
|
133
|
-
log "calling exec ..."
|
134
|
-
Dir.chdir @restart_cmd.dir
|
135
|
-
Kernel.exec(*@restart_cmd.argv)
|
136
|
-
end
|
137
|
-
|
138
|
-
def stop!(server)
|
139
|
-
log "Stopping #{@host.name} server..."
|
140
|
-
server.stop
|
141
|
-
log "#{@host.name} server stopped."
|
142
|
-
end
|
143
|
-
|
144
|
-
def halt!(server)
|
145
|
-
log "Halting #{@host.name} server..."
|
146
|
-
server.halt false
|
147
|
-
log "#{@host.name} server halted."
|
148
|
-
end
|
149
|
-
|
150
|
-
# Full explanation: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
|
151
|
-
def daemonize!(no_chdir = false, no_close = false)
|
152
|
-
exit if fork
|
153
|
-
Process.setsid
|
154
|
-
exit if fork
|
155
|
-
Dir.chdir "/" unless no_chdir
|
156
|
-
if !no_close
|
157
|
-
null = File.open "/dev/null", 'w'
|
158
|
-
STDIN.reopen null
|
159
|
-
STDOUT.reopen null
|
160
|
-
STDERR.reopen null
|
161
|
-
end
|
162
|
-
return 0
|
163
|
-
end
|
164
|
-
|
165
|
-
def log(message)
|
166
|
-
@logger.info "[Sanford] #{message}"
|
167
|
-
end
|
168
|
-
|
169
|
-
end
|
170
|
-
|
171
|
-
class SignalHandler
|
172
|
-
|
173
|
-
def initialize(options = nil)
|
174
|
-
@config = Config.new(options)
|
175
|
-
raise Sanford::NoPIDError.new if !@config.pid
|
176
|
-
end
|
177
|
-
|
178
|
-
def stop
|
179
|
-
Process.kill("TERM", @config.pid)
|
180
|
-
end
|
181
|
-
|
182
|
-
def restart
|
183
|
-
Process.kill("USR2", @config.pid)
|
184
|
-
end
|
185
|
-
|
186
|
-
end
|
187
|
-
|
188
|
-
class Config
|
189
|
-
attr_reader :host_name, :host, :ip, :port, :file_descriptor
|
190
|
-
attr_reader :client_file_descriptors, :pid_file, :pid, :restart_dir
|
191
|
-
|
192
|
-
def initialize(opts = nil)
|
193
|
-
options = OpenStruct.new(opts || {})
|
194
|
-
@host_name = ENV['SANFORD_HOST'] || options.host
|
195
|
-
|
196
|
-
@host = @host_name ? Sanford.hosts.find(@host_name) : Sanford.hosts.first
|
197
|
-
@host ||= NullHost.new
|
198
|
-
|
199
|
-
@file_descriptor = ENV['SANFORD_SERVER_FD'] || options.file_descriptor
|
200
|
-
@file_descriptor = @file_descriptor.to_i if @file_descriptor
|
201
|
-
@ip = ENV['SANFORD_IP'] || options.ip || @host.ip
|
202
|
-
@port = ENV['SANFORD_PORT'] || options.port || @host.port
|
203
|
-
@port = @port.to_i if @port
|
204
|
-
|
205
|
-
client_fds_str = ENV['SANFORD_CLIENT_FDS'] || options.client_fds || ""
|
206
|
-
@client_file_descriptors = client_fds_str.split(',').map(&:to_i)
|
207
|
-
|
208
|
-
@pid_file = PIDFile.new(ENV['SANFORD_PID_FILE'] || options.pid_file || @host.pid_file)
|
209
|
-
@pid = options.pid || @pid_file.pid
|
210
|
-
|
211
|
-
@restart_dir = ENV['SANFORD_RESTART_DIR'] || options.restart_dir
|
212
|
-
end
|
213
|
-
|
214
|
-
def listen_args
|
215
|
-
@file_descriptor ? [ @file_descriptor ] : [ @ip, @port ]
|
216
|
-
end
|
217
|
-
|
218
|
-
def has_listen_args?
|
219
|
-
!!@file_descriptor || !!(@ip && @port)
|
220
|
-
end
|
221
|
-
|
222
|
-
def found_host?
|
223
|
-
!@host.kind_of?(NullHost)
|
224
|
-
end
|
225
|
-
|
226
|
-
end
|
227
|
-
|
228
|
-
class NullHost
|
229
|
-
[ :ip, :port, :pid_file ].each do |method_name|
|
230
|
-
define_method(method_name){ }
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
class ProcessName < String
|
235
|
-
def initialize(name, ip, port)
|
236
|
-
super "#{[ name, ip, port ].join('_')}"
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
class PIDFile
|
241
|
-
DEF_FILE = '/dev/null'
|
242
|
-
|
243
|
-
def initialize(path)
|
244
|
-
@path = (path || DEF_FILE).to_s
|
245
|
-
end
|
246
|
-
|
247
|
-
def pid
|
248
|
-
pid = File.read(@path).strip if File.exists?(@path)
|
249
|
-
pid.to_i if pid && !pid.empty?
|
250
|
-
end
|
251
|
-
|
252
|
-
def write
|
253
|
-
begin
|
254
|
-
File.open(@path, 'w'){|f| f.puts Process.pid }
|
255
|
-
rescue Errno::ENOENT => err
|
256
|
-
e = RuntimeError.new("Can't write pid to file `#{@path}`")
|
257
|
-
e.set_backtrace(err.backtrace)
|
258
|
-
raise e
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
def remove
|
263
|
-
FileUtils.rm_f(@path)
|
264
|
-
end
|
265
|
-
|
266
|
-
def to_s
|
267
|
-
@path
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
class RestartCmd
|
272
|
-
attr_reader :argv, :dir
|
273
|
-
|
274
|
-
def initialize(config = nil)
|
275
|
-
require 'rubygems'
|
276
|
-
config ||= OpenStruct.new
|
277
|
-
@dir = config.restart_dir || get_pwd
|
278
|
-
@argv = [ Gem.ruby, $0, ARGV.dup ].flatten
|
279
|
-
end
|
280
|
-
|
281
|
-
protected
|
282
|
-
|
283
|
-
# Trick from puma/unicorn. Favor PWD because it contains an unresolved
|
284
|
-
# symlink. This is useful when restarting after deploying; the original
|
285
|
-
# directory may be removed, but the symlink is pointing to a new
|
286
|
-
# directory.
|
287
|
-
def get_pwd
|
288
|
-
env_stat = File.stat(ENV['PWD'])
|
289
|
-
pwd_stat = File.stat(Dir.pwd)
|
290
|
-
if env_stat.ino == pwd_stat.ino && env_stat.dev == pwd_stat.dev
|
291
|
-
ENV['PWD']
|
292
|
-
else
|
293
|
-
Dir.pwd
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
54
|
class CLIRB # Version 1.0.0, https://github.com/redding/cli.rb
|
301
55
|
Error = Class.new(RuntimeError);
|
302
56
|
HelpExit = Class.new(RuntimeError); VersionExit = Class.new(RuntimeError)
|
@@ -352,27 +106,4 @@ module Sanford
|
|
352
106
|
end
|
353
107
|
end
|
354
108
|
|
355
|
-
class NoHostError < CLIRB::Error
|
356
|
-
def initialize(host_name)
|
357
|
-
message = if Sanford.hosts.empty?
|
358
|
-
"No hosts have been defined. Please define a host before trying to run Sanford."
|
359
|
-
else
|
360
|
-
"A host couldn't be found with the name #{host_name.inspect}. "
|
361
|
-
end
|
362
|
-
super message
|
363
|
-
end
|
364
|
-
end
|
365
|
-
|
366
|
-
class InvalidHostError < CLIRB::Error
|
367
|
-
def initialize(host)
|
368
|
-
super "A port must be configured or provided to run a server for '#{host}'"
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
class NoPIDError < CLIRB::Error
|
373
|
-
def initialize
|
374
|
-
super "A PID or PID file is required"
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
109
|
end
|
data/lib/sanford/host.rb
CHANGED
@@ -18,16 +18,16 @@ module Sanford
|
|
18
18
|
# effects (messing up someone's `initialize`). Thus, the `Configuration`
|
19
19
|
# is a separate class and not on the `Host` directly.
|
20
20
|
|
21
|
-
option :name,
|
22
|
-
option :ip,
|
23
|
-
option :port,
|
24
|
-
option :pid_file,
|
25
|
-
option :logger,
|
26
|
-
option :verbose_logging,
|
27
|
-
option :receives_keep_alive,
|
28
|
-
option :runner,
|
29
|
-
option :error_procs,
|
30
|
-
option :
|
21
|
+
option :name, String
|
22
|
+
option :ip, String, :default => '0.0.0.0'
|
23
|
+
option :port, Integer
|
24
|
+
option :pid_file, Pathname
|
25
|
+
option :logger, :default => proc{ Sanford.config.logger }
|
26
|
+
option :verbose_logging, :default => true
|
27
|
+
option :receives_keep_alive, :default => false
|
28
|
+
option :runner, :default => proc{ Sanford.config.runner }
|
29
|
+
option :error_procs, Array, :default => []
|
30
|
+
option :init_procs, Array, :default => []
|
31
31
|
|
32
32
|
def initialize(host)
|
33
33
|
self.name = host.class.to_s
|
@@ -88,7 +88,7 @@ module Sanford
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def init(&block)
|
91
|
-
self.configuration.
|
91
|
+
self.configuration.init_procs << block
|
92
92
|
end
|
93
93
|
|
94
94
|
def service_handler_ns(value = nil)
|
@@ -100,7 +100,7 @@ module Sanford
|
|
100
100
|
if @service_handler_ns && !(handler_class_name =~ /^::/)
|
101
101
|
handler_class_name = "#{@service_handler_ns}::#{handler_class_name}"
|
102
102
|
end
|
103
|
-
@services[service_name] = handler_class_name
|
103
|
+
@services[service_name.to_s] = handler_class_name
|
104
104
|
end
|
105
105
|
|
106
106
|
def inspect
|
@@ -109,8 +109,6 @@ module Sanford
|
|
109
109
|
"port=#{self.configuration.port.inspect}>"
|
110
110
|
end
|
111
111
|
|
112
|
-
protected
|
113
|
-
|
114
112
|
module ClassMethods
|
115
113
|
|
116
114
|
# the class level of a `Host` should just proxy it's methods down to it's
|
data/lib/sanford/host_data.rb
CHANGED
@@ -15,7 +15,7 @@ module Sanford
|
|
15
15
|
attr_reader :name, :logger, :verbose, :keep_alive, :runner, :error_procs
|
16
16
|
|
17
17
|
def initialize(service_host, options = nil)
|
18
|
-
service_host.configuration.
|
18
|
+
service_host.configuration.init_procs.each(&:call)
|
19
19
|
|
20
20
|
overrides = self.remove_nil_values(options || {})
|
21
21
|
configuration = service_host.configuration.to_hash.merge(overrides)
|
@@ -32,14 +32,14 @@ module Sanford
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def run(handler_class, request)
|
36
|
-
self.runner.new(handler_class, request, self.logger).run
|
37
|
-
end
|
38
|
-
|
39
35
|
def handler_class_for(service)
|
40
36
|
@handlers[service] || raise(Sanford::NotFoundError)
|
41
37
|
end
|
42
38
|
|
39
|
+
def run(handler_class, request)
|
40
|
+
self.runner.new(handler_class, request, self.logger).run
|
41
|
+
end
|
42
|
+
|
43
43
|
protected
|
44
44
|
|
45
45
|
def constantize(handler_class_name)
|
@@ -57,8 +57,8 @@ module Sanford
|
|
57
57
|
|
58
58
|
class NoHandlerClassError < RuntimeError
|
59
59
|
def initialize(handler_class_name)
|
60
|
-
super "Sanford couldn't find the service handler '#{handler_class_name}'.
|
61
|
-
|
60
|
+
super "Sanford couldn't find the service handler '#{handler_class_name}'."\
|
61
|
+
" It doesn't exist or hasn't been required in yet."
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'sanford/cli'
|
2
|
+
require 'sanford/server'
|
3
|
+
|
4
|
+
module Sanford
|
5
|
+
|
6
|
+
module Manager
|
7
|
+
|
8
|
+
def self.call(action, options = nil)
|
9
|
+
get_handler_class(action).new(options).tap{ |manager| manager.send(action) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.get_handler_class(action)
|
13
|
+
case action.to_sym
|
14
|
+
when :start, :run
|
15
|
+
ServerHandler
|
16
|
+
when :stop, :restart
|
17
|
+
SignalHandler
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Config
|
22
|
+
attr_reader :host_name, :host, :ip, :port, :pid, :pid_file, :restart_dir
|
23
|
+
attr_reader :file_descriptor, :client_file_descriptors
|
24
|
+
|
25
|
+
def initialize(opts = nil)
|
26
|
+
options = OpenStruct.new(opts || {})
|
27
|
+
@host_name = ENV['SANFORD_HOST'] || options.host
|
28
|
+
|
29
|
+
@host = @host_name ? Sanford.hosts.find(@host_name) : Sanford.hosts.first
|
30
|
+
@host ||= NullHost.new
|
31
|
+
|
32
|
+
@file_descriptor = ENV['SANFORD_SERVER_FD'] || options.file_descriptor
|
33
|
+
@file_descriptor = @file_descriptor.to_i if @file_descriptor
|
34
|
+
@ip = ENV['SANFORD_IP'] || options.ip || @host.ip
|
35
|
+
@port = ENV['SANFORD_PORT'] || options.port || @host.port
|
36
|
+
@port = @port.to_i if @port
|
37
|
+
|
38
|
+
client_fds_str = ENV['SANFORD_CLIENT_FDS'] || options.client_fds || ""
|
39
|
+
@client_file_descriptors = client_fds_str.split(',').map(&:to_i)
|
40
|
+
|
41
|
+
@pid_file = PIDFile.new(ENV['SANFORD_PID_FILE'] || options.pid_file || @host.pid_file)
|
42
|
+
@pid = options.pid || @pid_file.pid
|
43
|
+
|
44
|
+
@restart_dir = ENV['SANFORD_RESTART_DIR'] || options.restart_dir
|
45
|
+
end
|
46
|
+
|
47
|
+
def listen_args
|
48
|
+
@file_descriptor ? [ @file_descriptor ] : [ @ip, @port ]
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_listen_args?
|
52
|
+
!!@file_descriptor || !!(@ip && @port)
|
53
|
+
end
|
54
|
+
|
55
|
+
def found_host?
|
56
|
+
!@host.kind_of?(NullHost)
|
57
|
+
end
|
58
|
+
|
59
|
+
class NullHost
|
60
|
+
[ :ip, :port, :pid_file ].each do |method_name|
|
61
|
+
define_method(method_name){ }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class PIDFile
|
66
|
+
DEF_FILE = '/dev/null'
|
67
|
+
|
68
|
+
def initialize(path)
|
69
|
+
@path = (path || DEF_FILE).to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def pid
|
73
|
+
pid = File.read(@path).strip if File.exists?(@path)
|
74
|
+
pid.to_i if pid && !pid.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def write
|
78
|
+
begin
|
79
|
+
File.open(@path, 'w'){|f| f.puts Process.pid }
|
80
|
+
rescue Errno::ENOENT => err
|
81
|
+
e = RuntimeError.new("Can't write pid to file `#{@path}`")
|
82
|
+
e.set_backtrace(err.backtrace)
|
83
|
+
raise e
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def remove
|
88
|
+
FileUtils.rm_f(@path)
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
@path
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class ServerHandler
|
99
|
+
|
100
|
+
def initialize(options = nil)
|
101
|
+
@config = Config.new(options)
|
102
|
+
raise Sanford::NoHostError.new(@config.host_name) if !@config.found_host?
|
103
|
+
raise Sanford::InvalidHostError.new(@config.host) if !@config.has_listen_args?
|
104
|
+
@host = @config.host
|
105
|
+
@logger = @host.logger
|
106
|
+
|
107
|
+
@server_options = {}
|
108
|
+
# FUTURE allow passing through dat-tcp options (min/max workers)
|
109
|
+
# FUTURE merge in host options for verbose / keep_alive
|
110
|
+
|
111
|
+
@restart_cmd = RestartCmd.new(@config)
|
112
|
+
end
|
113
|
+
|
114
|
+
def run
|
115
|
+
self.run! false
|
116
|
+
end
|
117
|
+
|
118
|
+
def start
|
119
|
+
self.run! true
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
def run!(daemonize = false)
|
125
|
+
daemonize!(true) if daemonize && !ENV['SANFORD_SKIP_DAEMONIZE']
|
126
|
+
Sanford::Server.new(@host, @server_options).tap do |server|
|
127
|
+
log "Starting #{@host.name} server..."
|
128
|
+
|
129
|
+
server.listen(*@config.listen_args)
|
130
|
+
$0 = ProcessName.new(@host.name, server.ip, server.port)
|
131
|
+
log "Listening on #{server.ip}:#{server.port}"
|
132
|
+
|
133
|
+
@config.pid_file.write
|
134
|
+
log "PID: #{Process.pid}"
|
135
|
+
|
136
|
+
Signal.trap("TERM"){ self.stop!(server) }
|
137
|
+
Signal.trap("INT"){ self.halt!(server) }
|
138
|
+
Signal.trap("USR2"){ self.restart!(server) }
|
139
|
+
|
140
|
+
server_thread = server.run(@config.client_file_descriptors)
|
141
|
+
log "#{@host.name} server started and ready."
|
142
|
+
server_thread.join
|
143
|
+
end
|
144
|
+
rescue RuntimeError => err
|
145
|
+
log "Error: #{err.message}"
|
146
|
+
log "#{@host.name} server never started."
|
147
|
+
ensure
|
148
|
+
@config.pid_file.remove
|
149
|
+
end
|
150
|
+
|
151
|
+
def restart!(server)
|
152
|
+
log "Restarting #{@host.name} server..."
|
153
|
+
server.pause
|
154
|
+
log "server paused"
|
155
|
+
|
156
|
+
ENV['SANFORD_HOST'] = @host.name
|
157
|
+
ENV['SANFORD_SERVER_FD'] = server.file_descriptor.to_s
|
158
|
+
ENV['SANFORD_CLIENT_FDS'] = server.client_file_descriptors.join(',')
|
159
|
+
ENV['SANFORD_SKIP_DAEMONIZE'] = 'yes'
|
160
|
+
|
161
|
+
log "calling exec ..."
|
162
|
+
Dir.chdir @restart_cmd.dir
|
163
|
+
Kernel.exec(*@restart_cmd.argv)
|
164
|
+
end
|
165
|
+
|
166
|
+
def stop!(server)
|
167
|
+
log "Stopping #{@host.name} server..."
|
168
|
+
server.stop
|
169
|
+
log "#{@host.name} server stopped."
|
170
|
+
end
|
171
|
+
|
172
|
+
def halt!(server)
|
173
|
+
log "Halting #{@host.name} server..."
|
174
|
+
server.halt false
|
175
|
+
log "#{@host.name} server halted."
|
176
|
+
end
|
177
|
+
|
178
|
+
# Full explanation: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
|
179
|
+
def daemonize!(no_chdir = false, no_close = false)
|
180
|
+
exit if fork
|
181
|
+
Process.setsid
|
182
|
+
exit if fork
|
183
|
+
Dir.chdir "/" unless no_chdir
|
184
|
+
if !no_close
|
185
|
+
null = File.open "/dev/null", 'w'
|
186
|
+
STDIN.reopen null
|
187
|
+
STDOUT.reopen null
|
188
|
+
STDERR.reopen null
|
189
|
+
end
|
190
|
+
return 0
|
191
|
+
end
|
192
|
+
|
193
|
+
def log(message)
|
194
|
+
@logger.info "[Sanford] #{message}"
|
195
|
+
end
|
196
|
+
|
197
|
+
class ProcessName < String
|
198
|
+
def initialize(name, ip, port)
|
199
|
+
super "#{[ name, ip, port ].join('_')}"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class RestartCmd
|
204
|
+
attr_reader :argv, :dir
|
205
|
+
|
206
|
+
def initialize(config = nil)
|
207
|
+
require 'rubygems'
|
208
|
+
config ||= OpenStruct.new
|
209
|
+
@dir = config.restart_dir || get_pwd
|
210
|
+
@argv = [ Gem.ruby, $0, ARGV.dup ].flatten
|
211
|
+
end
|
212
|
+
|
213
|
+
protected
|
214
|
+
|
215
|
+
# Trick from puma/unicorn. Favor PWD because it contains an unresolved
|
216
|
+
# symlink. This is useful when restarting after deploying; the original
|
217
|
+
# directory may be removed, but the symlink is pointing to a new
|
218
|
+
# directory.
|
219
|
+
def get_pwd
|
220
|
+
env_stat = File.stat(ENV['PWD'])
|
221
|
+
pwd_stat = File.stat(Dir.pwd)
|
222
|
+
if env_stat.ino == pwd_stat.ino && env_stat.dev == pwd_stat.dev
|
223
|
+
ENV['PWD']
|
224
|
+
else
|
225
|
+
Dir.pwd
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
class SignalHandler
|
234
|
+
|
235
|
+
def initialize(options = nil)
|
236
|
+
@config = Config.new(options)
|
237
|
+
raise Sanford::NoPIDError.new if !@config.pid
|
238
|
+
end
|
239
|
+
|
240
|
+
def stop
|
241
|
+
Process.kill("TERM", @config.pid)
|
242
|
+
end
|
243
|
+
|
244
|
+
def restart
|
245
|
+
Process.kill("USR2", @config.pid)
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
class NoHostError < CLIRB::Error
|
253
|
+
def initialize(host_name)
|
254
|
+
message = if Sanford.hosts.empty?
|
255
|
+
"No hosts have been defined. Please define a host before trying to run Sanford."
|
256
|
+
else
|
257
|
+
"A host couldn't be found with the name #{host_name.inspect}. "
|
258
|
+
end
|
259
|
+
super message
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class InvalidHostError < CLIRB::Error
|
264
|
+
def initialize(host)
|
265
|
+
super "A port must be configured or provided to run a server for '#{host}'"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
class NoPIDError < CLIRB::Error
|
270
|
+
def initialize
|
271
|
+
super "A PID or PID file is required"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|