puma 3.12.1 → 5.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1414 -448
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +254 -91
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma.rb +54 -0
  37. data/lib/puma/app/status.rb +68 -49
  38. data/lib/puma/binder.rb +191 -139
  39. data/lib/puma/cli.rb +15 -15
  40. data/lib/puma/client.rb +257 -228
  41. data/lib/puma/cluster.rb +221 -212
  42. data/lib/puma/cluster/worker.rb +183 -0
  43. data/lib/puma/cluster/worker_handle.rb +90 -0
  44. data/lib/puma/commonlogger.rb +2 -2
  45. data/lib/puma/configuration.rb +58 -51
  46. data/lib/puma/const.rb +39 -19
  47. data/lib/puma/control_cli.rb +109 -67
  48. data/lib/puma/detect.rb +24 -3
  49. data/lib/puma/dsl.rb +519 -121
  50. data/lib/puma/error_logger.rb +104 -0
  51. data/lib/puma/events.rb +55 -31
  52. data/lib/puma/io_buffer.rb +7 -5
  53. data/lib/puma/jruby_restart.rb +0 -58
  54. data/lib/puma/json.rb +96 -0
  55. data/lib/puma/launcher.rb +178 -68
  56. data/lib/puma/minissl.rb +147 -48
  57. data/lib/puma/minissl/context_builder.rb +79 -0
  58. data/lib/puma/null_io.rb +13 -1
  59. data/lib/puma/plugin.rb +6 -12
  60. data/lib/puma/plugin/tmp_restart.rb +2 -0
  61. data/lib/puma/queue_close.rb +26 -0
  62. data/lib/puma/rack/builder.rb +2 -4
  63. data/lib/puma/rack/urlmap.rb +2 -0
  64. data/lib/puma/rack_default.rb +2 -0
  65. data/lib/puma/reactor.rb +85 -316
  66. data/lib/puma/request.rb +467 -0
  67. data/lib/puma/runner.rb +31 -52
  68. data/lib/puma/server.rb +282 -680
  69. data/lib/puma/single.rb +11 -67
  70. data/lib/puma/state_file.rb +8 -3
  71. data/lib/puma/systemd.rb +46 -0
  72. data/lib/puma/thread_pool.rb +129 -81
  73. data/lib/puma/util.rb +13 -6
  74. data/lib/rack/handler/puma.rb +5 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +42 -26
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -25
  82. data/lib/puma/daemon_ext.rb +0 -33
  83. data/lib/puma/delegation.rb +0 -13
  84. data/lib/puma/java_io_buffer.rb +0 -47
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  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
@@ -1,73 +1,92 @@
1
+ # frozen_string_literal: true
2
+ require 'puma/json'
3
+
1
4
  module Puma
2
5
  module App
6
+ # Check out {#call}'s source code to see what actions this web application
7
+ # can respond to.
3
8
  class Status
4
- def initialize(cli)
5
- @cli = cli
6
- @auth_token = nil
7
- end
8
9
  OK_STATUS = '{ "status": "ok" }'.freeze
9
10
 
10
- attr_accessor :auth_token
11
-
12
- def authenticate(env)
13
- return true unless @auth_token
14
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
15
- end
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]]
11
+ # @param launcher [::Puma::Launcher]
12
+ # @param token [String, nil] the token used for authentication
13
+ #
14
+ def initialize(launcher, token = nil)
15
+ @launcher = launcher
16
+ @auth_token = token
24
17
  end
25
18
 
19
+ # most commands call methods in `::Puma::Launcher` based on command in
20
+ # `env['PATH_INFO']`
26
21
  def call(env)
27
22
  unless authenticate(env)
28
23
  return rack_response(403, 'Invalid auth token', 'text/plain')
29
24
  end
30
25
 
31
- case env['PATH_INFO']
32
- when /\/stop$/
33
- @cli.stop
34
- return rack_response(200, OK_STATUS)
26
+ # resp_type is processed by following case statement, return
27
+ # is a number (status) or a string used as the body of a 200 response
28
+ resp_type =
29
+ case env['PATH_INFO'][/\/([^\/]+)$/, 1]
30
+ when 'stop'
31
+ @launcher.stop ; 200
35
32
 
36
- when /\/halt$/
37
- @cli.halt
38
- return rack_response(200, OK_STATUS)
33
+ when 'halt'
34
+ @launcher.halt ; 200
39
35
 
40
- when /\/restart$/
41
- @cli.restart
42
- return rack_response(200, OK_STATUS)
36
+ when 'restart'
37
+ @launcher.restart ; 200
43
38
 
44
- when /\/phased-restart$/
45
- if !@cli.phased_restart
46
- return rack_response(404, '{ "error": "phased restart not available" }')
47
- else
48
- return rack_response(200, OK_STATUS)
49
- end
39
+ when 'phased-restart'
40
+ @launcher.phased_restart ? 200 : 404
41
+
42
+ when 'reload-worker-directory'
43
+ @launcher.send(:reload_worker_directory) ? 200 : 404
44
+
45
+ when 'gc'
46
+ GC.start ; 200
47
+
48
+ when 'gc-stats'
49
+ Puma::JSON.generate GC.stat
50
+
51
+ when 'stats'
52
+ Puma::JSON.generate @launcher.stats
53
+
54
+ when 'thread-backtraces'
55
+ backtraces = []
56
+ @launcher.thread_status do |name, backtrace|
57
+ backtraces << { name: name, backtrace: backtrace }
58
+ end
59
+ Puma::JSON.generate backtraces
50
60
 
51
- when /\/reload-worker-directory$/
52
- if !@cli.send(:reload_worker_directory)
53
- return rack_response(404, '{ "error": "reload_worker_directory not available" }')
54
61
  else
55
- return rack_response(200, OK_STATUS)
62
+ return rack_response(404, "Unsupported action", 'text/plain')
56
63
  end
57
64
 
58
- when /\/gc$/
59
- GC.start
60
- return rack_response(200, OK_STATUS)
65
+ case resp_type
66
+ when String
67
+ rack_response 200, resp_type
68
+ when 200
69
+ rack_response 200, OK_STATUS
70
+ when 404
71
+ str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
72
+ rack_response 404, "{ \"error\": \"#{str} not available\" }"
73
+ end
74
+ end
61
75
 
62
- when /\/gc-stats$/
63
- json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
64
- return rack_response(200, json)
76
+ private
65
77
 
66
- when /\/stats$/
67
- return rack_response(200, @cli.stats)
68
- else
69
- rack_response 404, "Unsupported action", 'text/plain'
70
- end
78
+ def authenticate(env)
79
+ return true unless @auth_token
80
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
81
+ end
82
+
83
+ def rack_response(status, body, content_type='application/json')
84
+ headers = {
85
+ 'Content-Type' => content_type,
86
+ 'Content-Length' => body.bytesize.to_s
87
+ }
88
+
89
+ [status, headers, [body]]
71
90
  end
72
91
  end
73
92
  end
data/lib/puma/binder.rb CHANGED
@@ -5,14 +5,30 @@ require 'socket'
5
5
 
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
+ require 'puma/configuration'
8
9
 
9
10
  module Puma
11
+
12
+ if HAS_SSL
13
+ require 'puma/minissl'
14
+ require 'puma/minissl/context_builder'
15
+
16
+ # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
17
+ # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
18
+ # The bug was that it did not require openssl.
19
+ # @todo remove when Ruby 2.3 support is dropped
20
+ #
21
+ if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
22
+ require 'openssl'
23
+ end
24
+ end
25
+
10
26
  class Binder
11
27
  include Puma::Const
12
28
 
13
- RACK_VERSION = [1,3].freeze
29
+ RACK_VERSION = [1,6].freeze
14
30
 
15
- def initialize(events)
31
+ def initialize(events, conf = Configuration.new)
16
32
  @events = events
17
33
  @listeners = []
18
34
  @inherited_fds = {}
@@ -22,8 +38,8 @@ module Puma
22
38
  @proto_env = {
23
39
  "rack.version".freeze => RACK_VERSION,
24
40
  "rack.errors".freeze => events.stderr,
25
- "rack.multithread".freeze => true,
26
- "rack.multiprocess".freeze => false,
41
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
27
43
  "rack.run_once".freeze => false,
28
44
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
29
45
 
@@ -42,7 +58,13 @@ module Puma
42
58
  @ios = []
43
59
  end
44
60
 
45
- attr_reader :listeners, :ios
61
+ attr_reader :ios
62
+
63
+ # @version 5.0.0
64
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
65
+
66
+ # @version 5.0.0
67
+ attr_writer :ios, :listeners
46
68
 
47
69
  def env(sock)
48
70
  @envs.fetch(sock, @proto_env)
@@ -50,43 +72,83 @@ module Puma
50
72
 
51
73
  def close
52
74
  @ios.each { |i| i.close }
53
- @unix_paths.each { |i| File.unlink i }
54
75
  end
55
76
 
56
- def import_from_env
57
- remove = []
58
-
59
- ENV.each do |k,v|
60
- if k =~ /PUMA_INHERIT_\d+/
61
- fd, url = v.split(":", 2)
62
- @inherited_fds[url] = fd.to_i
63
- remove << k
64
- elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
65
- v.to_i.times do |num|
66
- fd = num + 3
67
- sock = TCPServer.for_fd(fd)
68
- begin
69
- key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
70
- rescue ArgumentError
71
- port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
72
- if addr =~ /\:/
73
- addr = "[#{addr}]"
74
- end
75
- key = [ :tcp, addr, port ]
76
- end
77
- @activated_sockets[key] = sock
78
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
79
- end
80
- remove << k << 'LISTEN_PID'
77
+ # @!attribute [r] connected_ports
78
+ # @version 5.0.0
79
+ def connected_ports
80
+ ios.map { |io| io.addr[1] }.uniq
81
+ end
82
+
83
+ # @version 5.0.0
84
+ def create_inherited_fds(env_hash)
85
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
86
+ fd, url = v.split(":", 2)
87
+ @inherited_fds[url] = fd.to_i
88
+ end.keys # pass keys back for removal
89
+ end
90
+
91
+ # systemd socket activation.
92
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
93
+ # LISTEN_PID = PID of the service process, aka us
94
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
95
+ # @version 5.0.0
96
+ #
97
+ def create_activated_fds(env_hash)
98
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
+ env_hash['LISTEN_FDS'].to_i.times do |index|
100
+ sock = TCPServer.for_fd(socket_activation_fd(index))
101
+ key = begin # Try to parse as a path
102
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
103
+ rescue ArgumentError # Try to parse as a port/ip
104
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
105
+ addr = "[#{addr}]" if addr =~ /\:/
106
+ [:tcp, addr, port]
107
+ end
108
+ @activated_sockets[key] = sock
109
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
110
+ end
111
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
112
+ end
113
+
114
+ # Synthesize binds from systemd socket activation
115
+ #
116
+ # When systemd socket activation is enabled, it can be tedious to keep the
117
+ # binds in sync. This method can synthesize any binds based on the received
118
+ # activated sockets. Any existing matching binds will be respected.
119
+ #
120
+ # When only_matching is true in, all binds that do not match an activated
121
+ # socket is removed in place.
122
+ #
123
+ # It's a noop if no activated sockets were received.
124
+ def synthesize_binds_from_activated_fs(binds, only_matching)
125
+ return binds unless activated_sockets.any?
126
+
127
+ activated_binds = []
128
+
129
+ activated_sockets.keys.each do |proto, addr, port|
130
+ if port
131
+ tcp_url = "#{proto}://#{addr}:#{port}"
132
+ ssl_url = "ssl://#{addr}:#{port}"
133
+ ssl_url_prefix = "#{ssl_url}?"
134
+
135
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
136
+
137
+ activated_binds << (existing || tcp_url)
138
+ else
139
+ # TODO: can there be a SSL bind without a port?
140
+ activated_binds << "#{proto}://#{addr}"
81
141
  end
82
142
  end
83
143
 
84
- remove.each do |k|
85
- ENV.delete k
144
+ if only_matching
145
+ activated_binds
146
+ else
147
+ binds | activated_binds
86
148
  end
87
149
  end
88
150
 
89
- def parse(binds, logger)
151
+ def parse(binds, logger, log_msg = 'Listening')
90
152
  binds.each do |str|
91
153
  uri = URI.parse str
92
154
  case uri.scheme
@@ -98,23 +160,36 @@ module Puma
98
160
  io = inherit_tcp_listener uri.host, uri.port, sock
99
161
  logger.log "* Activated #{str}"
100
162
  else
163
+ ios_len = @ios.length
101
164
  params = Util.parse_query uri.query
102
165
 
103
- opt = params.key?('low_latency')
166
+ opt = params.key?('low_latency') && params['low_latency'] != 'false'
104
167
  bak = params.fetch('backlog', 1024).to_i
105
168
 
106
169
  io = add_tcp_listener uri.host, uri.port, opt, bak
107
- logger.log "* Listening on #{str}"
170
+
171
+ @ios[ios_len..-1].each do |i|
172
+ addr = loc_addr_str i
173
+ logger.log "* #{log_msg} on http://#{addr}"
174
+ end
108
175
  end
109
176
 
110
177
  @listeners << [str, io] if io
111
178
  when "unix"
112
179
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
180
+ abstract = false
181
+ if str.start_with? 'unix://@'
182
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
183
+ abstract = true
184
+ path = "@#{path}"
185
+ end
113
186
 
114
187
  if fd = @inherited_fds.delete(str)
188
+ @unix_paths << path unless abstract
115
189
  io = inherit_unix_listener path, fd
116
190
  logger.log "* Inherited #{str}"
117
191
  elsif sock = @activated_sockets.delete([ :unix, path ])
192
+ @unix_paths << path unless abstract || File.exist?(path)
118
193
  io = inherit_unix_listener path, sock
119
194
  logger.log "* Activated #{str}"
120
195
  else
@@ -138,68 +213,18 @@ module Puma
138
213
  end
139
214
  end
140
215
 
216
+ @unix_paths << path unless abstract || File.exist?(path)
141
217
  io = add_unix_listener path, umask, mode, backlog
142
- logger.log "* Listening on #{str}"
218
+ logger.log "* #{log_msg} on #{str}"
143
219
  end
144
220
 
145
221
  @listeners << [str, io]
146
222
  when "ssl"
147
- params = Util.parse_query uri.query
148
- require 'puma/minissl'
149
-
150
- MiniSSL.check
151
-
152
- ctx = MiniSSL::Context.new
153
-
154
- if defined?(JRUBY_VERSION)
155
- unless params['keystore']
156
- @events.error "Please specify the Java keystore via 'keystore='"
157
- end
158
-
159
- ctx.keystore = params['keystore']
160
-
161
- unless params['keystore-pass']
162
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
163
- end
164
-
165
- ctx.keystore_pass = params['keystore-pass']
166
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
167
- else
168
- unless params['key']
169
- @events.error "Please specify the SSL key via 'key='"
170
- end
171
-
172
- ctx.key = params['key']
173
-
174
- unless params['cert']
175
- @events.error "Please specify the SSL cert via 'cert='"
176
- end
177
223
 
178
- ctx.cert = params['cert']
224
+ raise "Puma compiled without SSL support" unless HAS_SSL
179
225
 
180
- if ['peer', 'force_peer'].include?(params['verify_mode'])
181
- unless params['ca']
182
- @events.error "Please specify the SSL ca via 'ca='"
183
- end
184
- end
185
-
186
- ctx.ca = params['ca'] if params['ca']
187
- ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
188
- end
189
-
190
- if params['verify_mode']
191
- ctx.verify_mode = case params['verify_mode']
192
- when "peer"
193
- MiniSSL::VERIFY_PEER
194
- when "force_peer"
195
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
196
- when "none"
197
- MiniSSL::VERIFY_NONE
198
- else
199
- @events.error "Please specify a valid verify_mode="
200
- MiniSSL::VERIFY_NONE
201
- end
202
- end
226
+ params = Util.parse_query uri.query
227
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
203
228
 
204
229
  if fd = @inherited_fds.delete(str)
205
230
  logger.log "* Inherited #{str}"
@@ -208,8 +233,13 @@ module Puma
208
233
  io = inherit_ssl_listener sock, ctx
209
234
  logger.log "* Activated #{str}"
210
235
  else
236
+ ios_len = @ios.length
211
237
  io = add_ssl_listener uri.host, uri.port, ctx
212
- logger.log "* Listening on #{str}"
238
+
239
+ @ios[ios_len..-1].each do |i|
240
+ addr = loc_addr_str i
241
+ logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
242
+ end
213
243
  end
214
244
 
215
245
  @listeners << [str, io] if io
@@ -237,23 +267,21 @@ module Puma
237
267
  end
238
268
 
239
269
  # Also close any unused activated sockets
240
- @activated_sockets.each do |key, sock|
241
- logger.log "* Closing unused activated socket: #{key.join ':'}"
242
- begin
243
- sock.close
244
- rescue SystemCallError
270
+ unless @activated_sockets.empty?
271
+ fds = @ios.map(&:to_i)
272
+ @activated_sockets.each do |key, sock|
273
+ next if fds.include? sock.to_i
274
+ logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
275
+ begin
276
+ sock.close
277
+ rescue SystemCallError
278
+ end
279
+ # We have to unlink a unix socket path that's not being used
280
+ File.unlink key[1] if key.first == :unix
245
281
  end
246
- # We have to unlink a unix socket path that's not being used
247
- File.unlink key[1] if key[0] == :unix
248
282
  end
249
283
  end
250
284
 
251
- def loopback_addresses
252
- Socket.ip_address_list.select do |addrinfo|
253
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
254
- end.map { |addrinfo| addrinfo.ip_address }.uniq
255
- end
256
-
257
285
  # Tell the server to listen on host +host+, port +port+.
258
286
  # If +optimize_for_latency+ is true (the default) then clients connecting
259
287
  # will be optimized for latency over throughput.
@@ -270,26 +298,19 @@ module Puma
270
298
  end
271
299
 
272
300
  host = host[1..-2] if host and host[0..0] == '['
273
- s = TCPServer.new(host, port)
301
+ tcp_server = TCPServer.new(host, port)
274
302
  if optimize_for_latency
275
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
303
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
276
304
  end
277
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
278
- s.listen backlog
279
- @connected_port = s.addr[1]
305
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
306
+ tcp_server.listen backlog
280
307
 
281
- @ios << s
282
- s
308
+ @ios << tcp_server
309
+ tcp_server
283
310
  end
284
311
 
285
- attr_reader :connected_port
286
-
287
312
  def inherit_tcp_listener(host, port, fd)
288
- if fd.kind_of? TCPServer
289
- s = fd
290
- else
291
- s = TCPServer.for_fd(fd)
292
- end
313
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
293
314
 
294
315
  @ios << s
295
316
  s
@@ -297,9 +318,8 @@ module Puma
297
318
 
298
319
  def add_ssl_listener(host, port, ctx,
299
320
  optimize_for_latency=true, backlog=1024)
300
- require 'puma/minissl'
301
321
 
302
- MiniSSL.check
322
+ raise "Puma compiled without SSL support" unless HAS_SSL
303
323
 
304
324
  if host == "localhost"
305
325
  loopback_addresses.each do |addr|
@@ -316,7 +336,6 @@ module Puma
316
336
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
317
337
  s.listen backlog
318
338
 
319
-
320
339
  ssl = MiniSSL::Server.new s, ctx
321
340
  env = @proto_env.dup
322
341
  env[HTTPS_KEY] = HTTPS
@@ -327,14 +346,10 @@ module Puma
327
346
  end
328
347
 
329
348
  def inherit_ssl_listener(fd, ctx)
330
- require 'puma/minissl'
331
- MiniSSL.check
349
+ raise "Puma compiled without SSL support" unless HAS_SSL
350
+
351
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
332
352
 
333
- if fd.kind_of? TCPServer
334
- s = fd
335
- else
336
- s = TCPServer.for_fd(fd)
337
- end
338
353
  ssl = MiniSSL::Server.new(s, ctx)
339
354
 
340
355
  env = @proto_env.dup
@@ -349,8 +364,6 @@ module Puma
349
364
  # Tell the server to listen on +path+ as a UNIX domain socket.
350
365
  #
351
366
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
352
- @unix_paths << path
353
-
354
367
  # Let anyone connect by default
355
368
  umask ||= 0
356
369
 
@@ -367,8 +380,7 @@ module Puma
367
380
  raise "There is already a server bound to: #{path}"
368
381
  end
369
382
  end
370
-
371
- s = UNIXServer.new(path)
383
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
372
384
  s.listen backlog
373
385
  @ios << s
374
386
  ensure
@@ -387,13 +399,8 @@ module Puma
387
399
  end
388
400
 
389
401
  def inherit_unix_listener(path, fd)
390
- @unix_paths << path
402
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
391
403
 
392
- if fd.kind_of? TCPServer
393
- s = fd
394
- else
395
- s = UNIXServer.for_fd fd
396
- end
397
404
  @ios << s
398
405
 
399
406
  env = @proto_env.dup
@@ -403,5 +410,50 @@ module Puma
403
410
  s
404
411
  end
405
412
 
413
+ def close_listeners
414
+ @listeners.each do |l, io|
415
+ io.close unless io.closed?
416
+ uri = URI.parse l
417
+ next unless uri.scheme == 'unix'
418
+ unix_path = "#{uri.host}#{uri.path}"
419
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
420
+ end
421
+ end
422
+
423
+ def redirects_for_restart
424
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
425
+ redirects[:close_others] = true
426
+ redirects
427
+ end
428
+
429
+ # @version 5.0.0
430
+ def redirects_for_restart_env
431
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
432
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
433
+ end
434
+ end
435
+
436
+ private
437
+
438
+ # @!attribute [r] loopback_addresses
439
+ def loopback_addresses
440
+ Socket.ip_address_list.select do |addrinfo|
441
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
442
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
443
+ end
444
+
445
+ def loc_addr_str(io)
446
+ loc_addr = io.to_io.local_address
447
+ if loc_addr.ipv6?
448
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
449
+ else
450
+ loc_addr.ip_unpack.join(':')
451
+ end
452
+ end
453
+
454
+ # @version 5.0.0
455
+ def socket_activation_fd(int)
456
+ int + 3 # 3 is the magic number you add to follow the SA protocol
457
+ end
406
458
  end
407
459
  end