puma 4.3.12 → 5.0.0.beta1

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.

Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +58 -41
  3. data/LICENSE +23 -20
  4. data/README.md +17 -11
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/deployment.md +3 -1
  8. data/docs/fork_worker.md +31 -0
  9. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  10. data/docs/images/puma-connection-flow.png +0 -0
  11. data/docs/images/puma-general-arch.png +0 -0
  12. data/docs/jungle/README.md +13 -0
  13. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  14. data/{tools → docs}/jungle/rc.d/puma +0 -0
  15. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  16. data/{tools → docs}/jungle/upstart/README.md +0 -0
  17. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  18. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  19. data/docs/nginx.md +0 -0
  20. data/docs/plugins.md +0 -0
  21. data/docs/restart.md +0 -0
  22. data/docs/signals.md +1 -0
  23. data/docs/systemd.md +1 -63
  24. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  25. data/ext/puma_http11/ext_help.h +0 -0
  26. data/ext/puma_http11/extconf.rb +3 -10
  27. data/ext/puma_http11/http11_parser.c +11 -26
  28. data/ext/puma_http11/http11_parser.h +0 -0
  29. data/ext/puma_http11/http11_parser.java.rl +0 -0
  30. data/ext/puma_http11/http11_parser.rl +1 -3
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +47 -82
  33. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  34. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +46 -48
  35. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +0 -0
  36. data/ext/puma_http11/puma_http11.c +2 -38
  37. data/lib/puma/accept_nonblock.rb +0 -0
  38. data/lib/puma/app/status.rb +16 -5
  39. data/lib/puma/binder.rb +62 -60
  40. data/lib/puma/cli.rb +7 -15
  41. data/lib/puma/client.rb +38 -78
  42. data/lib/puma/cluster.rb +179 -74
  43. data/lib/puma/commonlogger.rb +0 -0
  44. data/lib/puma/configuration.rb +30 -42
  45. data/lib/puma/const.rb +5 -8
  46. data/lib/puma/control_cli.rb +27 -17
  47. data/lib/puma/detect.rb +8 -0
  48. data/lib/puma/dsl.rb +70 -34
  49. data/lib/puma/events.rb +0 -0
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/launcher.rb +41 -29
  53. data/lib/puma/minissl/context_builder.rb +0 -0
  54. data/lib/puma/minissl.rb +13 -8
  55. data/lib/puma/null_io.rb +1 -1
  56. data/lib/puma/plugin/tmp_restart.rb +0 -0
  57. data/lib/puma/plugin.rb +1 -10
  58. data/lib/puma/rack/builder.rb +0 -4
  59. data/lib/puma/rack/urlmap.rb +0 -0
  60. data/lib/puma/rack_default.rb +0 -0
  61. data/lib/puma/reactor.rb +6 -1
  62. data/lib/puma/runner.rb +5 -34
  63. data/lib/puma/server.rb +74 -206
  64. data/lib/puma/single.rb +7 -64
  65. data/lib/puma/state_file.rb +5 -2
  66. data/lib/puma/thread_pool.rb +85 -47
  67. data/lib/puma/util.rb +0 -0
  68. data/lib/puma.rb +4 -0
  69. data/lib/rack/handler/puma.rb +1 -3
  70. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  71. data/tools/trickletest.rb +0 -0
  72. metadata +20 -24
  73. data/docs/tcp_mode.md +0 -96
  74. data/ext/puma_http11/io_buffer.c +0 -155
  75. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  76. data/lib/puma/tcp_logger.rb +0 -41
  77. data/tools/jungle/README.md +0 -19
  78. data/tools/jungle/init.d/README.md +0 -61
  79. data/tools/jungle/init.d/puma +0 -421
  80. data/tools/jungle/init.d/run-puma +0 -18
data/lib/puma/runner.rb CHANGED
@@ -18,10 +18,6 @@ module Puma
18
18
  @started_at = Time.now
19
19
  end
20
20
 
21
- def daemon?
22
- @options[:daemon]
23
- end
24
-
25
21
  def development?
26
22
  @options[:environment] == "development"
27
23
  end
@@ -52,8 +48,6 @@ module Puma
52
48
 
53
49
  require 'puma/app/status'
54
50
 
55
- uri = URI.parse str
56
-
57
51
  if token = @options[:control_auth_token]
58
52
  token = nil if token.empty? || token == 'none'
59
53
  end
@@ -64,30 +58,16 @@ module Puma
64
58
  control.min_threads = 0
65
59
  control.max_threads = 1
66
60
 
67
- case uri.scheme
68
- when "ssl"
69
- log "* Starting control server on #{str}"
70
- params = Util.parse_query uri.query
71
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
-
73
- control.add_ssl_listener uri.host, uri.port, ctx
74
- when "tcp"
75
- log "* Starting control server on #{str}"
76
- control.add_tcp_listener uri.host, uri.port
77
- when "unix"
78
- log "* Starting control server on #{str}"
79
- path = "#{uri.host}#{uri.path}"
80
- mask = @options[:control_url_umask]
81
-
82
- control.add_unix_listener path, mask
83
- else
84
- error "Invalid control URI: #{str}"
85
- end
61
+ control.binder.parse [str], self, 'Starting control server'
86
62
 
87
63
  control.run
88
64
  @control = control
89
65
  end
90
66
 
67
+ def close_control_listeners
68
+ @control.binder.close_listeners if @control
69
+ end
70
+
91
71
  def ruby_engine
92
72
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
73
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -108,10 +88,6 @@ module Puma
108
88
  log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
109
89
  log "* Min threads: #{min_t}, max threads: #{max_t}"
110
90
  log "* Environment: #{ENV['RACK_ENV']}"
111
-
112
- if @options[:mode] == :tcp
113
- log "* Mode: Lopez Express (tcp)"
114
- end
115
91
  end
116
92
 
117
93
  def redirected_io?
@@ -150,7 +126,6 @@ module Puma
150
126
  exit 1
151
127
  end
152
128
 
153
- # Load the app before we daemonize.
154
129
  begin
155
130
  @app = @launcher.config.app
156
131
  rescue Exception => e
@@ -174,10 +149,6 @@ module Puma
174
149
  server.max_threads = max_t
175
150
  server.inherit_binder @launcher.binder
176
151
 
177
- if @options[:mode] == :tcp
178
- server.tcp_mode!
179
- end
180
-
181
152
  if @options[:early_hints]
182
153
  server.early_hints = true
183
154
  end
data/lib/puma/server.rb CHANGED
@@ -11,6 +11,7 @@ require 'puma/client'
11
11
  require 'puma/binder'
12
12
  require 'puma/accept_nonblock'
13
13
  require 'puma/util'
14
+ require 'puma/io_buffer'
14
15
 
15
16
  require 'puma/puma_http11'
16
17
 
@@ -36,6 +37,7 @@ module Puma
36
37
 
37
38
  attr_reader :thread
38
39
  attr_reader :events
40
+ attr_reader :requests_count
39
41
  attr_accessor :app
40
42
 
41
43
  attr_accessor :min_threads
@@ -57,8 +59,7 @@ module Puma
57
59
  @app = app
58
60
  @events = events
59
61
 
60
- @check, @notify = Puma::Util.pipe
61
-
62
+ @check, @notify = nil
62
63
  @status = :stop
63
64
 
64
65
  @min_threads = 0
@@ -85,20 +86,18 @@ module Puma
85
86
  @mode = :http
86
87
 
87
88
  @precheck_closing = true
89
+
90
+ @requests_count = 0
88
91
  end
89
92
 
90
93
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
94
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
95
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
96
 
94
97
  def inherit_binder(bind)
95
98
  @binder = bind
96
99
  end
97
100
 
98
- def tcp_mode!
99
- @mode = :tcp
100
- end
101
-
102
101
  # On Linux, use TCP_CORK to better control how the TCP stack
103
102
  # packetizes our stream. This improves both latency and throughput.
104
103
  #
@@ -172,107 +171,6 @@ module Puma
172
171
  @thread_pool and @thread_pool.pool_capacity
173
172
  end
174
173
 
175
- # Lopez Mode == raw tcp apps
176
-
177
- def run_lopez_mode(background=true)
178
- @thread_pool = ThreadPool.new(@min_threads,
179
- @max_threads,
180
- Hash) do |client, tl|
181
-
182
- io = client.to_io
183
- addr = io.peeraddr.last
184
-
185
- if addr.empty?
186
- # Set unix socket addrs to localhost
187
- addr = "127.0.0.1:0"
188
- else
189
- addr = "#{addr}:#{io.peeraddr[1]}"
190
- end
191
-
192
- env = { 'thread' => tl, REMOTE_ADDR => addr }
193
-
194
- begin
195
- @app.call env, client.to_io
196
- rescue Object => e
197
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
198
- STDERR.puts e.backtrace
199
- end
200
-
201
- client.close unless env['detach']
202
- end
203
-
204
- @events.fire :state, :running
205
-
206
- if background
207
- @thread = Thread.new do
208
- Puma.set_thread_name "server"
209
- handle_servers_lopez_mode
210
- end
211
- return @thread
212
- else
213
- handle_servers_lopez_mode
214
- end
215
- end
216
-
217
- def handle_servers_lopez_mode
218
- begin
219
- check = @check
220
- sockets = [check] + @binder.ios
221
- pool = @thread_pool
222
-
223
- while @status == :run
224
- begin
225
- ios = IO.select sockets
226
- ios.first.each do |sock|
227
- if sock == check
228
- break if handle_check
229
- else
230
- begin
231
- if io = sock.accept_nonblock
232
- client = Client.new io, nil
233
- pool << client
234
- end
235
- rescue SystemCallError
236
- # nothing
237
- rescue Errno::ECONNABORTED
238
- # client closed the socket even before accept
239
- begin
240
- io.close
241
- rescue
242
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
- end
244
- end
245
- end
246
- end
247
- rescue Object => e
248
- @events.unknown_error self, e, "Listen loop"
249
- end
250
- end
251
-
252
- @events.fire :state, @status
253
-
254
- graceful_shutdown if @status == :stop || @status == :restart
255
-
256
- rescue Exception => e
257
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
258
- STDERR.puts e.backtrace
259
- ensure
260
- begin
261
- @check.close
262
- rescue
263
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
- end
265
-
266
- # Prevent can't modify frozen IOError (RuntimeError)
267
- begin
268
- @notify.close
269
- rescue IOError
270
- # no biggy
271
- end
272
- end
273
-
274
- @events.fire :state, :done
275
- end
276
174
  # Runs the server.
277
175
  #
278
176
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +184,9 @@ module Puma
286
184
 
287
185
  @status = :run
288
186
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
187
  @thread_pool = ThreadPool.new(@min_threads,
296
188
  @max_threads,
297
- IOBuffer) do |client, buffer|
189
+ ::Puma::IOBuffer) do |client, buffer|
298
190
 
299
191
  # Advertise this server into the thread
300
192
  Thread.current[ThreadLocalKey] = self
@@ -302,10 +194,10 @@ module Puma
302
194
  process_now = false
303
195
 
304
196
  begin
305
- if queue_requests
197
+ if @queue_requests
306
198
  process_now = client.eagerly_finish
307
199
  else
308
- client.finish
200
+ client.finish(@first_data_timeout)
309
201
  process_now = true
310
202
  end
311
203
  rescue MiniSSL::SSLError => e
@@ -320,10 +212,6 @@ module Puma
320
212
  client.write_error(400)
321
213
  client.close
322
214
 
323
- @events.parse_error self, client.env, e
324
- rescue HttpParserError501 => e
325
- client.write_error(501)
326
- client.close
327
215
  @events.parse_error self, client.env, e
328
216
  rescue ConnectionError, EOFError
329
217
  client.close
@@ -335,11 +223,14 @@ module Puma
335
223
  @reactor.add client
336
224
  end
337
225
  end
226
+
227
+ process_now
338
228
  end
339
229
 
230
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
340
231
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
341
232
 
342
- if queue_requests
233
+ if @queue_requests
343
234
  @reactor = Reactor.new self, @thread_pool
344
235
  @reactor.run_in_thread
345
236
  end
@@ -366,6 +257,7 @@ module Puma
366
257
  end
367
258
 
368
259
  def handle_servers
260
+ @check, @notify = Puma::Util.pipe unless @notify
369
261
  begin
370
262
  check = @check
371
263
  sockets = [check] + @binder.ios
@@ -390,6 +282,10 @@ module Puma
390
282
  break if handle_check
391
283
  else
392
284
  begin
285
+ pool.wait_until_not_full
286
+ pool.wait_for_less_busy_worker(
287
+ @options[:wait_for_less_busy_worker].to_f)
288
+
393
289
  if io = sock.accept_nonblock
394
290
  client = Client.new io, @binder.env(sock)
395
291
  if remote_addr_value
@@ -399,10 +295,6 @@ module Puma
399
295
  end
400
296
 
401
297
  pool << client
402
- busy_threads = pool.wait_until_not_full
403
- if busy_threads == 0
404
- @options[:out_of_band].each(&:call) if @options[:out_of_band]
405
- end
406
298
  end
407
299
  rescue SystemCallError
408
300
  # nothing
@@ -423,17 +315,20 @@ module Puma
423
315
 
424
316
  @events.fire :state, @status
425
317
 
426
- graceful_shutdown if @status == :stop || @status == :restart
427
318
  if queue_requests
319
+ @queue_requests = false
428
320
  @reactor.clear!
429
321
  @reactor.shutdown
430
322
  end
323
+ graceful_shutdown if @status == :stop || @status == :restart
431
324
  rescue Exception => e
432
325
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
433
326
  STDERR.puts e.backtrace
434
327
  ensure
435
- @check.close
328
+ @check.close unless @check.closed? # Ruby 2.2 issue
436
329
  @notify.close
330
+ @notify = nil
331
+ @check = nil
437
332
  end
438
333
 
439
334
  @events.fire :state, :done
@@ -480,29 +375,24 @@ module Puma
480
375
  close_socket = false
481
376
  return
482
377
  when true
483
- return unless @queue_requests
484
378
  buffer.reset
485
379
 
486
380
  ThreadPool.clean_thread_locals if clean_thread_locals
487
381
 
488
382
  requests += 1
489
383
 
490
- # Closing keepalive sockets after they've made a reasonable
491
- # number of requests allows Puma to service many connections
492
- # fairly, even when the number of concurrent connections exceeds
493
- # the size of the threadpool. It also allows cluster mode Pumas
494
- # to keep load evenly distributed across workers, because clients
495
- # are randomly assigned a new worker when opening a new connection.
496
- #
497
- # Previously, Puma would kick connections in this conditional back
498
- # to the reactor. However, because this causes the todo set to increase
499
- # in size, the wait_until_full mutex would never unlock, leaving
500
- # any additional connections unserviced.
501
- break if requests >= MAX_FAST_INLINE
502
-
503
384
  check_for_more_data = @status == :run
504
385
 
386
+ if requests >= MAX_FAST_INLINE
387
+ # This will mean that reset will only try to use the data it already
388
+ # has buffered and won't try to read more data. What this means is that
389
+ # every client, independent of their request speed, gets treated like a slow
390
+ # one once every MAX_FAST_INLINE requests.
391
+ check_for_more_data = false
392
+ end
393
+
505
394
  unless client.reset(check_for_more_data)
395
+ return unless @queue_requests
506
396
  close_socket = false
507
397
  client.set_timeout @persistent_timeout
508
398
  @reactor.add client
@@ -534,12 +424,7 @@ module Puma
534
424
  client.write_error(400)
535
425
 
536
426
  @events.parse_error self, client.env, e
537
- rescue HttpParserError501 => e
538
- lowlevel_error(e, client.env)
539
427
 
540
- client.write_error(501)
541
-
542
- @events.parse_error self, client.env, e
543
428
  # Server error
544
429
  rescue StandardError => e
545
430
  lowlevel_error(e, client.env)
@@ -640,6 +525,8 @@ module Puma
640
525
  #
641
526
  # Finally, it'll return +true+ on keep-alive connections.
642
527
  def handle_request(req, lines)
528
+ @requests_count +=1
529
+
643
530
  env = req.env
644
531
  client = req.io
645
532
 
@@ -686,37 +573,6 @@ module Puma
686
573
  }
687
574
  end
688
575
 
689
- # Fixup any headers with , in the name to have _ now. We emit
690
- # headers with , in them during the parse phase to avoid ambiguity
691
- # with the - to _ conversion for critical headers. But here for
692
- # compatibility, we'll convert them back. This code is written to
693
- # avoid allocation in the common case (ie there are no headers
694
- # with , in their names), that's why it has the extra conditionals.
695
-
696
- to_delete = nil
697
- to_add = nil
698
-
699
- env.each do |k,v|
700
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
701
- if to_delete
702
- to_delete << k
703
- else
704
- to_delete = [k]
705
- end
706
-
707
- unless to_add
708
- to_add = {}
709
- end
710
-
711
- to_add[k.tr(",", "_")] = v
712
- end
713
- end
714
-
715
- if to_delete
716
- to_delete.each { |k| env.delete(k) }
717
- env.merge! to_add
718
- end
719
-
720
576
  # A rack extension. If the app writes #call'ables to this
721
577
  # array, we will invoke them when the request is done.
722
578
  #
@@ -738,17 +594,14 @@ module Puma
738
594
  return :async
739
595
  end
740
596
  rescue ThreadPool::ForceShutdown => e
741
- @events.log "Detected force shutdown of a thread, returning 503"
742
- @events.unknown_error self, e, "Rack app"
743
-
744
- status = 503
745
- headers = {}
746
- res_body = ["Request was internally terminated early\n"]
597
+ @events.unknown_error self, e, "Rack app", env
598
+ @events.log "Detected force shutdown of a thread"
747
599
 
600
+ status, headers, res_body = lowlevel_error(e, env, 503)
748
601
  rescue Exception => e
749
602
  @events.unknown_error self, e, "Rack app", env
750
603
 
751
- status, headers, res_body = lowlevel_error(e, env)
604
+ status, headers, res_body = lowlevel_error(e, env, 500)
752
605
  end
753
606
 
754
607
  content_length = nil
@@ -763,10 +616,10 @@ module Puma
763
616
  line_ending = LINE_END
764
617
  colon = COLON
765
618
 
766
- http_11 = if env[HTTP_VERSION] == HTTP_11
619
+ http_11 = env[HTTP_VERSION] == HTTP_11
620
+ if http_11
767
621
  allow_chunked = true
768
622
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
769
- include_keepalive_header = false
770
623
 
771
624
  # An optimization. The most common response is 200, so we can
772
625
  # reply with the proper 200 status without having to compute
@@ -780,11 +633,9 @@ module Puma
780
633
 
781
634
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
782
635
  end
783
- true
784
636
  else
785
637
  allow_chunked = false
786
638
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
787
- include_keepalive_header = keep_alive
788
639
 
789
640
  # Same optimization as above for HTTP/1.1
790
641
  #
@@ -796,9 +647,12 @@ module Puma
796
647
 
797
648
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
798
649
  end
799
- false
800
650
  end
801
651
 
652
+ # regardless of what the client wants, we always close the connection
653
+ # if running without request queueing
654
+ keep_alive &&= @queue_requests
655
+
802
656
  response_hijack = nil
803
657
 
804
658
  headers.each do |k, vs|
@@ -825,10 +679,15 @@ module Puma
825
679
  end
826
680
  end
827
681
 
828
- if include_keepalive_header
829
- lines << CONNECTION_KEEP_ALIVE
830
- elsif http_11 && !keep_alive
831
- lines << CONNECTION_CLOSE
682
+ # HTTP/1.1 & 1.0 assume different defaults:
683
+ # - HTTP 1.0 assumes the connection will be closed if not specified
684
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
685
+ # Only set the header if we're doing something which is not the default
686
+ # for this protocol version
687
+ if http_11
688
+ lines << CONNECTION_CLOSE if !keep_alive
689
+ else
690
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
832
691
  end
833
692
 
834
693
  if no_body
@@ -882,14 +741,11 @@ module Puma
882
741
  end
883
742
 
884
743
  ensure
885
- begin
886
- uncork_socket client
744
+ uncork_socket client
887
745
 
888
- body.close
889
- req.tempfile.unlink if req.tempfile
890
- ensure
891
- res_body.close if res_body.respond_to? :close
892
- end
746
+ body.close
747
+ req.tempfile.unlink if req.tempfile
748
+ res_body.close if res_body.respond_to? :close
893
749
 
894
750
  after_reply.each { |o| o.call }
895
751
  end
@@ -958,19 +814,21 @@ module Puma
958
814
 
959
815
  # A fallback rack response if +@app+ raises as exception.
960
816
  #
961
- def lowlevel_error(e, env)
817
+ def lowlevel_error(e, env, status=500)
962
818
  if handler = @options[:lowlevel_error_handler]
963
819
  if handler.arity == 1
964
820
  return handler.call(e)
965
- else
821
+ elsif handler.arity == 2
966
822
  return handler.call(e, env)
823
+ else
824
+ return handler.call(e, env, status)
967
825
  end
968
826
  end
969
827
 
970
828
  if @leak_stack_on_error
971
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
829
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
972
830
  else
973
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
831
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
974
832
  end
975
833
  end
976
834
 
@@ -1028,6 +886,7 @@ module Puma
1028
886
  end
1029
887
 
1030
888
  def notify_safely(message)
889
+ @check, @notify = Puma::Util.pipe unless @notify
1031
890
  begin
1032
891
  @notify << message
1033
892
  rescue IOError
@@ -1057,8 +916,9 @@ module Puma
1057
916
  @thread.join if @thread && sync
1058
917
  end
1059
918
 
1060
- def begin_restart
919
+ def begin_restart(sync=false)
1061
920
  notify_safely(RESTART_COMMAND)
921
+ @thread.join if @thread && sync
1062
922
  end
1063
923
 
1064
924
  def fast_write(io, str)
@@ -1096,5 +956,13 @@ module Puma
1096
956
  HTTP_INJECTION_REGEX =~ header_value.to_s
1097
957
  end
1098
958
  private :possible_header_injection?
959
+
960
+ # List of methods invoked by #stats.
961
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
962
+
963
+ # Returns a hash of stats about the running server for reporting purposes.
964
+ def stats
965
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
966
+ end
1099
967
  end
1100
968
  end
data/lib/puma/single.rb CHANGED
@@ -14,11 +14,9 @@ module Puma
14
14
  # that this inherits from.
15
15
  class Single < Runner
16
16
  def stats
17
- b = @server.backlog || 0
18
- r = @server.running || 0
19
- t = @server.pool_capacity || 0
20
- m = @server.max_threads || 0
21
- %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
17
+ {
18
+ started_at: @started_at.utc.iso8601
19
+ }.merge(@server.stats)
22
20
  end
23
21
 
24
22
  def restart
@@ -39,64 +37,10 @@ module Puma
39
37
  @server.stop(true) if @server
40
38
  end
41
39
 
42
- def jruby_daemon?
43
- daemon? and Puma.jruby?
44
- end
45
-
46
- def jruby_daemon_start
47
- require 'puma/jruby_restart'
48
- JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
49
- end
50
-
51
40
  def run
52
- already_daemon = false
53
-
54
- if jruby_daemon?
55
- require 'puma/jruby_restart'
56
-
57
- if JRubyRestart.daemon?
58
- # load and bind before redirecting IO so errors show up on stdout/stderr
59
- load_and_bind
60
- redirect_io
61
- end
62
-
63
- already_daemon = JRubyRestart.daemon_init
64
- end
65
-
66
41
  output_header "single"
67
42
 
68
- if jruby_daemon?
69
- if already_daemon
70
- JRubyRestart.perm_daemonize
71
- else
72
- pid = nil
73
-
74
- Signal.trap "SIGUSR2" do
75
- log "* Started new process #{pid} as daemon..."
76
-
77
- # Must use exit! so we don't unwind and run the ensures
78
- # that will be run by the new child (such as deleting the
79
- # pidfile)
80
- exit!(true)
81
- end
82
-
83
- Signal.trap "SIGCHLD" do
84
- log "! Error starting new process as daemon, exiting"
85
- exit 1
86
- end
87
-
88
- jruby_daemon_start
89
- sleep
90
- end
91
- else
92
- if daemon?
93
- log "* Daemonizing..."
94
- Process.daemon(true)
95
- redirect_io
96
- end
97
-
98
- load_and_bind
99
- end
43
+ load_and_bind
100
44
 
101
45
  Plugins.fire_background
102
46
 
@@ -106,10 +50,9 @@ module Puma
106
50
 
107
51
  @server = server = start_server
108
52
 
109
- unless daemon?
110
- log "Use Ctrl-C to stop"
111
- redirect_io
112
- end
53
+
54
+ log "Use Ctrl-C to stop"
55
+ redirect_io
113
56
 
114
57
  @launcher.events.fire_on_booted!
115
58
 
@@ -8,8 +8,11 @@ module Puma
8
8
  @options = {}
9
9
  end
10
10
 
11
- def save(path)
12
- File.write path, YAML.dump(@options)
11
+ def save(path, permission = nil)
12
+ File.open(path, "w") do |file|
13
+ file.chmod(permission) if permission
14
+ file.write(YAML.dump(@options))
15
+ end
13
16
  end
14
17
 
15
18
  def load(path)