puma 4.3.12 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1526 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +44 -10
  28. data/ext/puma_http11/http11_parser.c +45 -47
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +0 -0
  33. data/ext/puma_http11/mini_ssl.c +225 -89
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +50 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +146 -84
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +22 -7
  49. data/lib/puma/control_cli.rb +99 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +368 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +128 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +137 -50
  60. data/lib/puma/null_io.rb +18 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +489 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +292 -763
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +48 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +32 -4
  76. data/lib/puma.rb +48 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/lib/rack/version_restriction.rb +15 -0
  79. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  80. data/tools/trickletest.rb +0 -0
  81. metadata +29 -24
  82. data/docs/tcp_mode.md +0 -96
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  85. data/lib/puma/accept_nonblock.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -41
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
@@ -0,0 +1,489 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+
5
+ # The methods here are included in Server, but are separated into this file.
6
+ # All the methods here pertain to passing the request to the app, then
7
+ # writing the response back to the client.
8
+ #
9
+ # None of the methods here are called externally, with the exception of
10
+ # #handle_request, which is called in Server#process_client.
11
+ # @version 5.0.3
12
+ #
13
+ module Request
14
+
15
+ include Puma::Const
16
+
17
+ # Takes the request contained in +client+, invokes the Rack application to construct
18
+ # the response and writes it back to +client.io+.
19
+ #
20
+ # It'll return +false+ when the connection is closed, this doesn't mean
21
+ # that the response wasn't successful.
22
+ #
23
+ # It'll return +:async+ if the connection remains open but will be handled
24
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
25
+ #
26
+ # Finally, it'll return +true+ on keep-alive connections.
27
+ # @param client [Puma::Client]
28
+ # @param lines [Puma::IOBuffer]
29
+ # @param requests [Integer]
30
+ # @return [Boolean,:async]
31
+ #
32
+ def handle_request(client, lines, requests)
33
+ env = client.env
34
+ io = client.io # io may be a MiniSSL::Socket
35
+
36
+ return false if closed_socket?(io)
37
+
38
+ normalize_env env, client
39
+
40
+ env[PUMA_SOCKET] = io
41
+
42
+ if env[HTTPS_KEY] && io.peercert
43
+ env[PUMA_PEERCERT] = io.peercert
44
+ end
45
+
46
+ env[HIJACK_P] = true
47
+ env[HIJACK] = client
48
+
49
+ body = client.body
50
+
51
+ head = env[REQUEST_METHOD] == HEAD
52
+
53
+ env[RACK_INPUT] = body
54
+ env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
55
+
56
+ if @early_hints
57
+ env[EARLY_HINTS] = lambda { |headers|
58
+ begin
59
+ fast_write io, str_early_hints(headers)
60
+ rescue ConnectionError => e
61
+ @events.debug_error e
62
+ # noop, if we lost the socket we just won't send the early hints
63
+ end
64
+ }
65
+ end
66
+
67
+ req_env_post_parse env
68
+
69
+ # A rack extension. If the app writes #call'ables to this
70
+ # array, we will invoke them when the request is done.
71
+ #
72
+ after_reply = env[RACK_AFTER_REPLY] = []
73
+
74
+ begin
75
+ begin
76
+ status, headers, res_body = @thread_pool.with_force_shutdown do
77
+ @app.call(env)
78
+ end
79
+
80
+ return :async if client.hijacked
81
+
82
+ status = status.to_i
83
+
84
+ if status == -1
85
+ unless headers.empty? and res_body == []
86
+ raise "async response must have empty headers and body"
87
+ end
88
+
89
+ return :async
90
+ end
91
+ rescue ThreadPool::ForceShutdown => e
92
+ @events.unknown_error e, client, "Rack app"
93
+ @events.log "Detected force shutdown of a thread"
94
+
95
+ status, headers, res_body = lowlevel_error(e, env, 503)
96
+ rescue Exception => e
97
+ @events.unknown_error e, client, "Rack app"
98
+
99
+ status, headers, res_body = lowlevel_error(e, env, 500)
100
+ end
101
+
102
+ res_info = {}
103
+ res_info[:content_length] = nil
104
+ res_info[:no_body] = head
105
+
106
+ res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1
107
+ res_body[0].bytesize
108
+ else
109
+ nil
110
+ end
111
+
112
+ cork_socket io
113
+
114
+ str_headers(env, status, headers, res_info, lines, requests, client)
115
+
116
+ line_ending = LINE_END
117
+
118
+ content_length = res_info[:content_length]
119
+ response_hijack = res_info[:response_hijack]
120
+
121
+ if res_info[:no_body]
122
+ if content_length and status != 204
123
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
124
+ end
125
+
126
+ lines << LINE_END
127
+ fast_write io, lines.to_s
128
+ return res_info[:keep_alive]
129
+ end
130
+
131
+ if content_length
132
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
133
+ chunked = false
134
+ elsif !response_hijack and res_info[:allow_chunked]
135
+ lines << TRANSFER_ENCODING_CHUNKED
136
+ chunked = true
137
+ end
138
+
139
+ lines << line_ending
140
+
141
+ fast_write io, lines.to_s
142
+
143
+ if response_hijack
144
+ response_hijack.call io
145
+ return :async
146
+ end
147
+
148
+ begin
149
+ res_body.each do |part|
150
+ next if part.bytesize.zero?
151
+ if chunked
152
+ fast_write io, (part.bytesize.to_s(16) << line_ending)
153
+ fast_write io, part # part may have different encoding
154
+ fast_write io, line_ending
155
+ else
156
+ fast_write io, part
157
+ end
158
+ io.flush
159
+ end
160
+
161
+ if chunked
162
+ fast_write io, CLOSE_CHUNKED
163
+ io.flush
164
+ end
165
+ rescue SystemCallError, IOError
166
+ raise ConnectionError, "Connection error detected during write"
167
+ end
168
+
169
+ ensure
170
+ begin
171
+ uncork_socket io
172
+
173
+ body.close
174
+ client.tempfile.unlink if client.tempfile
175
+ ensure
176
+ # Whatever happens, we MUST call `close` on the response body.
177
+ # Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
178
+ res_body.close if res_body.respond_to? :close
179
+ end
180
+
181
+ begin
182
+ after_reply.each { |o| o.call }
183
+ rescue StandardError => e
184
+ @log_writer.debug_error e
185
+ end
186
+ end
187
+
188
+ res_info[:keep_alive]
189
+ end
190
+
191
+ # @param env [Hash] see Puma::Client#env, from request
192
+ # @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
193
+ #
194
+ def default_server_port(env)
195
+ if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
196
+ PORT_443
197
+ else
198
+ PORT_80
199
+ end
200
+ end
201
+
202
+ # Writes to an io (normally Client#io) using #syswrite
203
+ # @param io [#syswrite] the io to write to
204
+ # @param str [String] the string written to the io
205
+ # @raise [ConnectionError]
206
+ #
207
+ def fast_write(io, str)
208
+ n = 0
209
+ while true
210
+ begin
211
+ n = io.syswrite str
212
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
213
+ unless io.wait_writable WRITE_TIMEOUT
214
+ raise ConnectionError, "Socket timeout writing data"
215
+ end
216
+
217
+ retry
218
+ rescue Errno::EPIPE, SystemCallError, IOError
219
+ raise ConnectionError, "Socket timeout writing data"
220
+ end
221
+
222
+ return if n == str.bytesize
223
+ str = str.byteslice(n..-1)
224
+ end
225
+ end
226
+ private :fast_write
227
+
228
+ # @param status [Integer] status from the app
229
+ # @return [String] the text description from Puma::HTTP_STATUS_CODES
230
+ #
231
+ def fetch_status_code(status)
232
+ HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
233
+ end
234
+ private :fetch_status_code
235
+
236
+ # Given a Hash +env+ for the request read from +client+, add
237
+ # and fixup keys to comply with Rack's env guidelines.
238
+ # @param env [Hash] see Puma::Client#env, from request
239
+ # @param client [Puma::Client] only needed for Client#peerip
240
+ # @todo make private in 6.0.0
241
+ #
242
+ def normalize_env(env, client)
243
+ if host = env[HTTP_HOST]
244
+ # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
245
+ if colon = host.rindex("]:") # IPV6 with port
246
+ env[SERVER_NAME] = host[0, colon+1]
247
+ env[SERVER_PORT] = host[colon+2, host.bytesize]
248
+ elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
249
+ env[SERVER_NAME] = host[0, colon]
250
+ env[SERVER_PORT] = host[colon+1, host.bytesize]
251
+ else
252
+ env[SERVER_NAME] = host
253
+ env[SERVER_PORT] = default_server_port(env)
254
+ end
255
+ else
256
+ env[SERVER_NAME] = LOCALHOST
257
+ env[SERVER_PORT] = default_server_port(env)
258
+ end
259
+
260
+ unless env[REQUEST_PATH]
261
+ # it might be a dumbass full host request header
262
+ uri = URI.parse(env[REQUEST_URI])
263
+ env[REQUEST_PATH] = uri.path
264
+
265
+ raise "No REQUEST PATH" unless env[REQUEST_PATH]
266
+
267
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
268
+ # so only set the env value if there actually is a value.
269
+ env[QUERY_STRING] = uri.query if uri.query
270
+ end
271
+
272
+ env[PATH_INFO] = env[REQUEST_PATH]
273
+
274
+ # From https://www.ietf.org/rfc/rfc3875 :
275
+ # "Script authors should be aware that the REMOTE_ADDR and
276
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
277
+ # may not identify the ultimate source of the request.
278
+ # They identify the client for the immediate request to the
279
+ # server; that client may be a proxy, gateway, or other
280
+ # intermediary acting on behalf of the actual source client."
281
+ #
282
+
283
+ unless env.key?(REMOTE_ADDR)
284
+ begin
285
+ addr = client.peerip
286
+ rescue Errno::ENOTCONN
287
+ # Client disconnects can result in an inability to get the
288
+ # peeraddr from the socket; default to localhost.
289
+ addr = LOCALHOST_IP
290
+ end
291
+
292
+ # Set unix socket addrs to localhost
293
+ addr = LOCALHOST_IP if addr.empty?
294
+
295
+ env[REMOTE_ADDR] = addr
296
+ end
297
+ end
298
+ # private :normalize_env
299
+
300
+ # @param header_key [#to_s]
301
+ # @return [Boolean]
302
+ #
303
+ def illegal_header_key?(header_key)
304
+ !!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
305
+ end
306
+
307
+ # @param header_value [#to_s]
308
+ # @return [Boolean]
309
+ #
310
+ def illegal_header_value?(header_value)
311
+ !!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
312
+ end
313
+ private :illegal_header_key?, :illegal_header_value?
314
+
315
+ # Fixup any headers with `,` in the name to have `_` now. We emit
316
+ # headers with `,` in them during the parse phase to avoid ambiguity
317
+ # with the `-` to `_` conversion for critical headers. But here for
318
+ # compatibility, we'll convert them back. This code is written to
319
+ # avoid allocation in the common case (ie there are no headers
320
+ # with `,` in their names), that's why it has the extra conditionals.
321
+ #
322
+ # @note If a normalized version of a `,` header already exists, we ignore
323
+ # the `,` version. This prevents clobbering headers managed by proxies
324
+ # but not by clients (Like X-Forwarded-For).
325
+ #
326
+ # @param env [Hash] see Puma::Client#env, from request, modifies in place
327
+ # @version 5.0.3
328
+ #
329
+ def req_env_post_parse(env)
330
+ to_delete = nil
331
+ to_add = nil
332
+
333
+ env.each do |k,v|
334
+ if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
335
+ if to_delete
336
+ to_delete << k
337
+ else
338
+ to_delete = [k]
339
+ end
340
+
341
+ new_k = k.tr(",", "_")
342
+ if env.key?(new_k)
343
+ next
344
+ end
345
+
346
+ unless to_add
347
+ to_add = {}
348
+ end
349
+
350
+ to_add[new_k] = v
351
+ end
352
+ end
353
+
354
+ if to_delete # rubocop:disable Style/SafeNavigation
355
+ to_delete.each { |k| env.delete(k) }
356
+ end
357
+
358
+ if to_add
359
+ env.merge! to_add
360
+ end
361
+ end
362
+ private :req_env_post_parse
363
+
364
+ # Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
365
+ # @param headers [Hash] the headers returned by the Rack application
366
+ # @return [String]
367
+ # @version 5.0.3
368
+ #
369
+ def str_early_hints(headers)
370
+ eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
371
+ headers.each_pair do |k, vs|
372
+ next if illegal_header_key?(k)
373
+
374
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
375
+ vs.to_s.split(NEWLINE).each do |v|
376
+ next if illegal_header_value?(v)
377
+ eh_str << "#{k}: #{v}\r\n"
378
+ end
379
+ else
380
+ eh_str << "#{k}: #{vs}\r\n"
381
+ end
382
+ end
383
+ "#{eh_str}\r\n".freeze
384
+ end
385
+ private :str_early_hints
386
+
387
+ # Processes and write headers to the IOBuffer.
388
+ # @param env [Hash] see Puma::Client#env, from request
389
+ # @param status [Integer] the status returned by the Rack application
390
+ # @param headers [Hash] the headers returned by the Rack application
391
+ # @param res_info [Hash] used to pass info between this method and #handle_request
392
+ # @param lines [Puma::IOBuffer] modified inn place
393
+ # @param requests [Integer] number of inline requests handled
394
+ # @param client [Puma::Client]
395
+ # @version 5.0.3
396
+ #
397
+ def str_headers(env, status, headers, res_info, lines, requests, client)
398
+ line_ending = LINE_END
399
+ colon = COLON
400
+
401
+ http_11 = env[HTTP_VERSION] == HTTP_11
402
+ if http_11
403
+ res_info[:allow_chunked] = true
404
+ res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
405
+
406
+ # An optimization. The most common response is 200, so we can
407
+ # reply with the proper 200 status without having to compute
408
+ # the response header.
409
+ #
410
+ if status == 200
411
+ lines << HTTP_11_200
412
+ else
413
+ lines.append "HTTP/1.1 ", status.to_s, " ",
414
+ fetch_status_code(status), line_ending
415
+
416
+ res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
417
+ end
418
+ else
419
+ res_info[:allow_chunked] = false
420
+ res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
421
+
422
+ # Same optimization as above for HTTP/1.1
423
+ #
424
+ if status == 200
425
+ lines << HTTP_10_200
426
+ else
427
+ lines.append "HTTP/1.0 ", status.to_s, " ",
428
+ fetch_status_code(status), line_ending
429
+
430
+ res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
431
+ end
432
+ end
433
+
434
+ # regardless of what the client wants, we always close the connection
435
+ # if running without request queueing
436
+ res_info[:keep_alive] &&= @queue_requests
437
+
438
+ # Close the connection after a reasonable number of inline requests
439
+ # if the server is at capacity and the listener has a new connection ready.
440
+ # This allows Puma to service connections fairly when the number
441
+ # of concurrent connections exceeds the size of the threadpool.
442
+ res_info[:keep_alive] &&= requests < @max_fast_inline ||
443
+ @thread_pool.busy_threads < @max_threads ||
444
+ !client.listener.to_io.wait_readable(0)
445
+
446
+ res_info[:response_hijack] = nil
447
+
448
+ headers.each do |k, vs|
449
+ next if illegal_header_key?(k)
450
+
451
+ case k.downcase
452
+ when CONTENT_LENGTH2
453
+ next if illegal_header_value?(vs)
454
+ res_info[:content_length] = vs
455
+ next
456
+ when TRANSFER_ENCODING
457
+ res_info[:allow_chunked] = false
458
+ res_info[:content_length] = nil
459
+ when HIJACK
460
+ res_info[:response_hijack] = vs
461
+ next
462
+ when BANNED_HEADER_KEY
463
+ next
464
+ end
465
+
466
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
467
+ vs.to_s.split(NEWLINE).each do |v|
468
+ next if illegal_header_value?(v)
469
+ lines.append k, colon, v, line_ending
470
+ end
471
+ else
472
+ lines.append k, colon, line_ending
473
+ end
474
+ end
475
+
476
+ # HTTP/1.1 & 1.0 assume different defaults:
477
+ # - HTTP 1.0 assumes the connection will be closed if not specified
478
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
479
+ # Only set the header if we're doing something which is not the default
480
+ # for this protocol version
481
+ if http_11
482
+ lines << CONNECTION_CLOSE if !res_info[:keep_alive]
483
+ else
484
+ lines << CONNECTION_KEEP_ALIVE if res_info[:keep_alive]
485
+ end
486
+ end
487
+ private :str_headers
488
+ end
489
+ end
data/lib/puma/runner.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'puma/server'
4
4
  require 'puma/const'
5
- require 'puma/minissl/context_builder'
6
5
 
7
6
  module Puma
8
7
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
@@ -16,10 +15,16 @@ module Puma
16
15
  @app = nil
17
16
  @control = nil
18
17
  @started_at = Time.now
18
+ @wakeup = nil
19
19
  end
20
20
 
21
- def daemon?
22
- @options[:daemon]
21
+ def wakeup!
22
+ return unless @wakeup
23
+
24
+ @wakeup.write "!" unless @wakeup.closed?
25
+
26
+ rescue SystemCallError, IOError
27
+ Puma::Util.purge_interrupt_queue
23
28
  end
24
29
 
25
30
  def development?
@@ -34,7 +39,8 @@ module Puma
34
39
  @events.log str
35
40
  end
36
41
 
37
- def before_restart
42
+ # @version 5.0.0
43
+ def stop_control
38
44
  @control.stop(true) if @control
39
45
  end
40
46
 
@@ -52,42 +58,27 @@ module Puma
52
58
 
53
59
  require 'puma/app/status'
54
60
 
55
- uri = URI.parse str
56
-
57
61
  if token = @options[:control_auth_token]
58
62
  token = nil if token.empty? || token == 'none'
59
63
  end
60
64
 
61
65
  app = Puma::App::Status.new @launcher, token
62
66
 
63
- control = Puma::Server.new app, @launcher.events
64
- control.min_threads = 0
65
- control.max_threads = 1
66
-
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
67
+ control = Puma::Server.new app, @launcher.events,
68
+ { min_threads: 0, max_threads: 1, queue_requests: false }
69
+
70
+ control.binder.parse [str], self, 'Starting control server'
86
71
 
87
- control.run
72
+ control.run thread_name: 'ctl'
88
73
  @control = control
89
74
  end
90
75
 
76
+ # @version 5.0.0
77
+ def close_control_listeners
78
+ @control.binder.close_listeners if @control
79
+ end
80
+
81
+ # @!attribute [r] ruby_engine
91
82
  def ruby_engine
92
83
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
84
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -103,14 +94,18 @@ module Puma
103
94
  def output_header(mode)
104
95
  min_t = @options[:min_threads]
105
96
  max_t = @options[:max_threads]
97
+ environment = @options[:environment]
106
98
 
107
99
  log "Puma starting in #{mode} mode..."
108
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
109
- log "* Min threads: #{min_t}, max threads: #{max_t}"
110
- log "* Environment: #{ENV['RACK_ENV']}"
100
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
101
+ log "* Min threads: #{min_t}"
102
+ log "* Max threads: #{max_t}"
103
+ log "* Environment: #{environment}"
111
104
 
112
- if @options[:mode] == :tcp
113
- log "* Mode: Lopez Express (tcp)"
105
+ if mode == "cluster"
106
+ log "* Master PID: #{Process.pid}"
107
+ else
108
+ log "* PID: #{Process.pid}"
114
109
  end
115
110
  end
116
111
 
@@ -124,23 +119,24 @@ module Puma
124
119
  append = @options[:redirect_append]
125
120
 
126
121
  if stdout
127
- unless Dir.exist?(File.dirname(stdout))
128
- raise "Cannot redirect STDOUT to #{stdout}"
129
- end
122
+ ensure_output_directory_exists(stdout, 'STDOUT')
130
123
 
131
124
  STDOUT.reopen stdout, (append ? "a" : "w")
132
- STDOUT.sync = true
133
125
  STDOUT.puts "=== puma startup: #{Time.now} ==="
126
+ STDOUT.flush unless STDOUT.sync
134
127
  end
135
128
 
136
129
  if stderr
137
- unless Dir.exist?(File.dirname(stderr))
138
- raise "Cannot redirect STDERR to #{stderr}"
139
- end
130
+ ensure_output_directory_exists(stderr, 'STDERR')
140
131
 
141
132
  STDERR.reopen stderr, (append ? "a" : "w")
142
- STDERR.sync = true
143
133
  STDERR.puts "=== puma startup: #{Time.now} ==="
134
+ STDERR.flush unless STDERR.sync
135
+ end
136
+
137
+ if @options[:mutate_stdout_and_stderr_to_sync_on_write]
138
+ STDOUT.sync = true
139
+ STDERR.sync = true
144
140
  end
145
141
  end
146
142
 
@@ -150,7 +146,6 @@ module Puma
150
146
  exit 1
151
147
  end
152
148
 
153
- # Load the app before we daemonize.
154
149
  begin
155
150
  @app = @launcher.config.app
156
151
  rescue Exception => e
@@ -161,32 +156,22 @@ module Puma
161
156
  @launcher.binder.parse @options[:binds], self
162
157
  end
163
158
 
159
+ # @!attribute [r] app
164
160
  def app
165
161
  @app ||= @launcher.config.app
166
162
  end
167
163
 
168
164
  def start_server
169
- min_t = @options[:min_threads]
170
- max_t = @options[:max_threads]
171
-
172
165
  server = Puma::Server.new app, @launcher.events, @options
173
- server.min_threads = min_t
174
- server.max_threads = max_t
175
166
  server.inherit_binder @launcher.binder
167
+ server
168
+ end
176
169
 
177
- if @options[:mode] == :tcp
178
- server.tcp_mode!
179
- end
180
-
181
- if @options[:early_hints]
182
- server.early_hints = true
183
- end
184
-
185
- unless development? || test?
186
- server.leak_stack_on_error = false
170
+ private
171
+ def ensure_output_directory_exists(path, io_name)
172
+ unless Dir.exist?(File.dirname(path))
173
+ raise "Cannot redirect #{io_name} to #{path}"
187
174
  end
188
-
189
- server
190
175
  end
191
176
  end
192
177
  end