puma 4.3.6 → 5.6.4

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1486 -518
  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 +46 -9
  28. data/ext/puma_http11/http11_parser.c +68 -57
  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 +1 -1
  33. data/ext/puma_http11/mini_ssl.c +275 -122
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +47 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +174 -91
  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 +18 -9
  49. data/lib/puma/control_cli.rb +93 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +364 -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 +117 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +128 -46
  60. data/lib/puma/null_io.rb +13 -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 +472 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +287 -743
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +20 -1
  76. data/lib/puma.rb +46 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  79. data/tools/trickletest.rb +0 -0
  80. metadata +28 -24
  81. data/docs/tcp_mode.md +0 -96
  82. data/ext/puma_http11/io_buffer.c +0 -155
  83. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  84. data/lib/puma/accept_nonblock.rb +0 -29
  85. data/lib/puma/tcp_logger.rb +0 -41
  86. data/tools/jungle/README.md +0 -19
  87. data/tools/jungle/init.d/README.md +0 -61
  88. data/tools/jungle/init.d/puma +0 -421
  89. data/tools/jungle/init.d/run-puma +0 -18
  90. data/tools/jungle/upstart/README.md +0 -61
  91. data/tools/jungle/upstart/puma-manager.conf +0 -31
  92. data/tools/jungle/upstart/puma.conf +0 -69
@@ -0,0 +1,472 @@
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
+ after_reply.each { |o| o.call }
182
+ end
183
+
184
+ res_info[:keep_alive]
185
+ end
186
+
187
+ # @param env [Hash] see Puma::Client#env, from request
188
+ # @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
189
+ #
190
+ def default_server_port(env)
191
+ 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"
192
+ PORT_443
193
+ else
194
+ PORT_80
195
+ end
196
+ end
197
+
198
+ # Writes to an io (normally Client#io) using #syswrite
199
+ # @param io [#syswrite] the io to write to
200
+ # @param str [String] the string written to the io
201
+ # @raise [ConnectionError]
202
+ #
203
+ def fast_write(io, str)
204
+ n = 0
205
+ while true
206
+ begin
207
+ n = io.syswrite str
208
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
209
+ unless io.wait_writable WRITE_TIMEOUT
210
+ raise ConnectionError, "Socket timeout writing data"
211
+ end
212
+
213
+ retry
214
+ rescue Errno::EPIPE, SystemCallError, IOError
215
+ raise ConnectionError, "Socket timeout writing data"
216
+ end
217
+
218
+ return if n == str.bytesize
219
+ str = str.byteslice(n..-1)
220
+ end
221
+ end
222
+ private :fast_write
223
+
224
+ # @param status [Integer] status from the app
225
+ # @return [String] the text description from Puma::HTTP_STATUS_CODES
226
+ #
227
+ def fetch_status_code(status)
228
+ HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
229
+ end
230
+ private :fetch_status_code
231
+
232
+ # Given a Hash +env+ for the request read from +client+, add
233
+ # and fixup keys to comply with Rack's env guidelines.
234
+ # @param env [Hash] see Puma::Client#env, from request
235
+ # @param client [Puma::Client] only needed for Client#peerip
236
+ # @todo make private in 6.0.0
237
+ #
238
+ def normalize_env(env, client)
239
+ if host = env[HTTP_HOST]
240
+ # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
241
+ if colon = host.rindex("]:") # IPV6 with port
242
+ env[SERVER_NAME] = host[0, colon+1]
243
+ env[SERVER_PORT] = host[colon+2, host.bytesize]
244
+ elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
245
+ env[SERVER_NAME] = host[0, colon]
246
+ env[SERVER_PORT] = host[colon+1, host.bytesize]
247
+ else
248
+ env[SERVER_NAME] = host
249
+ env[SERVER_PORT] = default_server_port(env)
250
+ end
251
+ else
252
+ env[SERVER_NAME] = LOCALHOST
253
+ env[SERVER_PORT] = default_server_port(env)
254
+ end
255
+
256
+ unless env[REQUEST_PATH]
257
+ # it might be a dumbass full host request header
258
+ uri = URI.parse(env[REQUEST_URI])
259
+ env[REQUEST_PATH] = uri.path
260
+
261
+ raise "No REQUEST PATH" unless env[REQUEST_PATH]
262
+
263
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
264
+ # so only set the env value if there actually is a value.
265
+ env[QUERY_STRING] = uri.query if uri.query
266
+ end
267
+
268
+ env[PATH_INFO] = env[REQUEST_PATH]
269
+
270
+ # From https://www.ietf.org/rfc/rfc3875 :
271
+ # "Script authors should be aware that the REMOTE_ADDR and
272
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
273
+ # may not identify the ultimate source of the request.
274
+ # They identify the client for the immediate request to the
275
+ # server; that client may be a proxy, gateway, or other
276
+ # intermediary acting on behalf of the actual source client."
277
+ #
278
+
279
+ unless env.key?(REMOTE_ADDR)
280
+ begin
281
+ addr = client.peerip
282
+ rescue Errno::ENOTCONN
283
+ # Client disconnects can result in an inability to get the
284
+ # peeraddr from the socket; default to localhost.
285
+ addr = LOCALHOST_IP
286
+ end
287
+
288
+ # Set unix socket addrs to localhost
289
+ addr = LOCALHOST_IP if addr.empty?
290
+
291
+ env[REMOTE_ADDR] = addr
292
+ end
293
+ end
294
+ # private :normalize_env
295
+
296
+ # @param header_key [#to_s]
297
+ # @return [Boolean]
298
+ #
299
+ def illegal_header_key?(header_key)
300
+ !!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
301
+ end
302
+
303
+ # @param header_value [#to_s]
304
+ # @return [Boolean]
305
+ #
306
+ def illegal_header_value?(header_value)
307
+ !!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
308
+ end
309
+ private :illegal_header_key?, :illegal_header_value?
310
+
311
+ # Fixup any headers with `,` in the name to have `_` now. We emit
312
+ # headers with `,` in them during the parse phase to avoid ambiguity
313
+ # with the `-` to `_` conversion for critical headers. But here for
314
+ # compatibility, we'll convert them back. This code is written to
315
+ # avoid allocation in the common case (ie there are no headers
316
+ # with `,` in their names), that's why it has the extra conditionals.
317
+ # @param env [Hash] see Puma::Client#env, from request, modifies in place
318
+ # @version 5.0.3
319
+ #
320
+ def req_env_post_parse(env)
321
+ to_delete = nil
322
+ to_add = nil
323
+
324
+ env.each do |k,v|
325
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
326
+ if to_delete
327
+ to_delete << k
328
+ else
329
+ to_delete = [k]
330
+ end
331
+
332
+ unless to_add
333
+ to_add = {}
334
+ end
335
+
336
+ to_add[k.tr(",", "_")] = v
337
+ end
338
+ end
339
+
340
+ if to_delete
341
+ to_delete.each { |k| env.delete(k) }
342
+ env.merge! to_add
343
+ end
344
+ end
345
+ private :req_env_post_parse
346
+
347
+ # Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
348
+ # @param headers [Hash] the headers returned by the Rack application
349
+ # @return [String]
350
+ # @version 5.0.3
351
+ #
352
+ def str_early_hints(headers)
353
+ eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
354
+ headers.each_pair do |k, vs|
355
+ next if illegal_header_key?(k)
356
+
357
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
358
+ vs.to_s.split(NEWLINE).each do |v|
359
+ next if illegal_header_value?(v)
360
+ eh_str << "#{k}: #{v}\r\n"
361
+ end
362
+ else
363
+ eh_str << "#{k}: #{vs}\r\n"
364
+ end
365
+ end
366
+ "#{eh_str}\r\n".freeze
367
+ end
368
+ private :str_early_hints
369
+
370
+ # Processes and write headers to the IOBuffer.
371
+ # @param env [Hash] see Puma::Client#env, from request
372
+ # @param status [Integer] the status returned by the Rack application
373
+ # @param headers [Hash] the headers returned by the Rack application
374
+ # @param res_info [Hash] used to pass info between this method and #handle_request
375
+ # @param lines [Puma::IOBuffer] modified inn place
376
+ # @param requests [Integer] number of inline requests handled
377
+ # @param client [Puma::Client]
378
+ # @version 5.0.3
379
+ #
380
+ def str_headers(env, status, headers, res_info, lines, requests, client)
381
+ line_ending = LINE_END
382
+ colon = COLON
383
+
384
+ http_11 = env[HTTP_VERSION] == HTTP_11
385
+ if http_11
386
+ res_info[:allow_chunked] = true
387
+ res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
388
+
389
+ # An optimization. The most common response is 200, so we can
390
+ # reply with the proper 200 status without having to compute
391
+ # the response header.
392
+ #
393
+ if status == 200
394
+ lines << HTTP_11_200
395
+ else
396
+ lines.append "HTTP/1.1 ", status.to_s, " ",
397
+ fetch_status_code(status), line_ending
398
+
399
+ res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
400
+ end
401
+ else
402
+ res_info[:allow_chunked] = false
403
+ res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
404
+
405
+ # Same optimization as above for HTTP/1.1
406
+ #
407
+ if status == 200
408
+ lines << HTTP_10_200
409
+ else
410
+ lines.append "HTTP/1.0 ", status.to_s, " ",
411
+ fetch_status_code(status), line_ending
412
+
413
+ res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
414
+ end
415
+ end
416
+
417
+ # regardless of what the client wants, we always close the connection
418
+ # if running without request queueing
419
+ res_info[:keep_alive] &&= @queue_requests
420
+
421
+ # Close the connection after a reasonable number of inline requests
422
+ # if the server is at capacity and the listener has a new connection ready.
423
+ # This allows Puma to service connections fairly when the number
424
+ # of concurrent connections exceeds the size of the threadpool.
425
+ res_info[:keep_alive] &&= requests < @max_fast_inline ||
426
+ @thread_pool.busy_threads < @max_threads ||
427
+ !client.listener.to_io.wait_readable(0)
428
+
429
+ res_info[:response_hijack] = nil
430
+
431
+ headers.each do |k, vs|
432
+ next if illegal_header_key?(k)
433
+
434
+ case k.downcase
435
+ when CONTENT_LENGTH2
436
+ next if illegal_header_value?(vs)
437
+ res_info[:content_length] = vs
438
+ next
439
+ when TRANSFER_ENCODING
440
+ res_info[:allow_chunked] = false
441
+ res_info[:content_length] = nil
442
+ when HIJACK
443
+ res_info[:response_hijack] = vs
444
+ next
445
+ when BANNED_HEADER_KEY
446
+ next
447
+ end
448
+
449
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
450
+ vs.to_s.split(NEWLINE).each do |v|
451
+ next if illegal_header_value?(v)
452
+ lines.append k, colon, v, line_ending
453
+ end
454
+ else
455
+ lines.append k, colon, line_ending
456
+ end
457
+ end
458
+
459
+ # HTTP/1.1 & 1.0 assume different defaults:
460
+ # - HTTP 1.0 assumes the connection will be closed if not specified
461
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
462
+ # Only set the header if we're doing something which is not the default
463
+ # for this protocol version
464
+ if http_11
465
+ lines << CONNECTION_CLOSE if !res_info[:keep_alive]
466
+ else
467
+ lines << CONNECTION_KEEP_ALIVE if res_info[:keep_alive]
468
+ end
469
+ end
470
+ private :str_headers
471
+ end
472
+ 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