ed-precompiled_puma 7.0.4

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