puma 2.2.2 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- data/History.txt +19 -0
- data/README.md +28 -8
- data/lib/puma/app/status.rb +21 -18
- data/lib/puma/binder.rb +12 -0
- data/lib/puma/cli.rb +185 -564
- data/lib/puma/configuration.rb +2 -2
- data/lib/puma/const.rb +2 -1
- data/lib/puma/control_cli.rb +16 -4
- data/lib/puma/delegation.rb +2 -2
- data/lib/puma/events.rb +14 -0
- data/lib/puma/server.rb +30 -19
- data/lib/puma/thread_pool.rb +2 -2
- data/puma.gemspec +2 -2
- data/test/test_app_status.rb +6 -2
- data/test/test_cli.rb +19 -17
- data/test/test_integration.rb +16 -5
- data/test/test_puma_server.rb +48 -3
- data/test/test_thread_pool.rb +3 -3
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
=== 2.3.0 / 2013-07-05
|
2
|
+
|
3
|
+
* 1 major bug fix:
|
4
|
+
|
5
|
+
* Stabilize control server, add support in cluster mode
|
6
|
+
|
7
|
+
* 5 minor bug fixes:
|
8
|
+
|
9
|
+
* Add ability to cleanup stale unix sockets
|
10
|
+
* Check status data better. Fixes #292
|
11
|
+
* Convert raw IO errors to ConnectionError. Fixes #274
|
12
|
+
* Fix sending Content-Type and Content-Length for no body status. Fixes #304
|
13
|
+
* Pass state path through to `pumactl start`. Fixes #287
|
14
|
+
|
15
|
+
* 2 internal changes:
|
16
|
+
|
17
|
+
* Refactored modes into seperate classes that CLI uses
|
18
|
+
* Changed CLI to take an Events object instead of stdout/stderr (API change)
|
19
|
+
|
1
20
|
=== 2.2.2 / 2013-07-02
|
2
21
|
|
3
22
|
* 1 bug fix:
|
data/README.md
CHANGED
@@ -82,21 +82,41 @@ Puma 2 offers clustered mode, allowing you to use forked processes to handle mul
|
|
82
82
|
On a ruby implementation that offers native threads, you should tune this number to match the number of cores available.
|
83
83
|
Note that threads are still used in clustered mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will be 32 threads.
|
84
84
|
|
85
|
+
This code can be used to setup the process before booting the application, allowing
|
86
|
+
you to do some puma-specific things that you don't want to embed in your application.
|
87
|
+
For instance, you could fire a log notification that a worker booted or send something to statsd.
|
88
|
+
This can be called multiple times to add hooks.
|
89
|
+
|
90
|
+
If you're running in Clustered Mode you can optionally choose to preload your application before starting up the workers. To do this simply specify the `--preload` flag in invocation:
|
91
|
+
|
92
|
+
# CLI invocation
|
93
|
+
$ puma -t 8:32 -w 3 --preload
|
94
|
+
|
95
|
+
If you're using a configuration file, use the `preload_app!` method, and be sure to specify your config file's location with the `-C` flag:
|
96
|
+
|
97
|
+
$ puma -C config/puma.rb
|
98
|
+
|
99
|
+
# config/puma.rb
|
100
|
+
threads 8,32
|
101
|
+
workers 3
|
102
|
+
preload_app!
|
103
|
+
|
104
|
+
|
85
105
|
Additionally, you can specify a block in your configuration that will be run on boot of each worker:
|
86
106
|
|
87
107
|
# config/puma.rb
|
88
108
|
on_worker_boot do
|
89
109
|
# configuration here
|
90
110
|
end
|
111
|
+
|
112
|
+
If you're preloading your application and using ActiveRecord, it's recommend you setup your connection pool here:
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
$ puma -t 8:32 -w 3 -C config/puma.rb
|
114
|
+
# config/puma.rb
|
115
|
+
on_worker_boot do
|
116
|
+
ActiveSupport.on_load(:active_record) do
|
117
|
+
ActiveRecord::Base.establish_connection
|
118
|
+
end
|
119
|
+
end
|
100
120
|
|
101
121
|
### Binding TCP / Sockets
|
102
122
|
|
data/lib/puma/app/status.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
module Puma
|
2
2
|
module App
|
3
3
|
class Status
|
4
|
-
def initialize(
|
5
|
-
@server = server
|
4
|
+
def initialize(cli)
|
6
5
|
@cli = cli
|
7
6
|
@auth_token = nil
|
8
7
|
end
|
@@ -15,6 +14,15 @@ module Puma
|
|
15
14
|
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
16
15
|
end
|
17
16
|
|
17
|
+
def rack_response(status, body, content_type='application/json')
|
18
|
+
headers = {
|
19
|
+
'Content-Type' => content_type,
|
20
|
+
'Content-Length' => body.bytesize.to_s
|
21
|
+
}
|
22
|
+
|
23
|
+
[status, headers, [body]]
|
24
|
+
end
|
25
|
+
|
18
26
|
def call(env)
|
19
27
|
unless authenticate(env)
|
20
28
|
return rack_response(403, 'Invalid auth token', 'text/plain')
|
@@ -22,34 +30,29 @@ module Puma
|
|
22
30
|
|
23
31
|
case env['PATH_INFO']
|
24
32
|
when /\/stop$/
|
25
|
-
@
|
33
|
+
@cli.stop
|
26
34
|
return rack_response(200, OK_STATUS)
|
27
35
|
|
28
36
|
when /\/halt$/
|
29
|
-
@
|
37
|
+
@cli.halt
|
30
38
|
return rack_response(200, OK_STATUS)
|
31
39
|
|
32
40
|
when /\/restart$/
|
33
|
-
|
34
|
-
|
41
|
+
@cli.restart
|
42
|
+
return rack_response(200, OK_STATUS)
|
35
43
|
|
36
|
-
|
44
|
+
when /\/phased-restart$/
|
45
|
+
if !@cli.phased_restart
|
46
|
+
return rack_response(404, '{ "error": "phased resart not available" }')
|
37
47
|
else
|
38
|
-
return rack_response(200,
|
48
|
+
return rack_response(200, OK_STATUS)
|
39
49
|
end
|
40
50
|
|
41
51
|
when /\/stats$/
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
return rack_response(200, @cli.stats)
|
53
|
+
else
|
54
|
+
rack_response 404, "Unsupported action", 'text/plain'
|
45
55
|
end
|
46
|
-
|
47
|
-
rack_response 404, "Unsupported action", 'text/plain'
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
def rack_response(status, body, content_type='application/json')
|
52
|
-
[status, { 'Content-Type' => content_type, 'Content-Length' => body.bytesize.to_s }, [body]]
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
data/lib/puma/binder.rb
CHANGED
@@ -255,6 +255,18 @@ module Puma
|
|
255
255
|
|
256
256
|
begin
|
257
257
|
old_mask = File.umask(umask)
|
258
|
+
|
259
|
+
if File.exists? path
|
260
|
+
begin
|
261
|
+
old = UNIXSocket.new path
|
262
|
+
rescue SystemCallError
|
263
|
+
File.unlink path
|
264
|
+
else
|
265
|
+
old.close
|
266
|
+
raise "There is already a server bound to: #{path}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
258
270
|
s = UNIXServer.new(path)
|
259
271
|
@ios << s
|
260
272
|
ensure
|
data/lib/puma/cli.rb
CHANGED
@@ -8,6 +8,8 @@ require 'puma/binder'
|
|
8
8
|
require 'puma/detect'
|
9
9
|
require 'puma/daemon_ext'
|
10
10
|
require 'puma/util'
|
11
|
+
require 'puma/single'
|
12
|
+
require 'puma/cluster'
|
11
13
|
|
12
14
|
require 'rack/commonlogger'
|
13
15
|
require 'rack/utils'
|
@@ -22,122 +24,37 @@ module Puma
|
|
22
24
|
# +stdout+ and +stderr+ can be set to IO-like objects which
|
23
25
|
# this object will report status on.
|
24
26
|
#
|
25
|
-
def initialize(argv,
|
27
|
+
def initialize(argv, events=Events.stdio)
|
26
28
|
@debug = false
|
27
29
|
@argv = argv
|
28
|
-
@stdout = stdout
|
29
|
-
@stderr = stderr
|
30
30
|
|
31
|
-
@
|
32
|
-
@workers = []
|
31
|
+
@events = events
|
33
32
|
|
34
|
-
@events = Events.new @stdout, @stderr
|
35
|
-
|
36
|
-
@server = nil
|
37
33
|
@status = nil
|
34
|
+
@runner = nil
|
38
35
|
|
39
|
-
@
|
40
|
-
@phased_state = :idle
|
41
|
-
|
42
|
-
@app = nil
|
36
|
+
@config = nil
|
43
37
|
|
44
38
|
ENV['NEWRELIC_DISPATCHER'] ||= "puma"
|
45
39
|
|
46
40
|
setup_options
|
47
|
-
|
48
41
|
generate_restart_data
|
49
42
|
|
50
43
|
@binder = Binder.new(@events)
|
51
44
|
@binder.import_from_env
|
52
45
|
end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def generate_restart_data
|
59
|
-
# Use the same trick as unicorn, namely favor PWD because
|
60
|
-
# it will contain an unresolved symlink, useful for when
|
61
|
-
# the pwd is /data/releases/current.
|
62
|
-
if dir = ENV['PWD']
|
63
|
-
s_env = File.stat(dir)
|
64
|
-
s_pwd = File.stat(Dir.pwd)
|
65
|
-
|
66
|
-
if s_env.ino == s_pwd.ino and s_env.dev == s_pwd.dev
|
67
|
-
@restart_dir = dir
|
68
|
-
@options[:worker_directory] = dir
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
@restart_dir ||= Dir.pwd
|
73
|
-
|
74
|
-
@original_argv = ARGV.dup
|
75
|
-
|
76
|
-
if defined? Rubinius::OS_ARGV
|
77
|
-
@restart_argv = Rubinius::OS_ARGV
|
78
|
-
else
|
79
|
-
require 'rubygems'
|
80
|
-
|
81
|
-
# if $0 is a file in the current directory, then restart
|
82
|
-
# it the same, otherwise add -S on there because it was
|
83
|
-
# picked up in PATH.
|
84
|
-
#
|
85
|
-
if File.exists?($0)
|
86
|
-
arg0 = [Gem.ruby, $0]
|
87
|
-
else
|
88
|
-
arg0 = [Gem.ruby, "-S", $0]
|
89
|
-
end
|
90
|
-
|
91
|
-
# Detect and reinject -Ilib from the command line
|
92
|
-
lib = File.expand_path "lib"
|
93
|
-
arg0[1,0] = ["-I", lib] if $:[0] == lib
|
94
|
-
|
95
|
-
@restart_argv = arg0 + ARGV
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def restart_args
|
100
|
-
if cmd = @options[:restart_cmd]
|
101
|
-
cmd.split(' ') + @original_argv
|
102
|
-
else
|
103
|
-
@restart_argv
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def restart!
|
108
|
-
@options[:on_restart].each do |blk|
|
109
|
-
blk.call self
|
110
|
-
end
|
47
|
+
# The Binder object containing the sockets bound to.
|
48
|
+
attr_reader :binder
|
111
49
|
|
112
|
-
|
113
|
-
|
114
|
-
io.close
|
50
|
+
# The Configuration object used.
|
51
|
+
attr_reader :config
|
115
52
|
|
116
|
-
|
117
|
-
|
118
|
-
if uri.scheme == "unix"
|
119
|
-
path = "#{uri.host}#{uri.path}"
|
120
|
-
File.unlink path
|
121
|
-
end
|
122
|
-
end
|
53
|
+
# The Hash of options used to configure puma.
|
54
|
+
attr_reader :options
|
123
55
|
|
124
|
-
|
125
|
-
|
126
|
-
else
|
127
|
-
redirects = {:close_others => true}
|
128
|
-
@binder.listeners.each_with_index do |(l,io),i|
|
129
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
130
|
-
redirects[io.to_i] = io.to_i
|
131
|
-
end
|
132
|
-
|
133
|
-
argv = restart_args
|
134
|
-
|
135
|
-
Dir.chdir @restart_dir
|
136
|
-
|
137
|
-
argv += [redirects] unless RUBY_VERSION < '1.9'
|
138
|
-
Kernel.exec(*argv)
|
139
|
-
end
|
140
|
-
end
|
56
|
+
# The Events object used to output information.
|
57
|
+
attr_reader :events
|
141
58
|
|
142
59
|
# Delegate +log+ to +@events+
|
143
60
|
#
|
@@ -289,6 +206,26 @@ module Puma
|
|
289
206
|
end
|
290
207
|
end
|
291
208
|
|
209
|
+
def write_state
|
210
|
+
write_pid
|
211
|
+
|
212
|
+
require 'yaml'
|
213
|
+
|
214
|
+
if path = @options[:state]
|
215
|
+
state = { "pid" => Process.pid }
|
216
|
+
|
217
|
+
cfg = @config.dup
|
218
|
+
|
219
|
+
[ :logger, :worker_boot, :on_restart ].each { |o| cfg.options.delete o }
|
220
|
+
|
221
|
+
state["config"] = cfg
|
222
|
+
|
223
|
+
File.open(path, "w") do |f|
|
224
|
+
f.write state.to_yaml
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
292
229
|
# If configured, write the pid of the current process out
|
293
230
|
# to a file.
|
294
231
|
#
|
@@ -297,6 +234,8 @@ module Puma
|
|
297
234
|
File.open(path, "w") do |f|
|
298
235
|
f.puts Process.pid
|
299
236
|
end
|
237
|
+
|
238
|
+
at_exit { delete_pidfile }
|
300
239
|
end
|
301
240
|
end
|
302
241
|
|
@@ -312,33 +251,9 @@ module Puma
|
|
312
251
|
ENV['RACK_ENV'] = env
|
313
252
|
end
|
314
253
|
|
315
|
-
def development?
|
316
|
-
@options[:environment] == "development"
|
317
|
-
end
|
318
|
-
|
319
254
|
def delete_pidfile
|
320
255
|
if path = @options[:pidfile]
|
321
|
-
File.unlink path
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
def write_state
|
326
|
-
write_pid
|
327
|
-
|
328
|
-
require 'yaml'
|
329
|
-
|
330
|
-
if path = @options[:state]
|
331
|
-
state = { "pid" => Process.pid }
|
332
|
-
|
333
|
-
cfg = @config.dup
|
334
|
-
|
335
|
-
[ :logger, :worker_boot, :on_restart ].each { |o| cfg.options.delete o }
|
336
|
-
|
337
|
-
state["config"] = cfg
|
338
|
-
|
339
|
-
File.open(path, "w") do |f|
|
340
|
-
f.write state.to_yaml
|
341
|
-
end
|
256
|
+
File.unlink path if File.exists? path
|
342
257
|
end
|
343
258
|
end
|
344
259
|
|
@@ -357,535 +272,241 @@ module Puma
|
|
357
272
|
|
358
273
|
@config.load
|
359
274
|
|
360
|
-
if
|
275
|
+
if clustered?
|
361
276
|
unsupported "worker mode not supported on JRuby and Windows",
|
362
277
|
jruby? || windows?
|
363
278
|
end
|
279
|
+
|
280
|
+
if @options[:daemon] and windows?
|
281
|
+
unsupported "daemon mode not supported on Windows"
|
282
|
+
end
|
364
283
|
end
|
365
284
|
|
366
|
-
def
|
367
|
-
|
368
|
-
@status.stop(true) if @status
|
369
|
-
server.stop(true)
|
370
|
-
delete_pidfile
|
371
|
-
log " - Goodbye!"
|
285
|
+
def clustered?
|
286
|
+
@options[:workers] > 0
|
372
287
|
end
|
373
288
|
|
374
|
-
def
|
375
|
-
|
376
|
-
|
377
|
-
|
289
|
+
def graceful_stop
|
290
|
+
@control.stop(true) if @control
|
291
|
+
@runner.stop_blocked
|
292
|
+
log "- Goodbye!"
|
293
|
+
end
|
378
294
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
295
|
+
def generate_restart_data
|
296
|
+
# Use the same trick as unicorn, namely favor PWD because
|
297
|
+
# it will contain an unresolved symlink, useful for when
|
298
|
+
# the pwd is /data/releases/current.
|
299
|
+
if dir = ENV['PWD']
|
300
|
+
s_env = File.stat(dir)
|
301
|
+
s_pwd = File.stat(Dir.pwd)
|
384
302
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
303
|
+
if s_env.ino == s_pwd.ino and s_env.dev == s_pwd.dev
|
304
|
+
@restart_dir = dir
|
305
|
+
@options[:worker_directory] = dir
|
306
|
+
end
|
389
307
|
end
|
390
|
-
end
|
391
308
|
|
392
|
-
|
393
|
-
# for it to finish.
|
394
|
-
#
|
395
|
-
def run
|
396
|
-
begin
|
397
|
-
parse_options
|
398
|
-
rescue UnsupportedOption
|
399
|
-
exit 1
|
400
|
-
end
|
309
|
+
@restart_dir ||= Dir.pwd
|
401
310
|
|
402
|
-
|
403
|
-
Dir.chdir dir
|
404
|
-
end
|
311
|
+
@original_argv = ARGV.dup
|
405
312
|
|
406
|
-
|
313
|
+
if defined? Rubinius::OS_ARGV
|
314
|
+
@restart_argv = Rubinius::OS_ARGV
|
315
|
+
else
|
316
|
+
require 'rubygems'
|
407
317
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
318
|
+
# if $0 is a file in the current directory, then restart
|
319
|
+
# it the same, otherwise add -S on there because it was
|
320
|
+
# picked up in PATH.
|
321
|
+
#
|
322
|
+
if File.exists?($0)
|
323
|
+
arg0 = [Gem.ruby, $0]
|
324
|
+
else
|
325
|
+
arg0 = [Gem.ruby, "-S", $0]
|
326
|
+
end
|
412
327
|
|
413
|
-
|
328
|
+
# Detect and reinject -Ilib from the command line
|
329
|
+
lib = File.expand_path "lib"
|
330
|
+
arg0[1,0] = ["-I", lib] if $:[0] == lib
|
414
331
|
|
415
|
-
|
416
|
-
run_cluster
|
417
|
-
else
|
418
|
-
run_single
|
332
|
+
@restart_argv = arg0 + ARGV
|
419
333
|
end
|
420
334
|
end
|
421
335
|
|
422
|
-
def
|
423
|
-
@options[:
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
336
|
+
def restart_args
|
337
|
+
if cmd = @options[:restart_cmd]
|
338
|
+
cmd.split(' ') + @original_argv
|
339
|
+
else
|
340
|
+
@restart_argv
|
341
|
+
end
|
428
342
|
end
|
429
343
|
|
430
|
-
def
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
log "Puma #{Puma::Const::PUMA_VERSION} starting..."
|
435
|
-
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
436
|
-
log "* Environment: #{ENV['RACK_ENV']}"
|
344
|
+
def jruby_daemon_start
|
345
|
+
require 'puma/jruby_restart'
|
346
|
+
JRubyRestart.daemon_start(@restart_dir, restart_args)
|
437
347
|
end
|
438
348
|
|
439
|
-
def
|
440
|
-
|
441
|
-
|
442
|
-
uri = URI.parse str
|
443
|
-
|
444
|
-
app = Puma::App::Status.new server, self
|
445
|
-
|
446
|
-
if token = @options[:control_auth_token]
|
447
|
-
app.auth_token = token unless token.empty? or token == :none
|
349
|
+
def restart!
|
350
|
+
@options[:on_restart].each do |block|
|
351
|
+
block.call self
|
448
352
|
end
|
449
353
|
|
450
|
-
|
451
|
-
|
452
|
-
|
354
|
+
if jruby?
|
355
|
+
@binder.listeners.each_with_index do |(str,io),i|
|
356
|
+
io.close
|
453
357
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
358
|
+
# We have to unlink a unix socket path that's not being used
|
359
|
+
uri = URI.parse str
|
360
|
+
if uri.scheme == "unix"
|
361
|
+
path = "#{uri.host}#{uri.path}"
|
362
|
+
File.unlink path
|
363
|
+
end
|
364
|
+
end
|
461
365
|
|
462
|
-
|
366
|
+
require 'puma/jruby_restart'
|
367
|
+
JRubyRestart.chdir_exec(@restart_dir, restart_args)
|
463
368
|
else
|
464
|
-
|
465
|
-
|
369
|
+
redirects = {:close_others => true}
|
370
|
+
@binder.listeners.each_with_index do |(l,io),i|
|
371
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
372
|
+
redirects[io.to_i] = io.to_i
|
373
|
+
end
|
466
374
|
|
467
|
-
|
468
|
-
@status = status
|
469
|
-
end
|
375
|
+
argv = restart_args
|
470
376
|
|
471
|
-
|
472
|
-
unless @config.app_configured?
|
473
|
-
error "No application configured, nothing to run"
|
474
|
-
exit 1
|
475
|
-
end
|
377
|
+
Dir.chdir @restart_dir
|
476
378
|
|
477
|
-
|
478
|
-
|
479
|
-
@app = @config.app
|
480
|
-
rescue Exception => e
|
481
|
-
log "! Unable to load application"
|
482
|
-
raise e
|
379
|
+
argv += [redirects] unless RUBY_VERSION < '1.9'
|
380
|
+
Kernel.exec(*argv)
|
483
381
|
end
|
484
|
-
|
485
|
-
@binder.parse @options[:binds], self
|
486
382
|
end
|
487
383
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
load_and_bind
|
497
|
-
end
|
498
|
-
|
499
|
-
already_daemon = JRubyRestart.daemon_init
|
384
|
+
# Parse the options, load the rackup, start the server and wait
|
385
|
+
# for it to finish.
|
386
|
+
#
|
387
|
+
def run
|
388
|
+
begin
|
389
|
+
parse_options
|
390
|
+
rescue UnsupportedOption
|
391
|
+
exit 1
|
500
392
|
end
|
501
393
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
unless already_daemon
|
506
|
-
require 'puma/jruby_restart'
|
394
|
+
if dir = @options[:directory]
|
395
|
+
Dir.chdir dir
|
396
|
+
end
|
507
397
|
|
508
|
-
|
398
|
+
set_rack_environment
|
509
399
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
end
|
400
|
+
if clustered?
|
401
|
+
@events = PidEvents.new STDOUT, STDERR
|
402
|
+
@options[:logger] = @events
|
514
403
|
|
515
|
-
|
516
|
-
sleep
|
517
|
-
end
|
404
|
+
@runner = Cluster.new(self)
|
518
405
|
else
|
519
|
-
|
520
|
-
Process.daemon(true) if daemon?
|
406
|
+
@runner = Single.new(self)
|
521
407
|
end
|
522
408
|
|
523
|
-
|
524
|
-
|
525
|
-
server = Puma::Server.new @app, @events
|
526
|
-
server.binder = @binder
|
527
|
-
server.min_threads = @options[:min_threads]
|
528
|
-
server.max_threads = @options[:max_threads]
|
409
|
+
setup_signals
|
529
410
|
|
530
|
-
|
531
|
-
|
411
|
+
if cont = @options[:control_url]
|
412
|
+
start_control cont
|
532
413
|
end
|
533
414
|
|
534
|
-
@
|
415
|
+
@status = :run
|
535
416
|
|
536
|
-
|
537
|
-
|
417
|
+
@runner.run
|
418
|
+
|
419
|
+
case @status
|
420
|
+
when :halt
|
421
|
+
log "* Stopping immediately!"
|
422
|
+
when :run, :stop
|
423
|
+
graceful_stop
|
424
|
+
when :restart
|
425
|
+
log "* Restarting..."
|
426
|
+
@control.stop true if @control
|
427
|
+
restart!
|
538
428
|
end
|
429
|
+
end
|
539
430
|
|
431
|
+
def setup_signals
|
540
432
|
begin
|
541
433
|
Signal.trap "SIGUSR2" do
|
542
|
-
|
543
|
-
server.begin_restart
|
434
|
+
restart
|
544
435
|
end
|
545
436
|
rescue Exception
|
546
|
-
log "***
|
437
|
+
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
|
547
438
|
end
|
548
439
|
|
549
440
|
begin
|
550
441
|
Signal.trap "SIGTERM" do
|
551
|
-
|
552
|
-
server.stop false
|
442
|
+
stop
|
553
443
|
end
|
554
444
|
rescue Exception
|
555
|
-
log "***
|
556
|
-
end
|
557
|
-
|
558
|
-
unless @options[:daemon]
|
559
|
-
log "Use Ctrl-C to stop"
|
445
|
+
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
560
446
|
end
|
561
447
|
|
562
|
-
redirect_io
|
563
|
-
|
564
448
|
if jruby?
|
565
449
|
Signal.trap("INT") do
|
566
|
-
graceful_stop
|
450
|
+
graceful_stop
|
567
451
|
exit
|
568
452
|
end
|
569
453
|
end
|
570
|
-
|
571
|
-
begin
|
572
|
-
server.run.join
|
573
|
-
rescue Interrupt
|
574
|
-
graceful_stop server
|
575
|
-
end
|
576
|
-
|
577
|
-
if @restart
|
578
|
-
log "* Restarting..."
|
579
|
-
@status.stop true if @status
|
580
|
-
restart!
|
581
|
-
end
|
582
454
|
end
|
583
455
|
|
584
|
-
def
|
585
|
-
|
586
|
-
Signal.trap "SIGINT", "IGNORE"
|
587
|
-
|
588
|
-
@master_read.close
|
589
|
-
@suicide_pipe.close
|
590
|
-
|
591
|
-
Thread.new do
|
592
|
-
IO.select [@check_pipe]
|
593
|
-
log "! Detected parent died, dying"
|
594
|
-
exit! 1
|
595
|
-
end
|
596
|
-
|
597
|
-
# Be sure to change the directory again before loading
|
598
|
-
# the app. This way we can pick up new code.
|
599
|
-
if upgrade
|
600
|
-
if dir = @options[:worker_directory]
|
601
|
-
log "+ Changing to #{dir}"
|
602
|
-
Dir.chdir dir
|
603
|
-
end
|
604
|
-
end
|
605
|
-
|
606
|
-
# Invoke any worker boot hooks so they can get
|
607
|
-
# things in shape before booting the app.
|
608
|
-
hooks = @options[:worker_boot]
|
609
|
-
hooks.each { |h| h.call }
|
610
|
-
|
611
|
-
min_t = @options[:min_threads]
|
612
|
-
max_t = @options[:max_threads]
|
613
|
-
|
614
|
-
# If preload is used, then @app is set to the preloaded
|
615
|
-
# application. Otherwise load it now via the config.
|
616
|
-
app = @app || @config.app
|
617
|
-
|
618
|
-
server = Puma::Server.new app, @events
|
619
|
-
server.min_threads = min_t
|
620
|
-
server.max_threads = max_t
|
621
|
-
server.inherit_binder @binder
|
456
|
+
def start_control(str)
|
457
|
+
require 'puma/app/status'
|
622
458
|
|
623
|
-
|
624
|
-
server.leak_stack_on_error = false
|
625
|
-
end
|
459
|
+
uri = URI.parse str
|
626
460
|
|
627
|
-
|
628
|
-
server.stop
|
629
|
-
end
|
461
|
+
app = Puma::App::Status.new self
|
630
462
|
|
631
|
-
|
632
|
-
|
633
|
-
rescue SystemCallError, IOError
|
634
|
-
STDERR.puts "Master seems to have exitted, exitting."
|
635
|
-
return
|
463
|
+
if token = @options[:control_auth_token]
|
464
|
+
app.auth_token = token unless token.empty? or token == :none
|
636
465
|
end
|
637
466
|
|
638
|
-
|
467
|
+
control = Puma::Server.new app, @events
|
468
|
+
control.min_threads = 0
|
469
|
+
control.max_threads = 1
|
639
470
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
471
|
+
case uri.scheme
|
472
|
+
when "tcp"
|
473
|
+
log "* Starting control server on #{str}"
|
474
|
+
control.add_tcp_listener uri.host, uri.port
|
475
|
+
when "unix"
|
476
|
+
log "* Starting control server on #{str}"
|
477
|
+
path = "#{uri.host}#{uri.path}"
|
647
478
|
|
648
|
-
|
649
|
-
Process.waitall
|
650
|
-
rescue Interrupt
|
651
|
-
log "! Cancelled waiting for workers"
|
479
|
+
control.add_unix_listener path
|
652
480
|
else
|
653
|
-
|
654
|
-
end
|
655
|
-
end
|
656
|
-
|
657
|
-
def start_phased_restart
|
658
|
-
@phase += 1
|
659
|
-
log "- Starting phased worker restart, phase: #{@phase}"
|
660
|
-
end
|
661
|
-
|
662
|
-
class Worker
|
663
|
-
def initialize(pid, phase)
|
664
|
-
@pid = pid
|
665
|
-
@phase = phase
|
666
|
-
@stage = :started
|
481
|
+
error "Invalid control URI: #{str}"
|
667
482
|
end
|
668
483
|
|
669
|
-
|
670
|
-
|
671
|
-
def booted?
|
672
|
-
@stage == :booted
|
673
|
-
end
|
674
|
-
|
675
|
-
def boot!
|
676
|
-
@stage = :booted
|
677
|
-
end
|
678
|
-
|
679
|
-
def term
|
680
|
-
begin
|
681
|
-
Process.kill "TERM", @pid
|
682
|
-
rescue Errno::ESRCH
|
683
|
-
end
|
684
|
-
end
|
484
|
+
control.run
|
485
|
+
@control = control
|
685
486
|
end
|
686
487
|
|
687
|
-
def
|
688
|
-
|
689
|
-
|
690
|
-
upgrade = (@phased_state == :waiting)
|
691
|
-
|
692
|
-
diff.times do
|
693
|
-
pid = fork { worker(upgrade) }
|
694
|
-
debug "Spawned worker: #{pid}"
|
695
|
-
@workers << Worker.new(pid, @phase)
|
696
|
-
end
|
697
|
-
|
698
|
-
if diff > 0
|
699
|
-
@phased_state = :idle
|
700
|
-
end
|
488
|
+
def stop
|
489
|
+
@status = :stop
|
490
|
+
@runner.stop
|
701
491
|
end
|
702
492
|
|
703
|
-
def
|
704
|
-
@
|
493
|
+
def restart
|
494
|
+
@status = :restart
|
495
|
+
@runner.restart
|
705
496
|
end
|
706
497
|
|
707
|
-
def
|
708
|
-
|
709
|
-
|
710
|
-
break unless pid
|
711
|
-
|
712
|
-
@workers.delete_if { |w| w.pid == pid }
|
713
|
-
end
|
714
|
-
|
715
|
-
spawn_workers
|
716
|
-
|
717
|
-
if @phased_state == :idle && all_workers_booted?
|
718
|
-
# If we're running at proper capacity, check to see if
|
719
|
-
# we need to phase any workers out (which will restart
|
720
|
-
# in the right phase).
|
721
|
-
#
|
722
|
-
w = @workers.find { |x| x.phase != @phase }
|
723
|
-
|
724
|
-
if w
|
725
|
-
@phased_state = :waiting
|
726
|
-
log "- Stopping #{w.pid} for phased upgrade..."
|
727
|
-
w.term
|
728
|
-
end
|
729
|
-
end
|
498
|
+
def phased_restart
|
499
|
+
return false unless @runner.respond_to? :phased_restart
|
500
|
+
@runner.phased_restart
|
730
501
|
end
|
731
502
|
|
732
|
-
def
|
733
|
-
|
734
|
-
@wakeup.write "!" unless @wakeup.closed?
|
735
|
-
rescue SystemCallError, IOError
|
736
|
-
end
|
503
|
+
def stats
|
504
|
+
@runner.stats
|
737
505
|
end
|
738
506
|
|
739
|
-
def
|
740
|
-
|
741
|
-
|
742
|
-
log "* Min threads: #{@options[:min_threads]}, max threads: #{@options[:max_threads]}"
|
743
|
-
log "* Environment: #{ENV['RACK_ENV']}"
|
744
|
-
|
745
|
-
if @options[:preload_app]
|
746
|
-
log "* Preloading application"
|
747
|
-
load_and_bind
|
748
|
-
else
|
749
|
-
log "* Phased restart available"
|
750
|
-
|
751
|
-
unless @config.app_configured?
|
752
|
-
error "No application configured, nothing to run"
|
753
|
-
exit 1
|
754
|
-
end
|
755
|
-
|
756
|
-
@binder.parse @options[:binds], self
|
757
|
-
end
|
758
|
-
|
759
|
-
read, @wakeup = Puma::Util.pipe
|
760
|
-
|
761
|
-
Signal.trap "SIGCHLD" do
|
762
|
-
wakeup!
|
763
|
-
end
|
764
|
-
|
765
|
-
stop = false
|
766
|
-
|
767
|
-
begin
|
768
|
-
Signal.trap "SIGUSR2" do
|
769
|
-
@restart = true
|
770
|
-
stop = true
|
771
|
-
wakeup!
|
772
|
-
end
|
773
|
-
rescue Exception
|
774
|
-
end
|
775
|
-
|
776
|
-
master_pid = Process.pid
|
777
|
-
|
778
|
-
begin
|
779
|
-
Signal.trap "SIGTERM" do
|
780
|
-
# The worker installs their own SIGTERM when booted.
|
781
|
-
# Until then, this is run by the worker and the worker
|
782
|
-
# should just exit if they get it.
|
783
|
-
if Process.pid != master_pid
|
784
|
-
log "Early termination of worker"
|
785
|
-
exit! 0
|
786
|
-
else
|
787
|
-
stop = true
|
788
|
-
wakeup!
|
789
|
-
end
|
790
|
-
end
|
791
|
-
rescue Exception
|
792
|
-
end
|
793
|
-
|
794
|
-
phased_restart = false
|
795
|
-
|
796
|
-
begin
|
797
|
-
if @options[:preload_app]
|
798
|
-
Signal.trap "SIGUSR1" do
|
799
|
-
log "App preloaded, phased restart unavailable"
|
800
|
-
end
|
801
|
-
else
|
802
|
-
Signal.trap "SIGUSR1" do
|
803
|
-
phased_restart = true
|
804
|
-
@wakeup << "!"
|
805
|
-
wakeup!
|
806
|
-
end
|
807
|
-
end
|
808
|
-
rescue Exception
|
809
|
-
end
|
810
|
-
|
811
|
-
# Used by the workers to detect if the master process dies.
|
812
|
-
# If select says that @check_pipe is ready, it's because the
|
813
|
-
# master has exited and @suicide_pipe has been automatically
|
814
|
-
# closed.
|
815
|
-
#
|
816
|
-
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
817
|
-
|
818
|
-
if @options[:daemon]
|
819
|
-
Process.daemon(true)
|
820
|
-
else
|
821
|
-
log "Use Ctrl-C to stop"
|
822
|
-
end
|
823
|
-
|
824
|
-
@master_pid = Process.pid
|
825
|
-
|
826
|
-
redirect_io
|
827
|
-
|
828
|
-
write_state
|
829
|
-
|
830
|
-
@master_read, @worker_write = read, @wakeup
|
831
|
-
spawn_workers
|
832
|
-
|
833
|
-
Signal.trap "SIGINT" do
|
834
|
-
stop = true
|
835
|
-
wakeup!
|
836
|
-
end
|
837
|
-
|
838
|
-
begin
|
839
|
-
while !stop
|
840
|
-
begin
|
841
|
-
res = IO.select([read], nil, nil, 5)
|
842
|
-
|
843
|
-
if res
|
844
|
-
req = read.read_nonblock(1)
|
845
|
-
|
846
|
-
if req == "b"
|
847
|
-
pid = read.gets.to_i
|
848
|
-
w = @workers.find { |x| x.pid == pid }
|
849
|
-
if w
|
850
|
-
w.boot!
|
851
|
-
log "- Worker #{pid} booted, phase: #{w.phase}"
|
852
|
-
else
|
853
|
-
log "! Out-of-sync worker list, no #{pid} worker"
|
854
|
-
end
|
855
|
-
end
|
856
|
-
end
|
857
|
-
|
858
|
-
check_workers
|
859
|
-
|
860
|
-
if phased_restart
|
861
|
-
start_phased_restart
|
862
|
-
phased_restart = false
|
863
|
-
end
|
864
|
-
|
865
|
-
rescue Interrupt
|
866
|
-
stop = true
|
867
|
-
end
|
868
|
-
end
|
869
|
-
|
870
|
-
stop_workers
|
871
|
-
ensure
|
872
|
-
delete_pidfile
|
873
|
-
@check_pipe.close
|
874
|
-
@suicide_pipe.close
|
875
|
-
read.close
|
876
|
-
@wakeup.close
|
877
|
-
end
|
878
|
-
|
879
|
-
if @restart
|
880
|
-
log "* Restarting..."
|
881
|
-
restart!
|
882
|
-
end
|
883
|
-
end
|
884
|
-
|
885
|
-
def stop
|
886
|
-
@status.stop(true) if @status
|
887
|
-
@server.stop(true) if @server
|
888
|
-
delete_pidfile
|
507
|
+
def halt
|
508
|
+
@status = :halt
|
509
|
+
@runner.halt
|
889
510
|
end
|
890
511
|
end
|
891
512
|
end
|