puma 4.3.12 → 6.3.1

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1729 -521
  3. data/LICENSE +23 -20
  4. data/README.md +169 -45
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +31 -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 +2 -2
  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 +84 -128
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +49 -12
  30. data/ext/puma_http11/http11_parser.c +46 -48
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +2 -2
  35. data/ext/puma_http11/mini_ssl.c +278 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +46 -57
  41. data/lib/puma/app/status.rb +53 -39
  42. data/lib/puma/binder.rb +237 -121
  43. data/lib/puma/cli.rb +34 -34
  44. data/lib/puma/client.rb +172 -98
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +226 -231
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +114 -87
  50. data/lib/puma/const.rb +139 -95
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +516 -110
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +164 -155
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +36 -19
  63. data/lib/puma/minissl.rb +230 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +1 -1
  67. data/lib/puma/plugin.rb +3 -12
  68. data/lib/puma/rack/builder.rb +7 -11
  69. data/lib/puma/rack/urlmap.rb +0 -0
  70. data/lib/puma/rack_default.rb +19 -4
  71. data/lib/puma/reactor.rb +93 -368
  72. data/lib/puma/request.rb +671 -0
  73. data/lib/puma/runner.rb +92 -75
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +321 -794
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +140 -68
  79. data/lib/puma/util.rb +21 -4
  80. data/lib/puma.rb +54 -7
  81. data/lib/rack/handler/puma.rb +113 -87
  82. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  83. data/tools/trickletest.rb +0 -0
  84. metadata +33 -24
  85. data/docs/tcp_mode.md +0 -96
  86. data/ext/puma_http11/io_buffer.c +0 -155
  87. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  88. data/lib/puma/accept_nonblock.rb +0 -29
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/binder.rb CHANGED
@@ -3,18 +3,25 @@
3
3
  require 'uri'
4
4
  require 'socket'
5
5
 
6
- require 'puma/const'
7
- require 'puma/util'
8
- require 'puma/minissl/context_builder'
6
+ require_relative 'const'
7
+ require_relative 'util'
8
+ require_relative 'configuration'
9
9
 
10
10
  module Puma
11
+
12
+ if HAS_SSL
13
+ require_relative 'minissl'
14
+ require_relative 'minissl/context_builder'
15
+ end
16
+
11
17
  class Binder
12
18
  include Puma::Const
13
19
 
14
- RACK_VERSION = [1,3].freeze
20
+ RACK_VERSION = [1,6].freeze
15
21
 
16
- def initialize(events)
17
- @events = events
22
+ def initialize(log_writer, conf = Configuration.new)
23
+ @log_writer = log_writer
24
+ @conf = conf
18
25
  @listeners = []
19
26
  @inherited_fds = {}
20
27
  @activated_sockets = {}
@@ -22,10 +29,11 @@ module Puma
22
29
 
23
30
  @proto_env = {
24
31
  "rack.version".freeze => RACK_VERSION,
25
- "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
32
+ "rack.errors".freeze => log_writer.stderr,
33
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
34
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
35
  "rack.run_once".freeze => false,
36
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
29
37
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
38
 
31
39
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -34,7 +42,6 @@ module Puma
34
42
  # infer properly.
35
43
 
36
44
  "QUERY_STRING".freeze => "",
37
- SERVER_PROTOCOL => HTTP_11,
38
45
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
39
46
  GATEWAY_INTERFACE => CGI_VER
40
47
  }
@@ -45,6 +52,12 @@ module Puma
45
52
 
46
53
  attr_reader :ios
47
54
 
55
+ # @version 5.0.0
56
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
57
+
58
+ # @version 5.0.0
59
+ attr_writer :ios, :listeners
60
+
48
61
  def env(sock)
49
62
  @envs.fetch(sock, @proto_env)
50
63
  end
@@ -53,80 +66,127 @@ module Puma
53
66
  @ios.each { |i| i.close }
54
67
  end
55
68
 
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'
69
+ # @!attribute [r] connected_ports
70
+ # @version 5.0.0
71
+ def connected_ports
72
+ t = ios.map { |io| io.addr[1] }; t.uniq!; t
73
+ end
74
+
75
+ # @version 5.0.0
76
+ def create_inherited_fds(env_hash)
77
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
78
+ fd, url = v.split(":", 2)
79
+ @inherited_fds[url] = fd.to_i
80
+ end.keys # pass keys back for removal
81
+ end
82
+
83
+ # systemd socket activation.
84
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
85
+ # LISTEN_PID = PID of the service process, aka us
86
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
87
+ # @version 5.0.0
88
+ #
89
+ def create_activated_fds(env_hash)
90
+ @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
91
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
92
+ env_hash['LISTEN_FDS'].to_i.times do |index|
93
+ sock = TCPServer.for_fd(socket_activation_fd(index))
94
+ key = begin # Try to parse as a path
95
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
96
+ rescue ArgumentError # Try to parse as a port/ip
97
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
98
+ addr = "[#{addr}]" if addr&.include? ':'
99
+ [:tcp, addr, port]
100
+ end
101
+ @activated_sockets[key] = sock
102
+ @log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
103
+ end
104
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
105
+ end
106
+
107
+ # Synthesize binds from systemd socket activation
108
+ #
109
+ # When systemd socket activation is enabled, it can be tedious to keep the
110
+ # binds in sync. This method can synthesize any binds based on the received
111
+ # activated sockets. Any existing matching binds will be respected.
112
+ #
113
+ # When only_matching is true in, all binds that do not match an activated
114
+ # socket is removed in place.
115
+ #
116
+ # It's a noop if no activated sockets were received.
117
+ def synthesize_binds_from_activated_fs(binds, only_matching)
118
+ return binds unless activated_sockets.any?
119
+
120
+ activated_binds = []
121
+
122
+ activated_sockets.keys.each do |proto, addr, port|
123
+ if port
124
+ tcp_url = "#{proto}://#{addr}:#{port}"
125
+ ssl_url = "ssl://#{addr}:#{port}"
126
+ ssl_url_prefix = "#{ssl_url}?"
127
+
128
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
129
+
130
+ activated_binds << (existing || tcp_url)
131
+ else
132
+ # TODO: can there be a SSL bind without a port?
133
+ activated_binds << "#{proto}://#{addr}"
81
134
  end
82
135
  end
83
136
 
84
- remove.each do |k|
85
- ENV.delete k
137
+ if only_matching
138
+ activated_binds
139
+ else
140
+ binds | activated_binds
86
141
  end
87
142
  end
88
143
 
89
- def parse(binds, logger)
144
+ def parse(binds, log_writer = nil, log_msg = 'Listening')
145
+ log_writer ||= @log_writer
90
146
  binds.each do |str|
91
147
  uri = URI.parse str
92
148
  case uri.scheme
93
149
  when "tcp"
94
150
  if fd = @inherited_fds.delete(str)
95
151
  io = inherit_tcp_listener uri.host, uri.port, fd
96
- logger.log "* Inherited #{str}"
152
+ log_writer.log "* Inherited #{str}"
97
153
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
98
154
  io = inherit_tcp_listener uri.host, uri.port, sock
99
- logger.log "* Activated #{str}"
155
+ log_writer.log "* Activated #{str}"
100
156
  else
157
+ ios_len = @ios.length
101
158
  params = Util.parse_query uri.query
102
159
 
103
- opt = params.key?('low_latency')
104
- bak = params.fetch('backlog', 1024).to_i
160
+ low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
161
+ backlog = params.fetch('backlog', 1024).to_i
105
162
 
106
- io = add_tcp_listener uri.host, uri.port, opt, bak
163
+ io = add_tcp_listener uri.host, uri.port, low_latency, backlog
107
164
 
108
- @ios.each do |i|
109
- next unless TCPServer === i
110
- addr = if i.local_address.ipv6?
111
- "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
112
- else
113
- i.local_address.ip_unpack.join(':')
114
- end
115
-
116
- logger.log "* Listening on tcp://#{addr}"
165
+ @ios[ios_len..-1].each do |i|
166
+ addr = loc_addr_str i
167
+ log_writer.log "* #{log_msg} on http://#{addr}"
117
168
  end
118
169
  end
119
170
 
120
171
  @listeners << [str, io] if io
121
172
  when "unix"
122
173
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
174
+ abstract = false
175
+ if str.start_with? 'unix://@'
176
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
177
+ abstract = true
178
+ path = "@#{path}"
179
+ end
123
180
 
124
181
  if fd = @inherited_fds.delete(str)
182
+ @unix_paths << path unless abstract || File.exist?(path)
125
183
  io = inherit_unix_listener path, fd
126
- logger.log "* Inherited #{str}"
127
- elsif sock = @activated_sockets.delete([ :unix, path ])
184
+ log_writer.log "* Inherited #{str}"
185
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
186
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
187
+ @unix_paths << path unless abstract || File.exist?(path)
128
188
  io = inherit_unix_listener path, sock
129
- logger.log "* Activated #{str}"
189
+ log_writer.log "* Activated #{str}"
130
190
  else
131
191
  umask = nil
132
192
  mode = nil
@@ -148,36 +208,67 @@ module Puma
148
208
  end
149
209
  end
150
210
 
211
+ @unix_paths << path unless abstract || File.exist?(path)
151
212
  io = add_unix_listener path, umask, mode, backlog
152
- logger.log "* Listening on #{str}"
213
+ log_writer.log "* #{log_msg} on #{str}"
153
214
  end
154
215
 
155
216
  @listeners << [str, io]
156
217
  when "ssl"
218
+ cert_key = %w[cert key]
219
+
220
+ raise "Puma compiled without SSL support" unless HAS_SSL
221
+
157
222
  params = Util.parse_query uri.query
158
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
223
+
224
+ # If key and certs are not defined and localhost gem is required.
225
+ # localhost gem will be used for self signed
226
+ # Load localhost authority if not loaded.
227
+ # Ruby 3 `values_at` accepts an array, earlier do not
228
+ if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
229
+ ctx = localhost_authority && localhost_authority_context
230
+ end
231
+
232
+ ctx ||=
233
+ begin
234
+ # Extract cert_pem and key_pem from options[:store] if present
235
+ cert_key.each do |v|
236
+ if params[v]&.start_with?('store:')
237
+ index = Integer(params.delete(v).split('store:').last)
238
+ params["#{v}_pem"] = @conf.options[:store][index]
239
+ end
240
+ end
241
+ MiniSSL::ContextBuilder.new(params, @log_writer).context
242
+ end
159
243
 
160
244
  if fd = @inherited_fds.delete(str)
161
- logger.log "* Inherited #{str}"
245
+ log_writer.log "* Inherited #{str}"
162
246
  io = inherit_ssl_listener fd, ctx
163
247
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
164
248
  io = inherit_ssl_listener sock, ctx
165
- logger.log "* Activated #{str}"
249
+ log_writer.log "* Activated #{str}"
166
250
  else
167
- io = add_ssl_listener uri.host, uri.port, ctx
168
- logger.log "* Listening on #{str}"
251
+ ios_len = @ios.length
252
+ backlog = params.fetch('backlog', 1024).to_i
253
+ low_latency = params['low_latency'] != 'false'
254
+ io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
255
+
256
+ @ios[ios_len..-1].each do |i|
257
+ addr = loc_addr_str i
258
+ log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
259
+ end
169
260
  end
170
261
 
171
262
  @listeners << [str, io] if io
172
263
  else
173
- logger.error "Invalid URI: #{str}"
264
+ log_writer.error "Invalid URI: #{str}"
174
265
  end
175
266
  end
176
267
 
177
268
  # If we inherited fds but didn't use them (because of a
178
269
  # configuration change), then be sure to close them.
179
270
  @inherited_fds.each do |str, fd|
180
- logger.log "* Closing unused inherited connection: #{str}"
271
+ log_writer.log "* Closing unused inherited connection: #{str}"
181
272
 
182
273
  begin
183
274
  IO.for_fd(fd).close
@@ -193,21 +284,35 @@ module Puma
193
284
  end
194
285
 
195
286
  # Also close any unused activated sockets
196
- @activated_sockets.each do |key, sock|
197
- logger.log "* Closing unused activated socket: #{key.join ':'}"
198
- begin
199
- sock.close
200
- rescue SystemCallError
287
+ unless @activated_sockets.empty?
288
+ fds = @ios.map(&:to_i)
289
+ @activated_sockets.each do |key, sock|
290
+ next if fds.include? sock.to_i
291
+ log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
292
+ begin
293
+ sock.close
294
+ rescue SystemCallError
295
+ end
296
+ # We have to unlink a unix socket path that's not being used
297
+ File.unlink key[1] if key.first == :unix
201
298
  end
202
- # We have to unlink a unix socket path that's not being used
203
- File.unlink key[1] if key[0] == :unix
204
299
  end
205
300
  end
206
301
 
207
- def loopback_addresses
208
- Socket.ip_address_list.select do |addrinfo|
209
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
210
- end.map { |addrinfo| addrinfo.ip_address }.uniq
302
+ def localhost_authority
303
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
304
+ end
305
+
306
+ def localhost_authority_context
307
+ return unless localhost_authority
308
+
309
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
310
+ [localhost_authority.key_path, localhost_authority.certificate_path]
311
+ else
312
+ local_certificates_path = File.expand_path("~/.localhost")
313
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
314
+ end
315
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
211
316
  end
212
317
 
213
318
  # Tell the server to listen on host +host+, port +port+.
@@ -226,26 +331,20 @@ module Puma
226
331
  end
227
332
 
228
333
  host = host[1..-2] if host and host[0..0] == '['
229
- s = TCPServer.new(host, port)
334
+ tcp_server = TCPServer.new(host, port)
335
+
230
336
  if optimize_for_latency
231
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
337
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
338
  end
233
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
- s.listen backlog
235
- @connected_port = s.addr[1]
339
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
340
+ tcp_server.listen backlog
236
341
 
237
- @ios << s
238
- s
342
+ @ios << tcp_server
343
+ tcp_server
239
344
  end
240
345
 
241
- attr_reader :connected_port
242
-
243
346
  def inherit_tcp_listener(host, port, fd)
244
- if fd.kind_of? TCPServer
245
- s = fd
246
- else
247
- s = TCPServer.for_fd(fd)
248
- end
347
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
249
348
 
250
349
  @ios << s
251
350
  s
@@ -253,9 +352,10 @@ module Puma
253
352
 
254
353
  def add_ssl_listener(host, port, ctx,
255
354
  optimize_for_latency=true, backlog=1024)
256
- require 'puma/minissl'
257
355
 
258
- MiniSSL.check
356
+ raise "Puma compiled without SSL support" unless HAS_SSL
357
+ # Puma will try to use local authority context if context is supplied nil
358
+ ctx ||= localhost_authority_context
259
359
 
260
360
  if host == "localhost"
261
361
  loopback_addresses.each do |addr|
@@ -272,7 +372,6 @@ module Puma
272
372
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
273
373
  s.listen backlog
274
374
 
275
-
276
375
  ssl = MiniSSL::Server.new s, ctx
277
376
  env = @proto_env.dup
278
377
  env[HTTPS_KEY] = HTTPS
@@ -283,14 +382,12 @@ module Puma
283
382
  end
284
383
 
285
384
  def inherit_ssl_listener(fd, ctx)
286
- require 'puma/minissl'
287
- MiniSSL.check
385
+ raise "Puma compiled without SSL support" unless HAS_SSL
386
+ # Puma will try to use local authority context if context is supplied nil
387
+ ctx ||= localhost_authority_context
388
+
389
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
288
390
 
289
- if fd.kind_of? TCPServer
290
- s = fd
291
- else
292
- s = TCPServer.for_fd(fd)
293
- end
294
391
  ssl = MiniSSL::Server.new(s, ctx)
295
392
 
296
393
  env = @proto_env.dup
@@ -305,8 +402,6 @@ module Puma
305
402
  # Tell the server to listen on +path+ as a UNIX domain socket.
306
403
  #
307
404
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
308
- @unix_paths << path unless File.exist? path
309
-
310
405
  # Let anyone connect by default
311
406
  umask ||= 0
312
407
 
@@ -323,8 +418,7 @@ module Puma
323
418
  raise "There is already a server bound to: #{path}"
324
419
  end
325
420
  end
326
-
327
- s = UNIXServer.new(path)
421
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
328
422
  s.listen backlog
329
423
  @ios << s
330
424
  ensure
@@ -343,13 +437,8 @@ module Puma
343
437
  end
344
438
 
345
439
  def inherit_unix_listener(path, fd)
346
- @unix_paths << path unless File.exist? path
440
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
347
441
 
348
- if fd.kind_of? TCPServer
349
- s = fd
350
- else
351
- s = UNIXServer.for_fd fd
352
- end
353
442
  @ios << s
354
443
 
355
444
  env = @proto_env.dup
@@ -361,25 +450,52 @@ module Puma
361
450
 
362
451
  def close_listeners
363
452
  @listeners.each do |l, io|
364
- io.close
365
- uri = URI.parse(l)
366
- next unless uri.scheme == 'unix'
367
- unix_path = "#{uri.host}#{uri.path}"
368
- File.unlink unix_path if @unix_paths.include? unix_path
453
+ begin
454
+ io.close unless io.closed?
455
+ uri = URI.parse l
456
+ next unless uri.scheme == 'unix'
457
+ unix_path = "#{uri.host}#{uri.path}"
458
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
459
+ rescue Errno::EBADF
460
+ end
369
461
  end
370
462
  end
371
463
 
372
- def close_unix_paths
373
- @unix_paths.each { |up| File.unlink(up) if File.exist? up }
464
+ def redirects_for_restart
465
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
466
+ redirects[:close_others] = true
467
+ redirects
374
468
  end
375
469
 
376
- def redirects_for_restart
377
- redirects = {:close_others => true}
378
- @listeners.each_with_index do |(l, io), i|
379
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
380
- redirects[io.to_i] = io.to_i
470
+ # @version 5.0.0
471
+ def redirects_for_restart_env
472
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
473
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
381
474
  end
382
- redirects
475
+ end
476
+
477
+ private
478
+
479
+ # @!attribute [r] loopback_addresses
480
+ def loopback_addresses
481
+ t = Socket.ip_address_list.select do |addrinfo|
482
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
483
+ end
484
+ t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
485
+ end
486
+
487
+ def loc_addr_str(io)
488
+ loc_addr = io.to_io.local_address
489
+ if loc_addr.ipv6?
490
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
491
+ else
492
+ loc_addr.ip_unpack.join(':')
493
+ end
494
+ end
495
+
496
+ # @version 5.0.0
497
+ def socket_activation_fd(int)
498
+ int + 3 # 3 is the magic number you add to follow the SA protocol
383
499
  end
384
500
  end
385
501
  end