puma 4.3.3-java → 5.0.0.beta2-java

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +79 -8
  3. data/LICENSE +23 -20
  4. data/README.md +18 -12
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/signals.md +5 -4
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/http11_parser.c +3 -1
  20. data/ext/puma_http11/http11_parser.rl +3 -1
  21. data/ext/puma_http11/mini_ssl.c +12 -2
  22. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +37 -6
  24. data/ext/puma_http11/puma_http11.c +3 -38
  25. data/lib/puma.rb +5 -0
  26. data/lib/puma/app/status.rb +18 -3
  27. data/lib/puma/binder.rb +66 -63
  28. data/lib/puma/cli.rb +7 -15
  29. data/lib/puma/client.rb +64 -14
  30. data/lib/puma/cluster.rb +183 -74
  31. data/lib/puma/commonlogger.rb +2 -2
  32. data/lib/puma/configuration.rb +30 -42
  33. data/lib/puma/const.rb +2 -3
  34. data/lib/puma/control_cli.rb +27 -17
  35. data/lib/puma/detect.rb +8 -0
  36. data/lib/puma/dsl.rb +72 -36
  37. data/lib/puma/error_logger.rb +96 -0
  38. data/lib/puma/events.rb +33 -31
  39. data/lib/puma/io_buffer.rb +9 -2
  40. data/lib/puma/jruby_restart.rb +0 -58
  41. data/lib/puma/launcher.rb +46 -31
  42. data/lib/puma/minissl.rb +47 -10
  43. data/lib/puma/null_io.rb +1 -1
  44. data/lib/puma/plugin.rb +1 -10
  45. data/lib/puma/puma_http11.jar +0 -0
  46. data/lib/puma/rack/builder.rb +0 -4
  47. data/lib/puma/reactor.rb +8 -3
  48. data/lib/puma/runner.rb +6 -35
  49. data/lib/puma/server.rb +138 -182
  50. data/lib/puma/single.rb +7 -64
  51. data/lib/puma/state_file.rb +6 -3
  52. data/lib/puma/thread_pool.rb +90 -49
  53. data/lib/rack/handler/puma.rb +1 -3
  54. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  55. metadata +18 -21
  56. data/docs/tcp_mode.md +0 -96
  57. data/ext/puma_http11/io_buffer.c +0 -155
  58. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  59. data/lib/puma/tcp_logger.rb +0 -41
  60. data/tools/jungle/README.md +0 -19
  61. data/tools/jungle/init.d/README.md +0 -61
  62. data/tools/jungle/init.d/puma +0 -421
  63. data/tools/jungle/init.d/run-puma +0 -18
@@ -1,18 +1,16 @@
1
1
  package puma;
2
2
 
3
3
  import java.io.IOException;
4
-
4
+
5
5
  import org.jruby.Ruby;
6
6
  import org.jruby.runtime.load.BasicLibraryService;
7
7
 
8
8
  import org.jruby.puma.Http11;
9
- import org.jruby.puma.IOBuffer;
10
9
  import org.jruby.puma.MiniSSL;
11
10
 
12
- public class PumaHttp11Service implements BasicLibraryService {
11
+ public class PumaHttp11Service implements BasicLibraryService {
13
12
  public boolean basicLoad(final Ruby runtime) throws IOException {
14
13
  Http11.createHttp11(runtime);
15
- IOBuffer.createIOBuffer(runtime);
16
14
  MiniSSL.createMiniSSL(runtime);
17
15
  return true;
18
16
  }
@@ -1,9 +1,10 @@
1
1
  require 'mkmf'
2
2
 
3
3
  dir_config("puma_http11")
4
- if RUBY_PLATFORM[/mingw32/]
5
- append_cflags '-D_FORTIFY_SOURCE=2'
6
- append_ldflags '-fstack-protector'
4
+
5
+ if $mingw && RUBY_VERSION >= '2.4'
6
+ append_cflags '-fstack-protector-strong -D_FORTIFY_SOURCE=2'
7
+ append_ldflags '-fstack-protector-strong -l:libssp.a'
7
8
  have_library 'ssp'
8
9
  end
9
10
 
@@ -14,12 +14,14 @@
14
14
 
15
15
  /*
16
16
  * capitalizes all lower-case ASCII characters,
17
- * converts dashes to underscores.
17
+ * converts dashes to underscores, and underscores to commas.
18
18
  */
19
19
  static void snake_upcase_char(char *c)
20
20
  {
21
21
  if (*c >= 'a' && *c <= 'z')
22
22
  *c &= ~0x20;
23
+ else if (*c == '_')
24
+ *c = ',';
23
25
  else if (*c == '-')
24
26
  *c = '_';
25
27
  }
@@ -12,12 +12,14 @@
12
12
 
13
13
  /*
14
14
  * capitalizes all lower-case ASCII characters,
15
- * converts dashes to underscores.
15
+ * converts dashes to underscores, and underscores to commas.
16
16
  */
17
17
  static void snake_upcase_char(char *c)
18
18
  {
19
19
  if (*c >= 'a' && *c <= 'z')
20
20
  *c &= ~0x20;
21
+ else if (*c == '_')
22
+ *c = ',';
21
23
  else if (*c == '-')
22
24
  *c = '_';
23
25
  }
@@ -301,6 +301,7 @@ void raise_error(SSL* ssl, int result) {
301
301
  char msg[512];
302
302
  const char* err_str;
303
303
  int err = errno;
304
+ int mask = 4095;
304
305
  int ssl_err = SSL_get_error(ssl, result);
305
306
  int verify_err = (int) SSL_get_verify_result(ssl);
306
307
 
@@ -317,8 +318,8 @@ void raise_error(SSL* ssl, int result) {
317
318
  } else {
318
319
  err = (int) ERR_get_error();
319
320
  ERR_error_string_n(err, buf, sizeof(buf));
320
- snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, err);
321
-
321
+ int errexp = err & mask;
322
+ snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, errexp);
322
323
  }
323
324
  } else {
324
325
  snprintf(msg, sizeof(msg), "Unknown OpenSSL error: %d", ssl_err);
@@ -462,6 +463,13 @@ VALUE engine_peercert(VALUE self) {
462
463
  return rb_cert_buf;
463
464
  }
464
465
 
466
+ static VALUE
467
+ engine_ssl_vers_st(VALUE self) {
468
+ ms_conn* conn;
469
+ Data_Get_Struct(self, ms_conn, conn);
470
+ return rb_ary_new3(2, rb_str_new2(SSL_get_version(conn->ssl)), rb_str_new2(SSL_state_string(conn->ssl)));
471
+ }
472
+
465
473
  VALUE noop(VALUE self) {
466
474
  return Qnil;
467
475
  }
@@ -533,6 +541,8 @@ void Init_mini_ssl(VALUE puma) {
533
541
  rb_define_method(eng, "init?", engine_init, 0);
534
542
 
535
543
  rb_define_method(eng, "peercert", engine_peercert, 0);
544
+
545
+ rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
536
546
  }
537
547
 
538
548
  #else
@@ -30,8 +30,8 @@ public class Http11 extends RubyObject {
30
30
  public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
31
31
  public final static int MAX_FRAGMENT_LENGTH = 1024;
32
32
  public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
33
- public final static int MAX_REQUEST_PATH_LENGTH = 2048;
34
- public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 2048 allowed length.";
33
+ public final static int MAX_REQUEST_PATH_LENGTH = 8192;
34
+ public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
35
35
  public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
36
36
  public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
37
37
  public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
@@ -197,7 +197,7 @@ public class Http11 extends RubyObject {
197
197
  validateMaxLength(runtime, parser.nread,MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR);
198
198
 
199
199
  if(hp.has_error()) {
200
- throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails.");
200
+ throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
201
201
  } else {
202
202
  return runtime.newFixnum(parser.nread);
203
203
  }
@@ -120,6 +120,8 @@ public class MiniSSL extends RubyObject {
120
120
  }
121
121
 
122
122
  private SSLEngine engine;
123
+ private boolean closed;
124
+ private boolean handshake;
123
125
  private MiniSSLBuffer inboundNetData;
124
126
  private MiniSSLBuffer outboundAppData;
125
127
  private MiniSSLBuffer outboundNetData;
@@ -157,6 +159,8 @@ public class MiniSSL extends RubyObject {
157
159
  SSLContext sslCtx = SSLContext.getInstance("TLS");
158
160
 
159
161
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
162
+ closed = false;
163
+ handshake = false;
160
164
  engine = sslCtx.createSSLEngine();
161
165
 
162
166
  String[] protocols;
@@ -173,7 +177,7 @@ public class MiniSSL extends RubyObject {
173
177
  engine.setEnabledProtocols(protocols);
174
178
  engine.setUseClientMode(false);
175
179
 
176
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
180
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
177
181
  if ((verify_mode & 0x1) != 0) { // 'peer'
178
182
  engine.setWantClientAuth(true);
179
183
  }
@@ -240,14 +244,21 @@ public class MiniSSL extends RubyObject {
240
244
  // need to wait for more data to come in before we retry
241
245
  retryOp = false;
242
246
  break;
247
+ case CLOSED:
248
+ closed = true;
249
+ retryOp = false;
250
+ break;
243
251
  default:
244
- // other cases are OK and CLOSED. We're done here.
252
+ // other case is OK. We're done here.
245
253
  retryOp = false;
246
254
  }
255
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
256
+ handshake = true;
257
+ }
247
258
  }
248
259
 
249
260
  // after each op, run any delegated tasks if needed
250
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
261
+ if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
251
262
  Runnable runnable;
252
263
  while ((runnable = engine.getDelegatedTask()) != null) {
253
264
  runnable.run();
@@ -271,13 +282,14 @@ public class MiniSSL extends RubyObject {
271
282
 
272
283
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
284
  boolean done = false;
285
+ SSLEngineResult res = null;
274
286
  while (!done) {
275
287
  switch (handshakeStatus) {
276
288
  case NEED_WRAP:
277
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
289
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
278
290
  break;
279
291
  case NEED_UNWRAP:
280
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
292
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
293
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
294
  // need more data before we can shake more hands
283
295
  done = true;
@@ -286,7 +298,9 @@ public class MiniSSL extends RubyObject {
286
298
  default:
287
299
  done = true;
288
300
  }
289
- handshakeStatus = engine.getHandshakeStatus();
301
+ if (!done) {
302
+ handshakeStatus = res.getHandshakeStatus();
303
+ }
290
304
  }
291
305
 
292
306
  if (inboundNetData.hasRemaining()) {
@@ -360,4 +374,21 @@ public class MiniSSL extends RubyObject {
360
374
  return getRuntime().getNil();
361
375
  }
362
376
  }
377
+
378
+ @JRubyMethod(name = "init?")
379
+ public IRubyObject isInit(ThreadContext context) {
380
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
381
+ }
382
+
383
+ @JRubyMethod
384
+ public IRubyObject shutdown() {
385
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
386
+ if (engine.isOutboundDone()) {
387
+ engine.closeOutbound();
388
+ }
389
+ return getRuntime().getTrue();
390
+ } else {
391
+ return getRuntime().getFalse();
392
+ }
393
+ }
363
394
  }
@@ -10,6 +10,7 @@
10
10
  #include "ext_help.h"
11
11
  #include <assert.h>
12
12
  #include <string.h>
13
+ #include <ctype.h>
13
14
  #include "http11_parser.h"
14
15
 
15
16
  #ifndef MANAGED_STRINGS
@@ -53,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
53
54
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
54
55
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
55
56
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
56
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
+ DEF_MAX_LENGTH(REQUEST_PATH, 8192);
57
58
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
58
59
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
59
60
 
@@ -111,21 +112,6 @@ static struct common_field common_http_fields[] = {
111
112
  # undef f
112
113
  };
113
114
 
114
- /*
115
- * qsort(3) and bsearch(3) improve average performance slightly, but may
116
- * not be worth it for lack of portability to certain platforms...
117
- */
118
- #if defined(HAVE_QSORT_BSEARCH)
119
- /* sort by length, then by name if there's a tie */
120
- static int common_field_cmp(const void *a, const void *b)
121
- {
122
- struct common_field *cfa = (struct common_field *)a;
123
- struct common_field *cfb = (struct common_field *)b;
124
- signed long diff = cfa->len - cfb->len;
125
- return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
126
- }
127
- #endif /* HAVE_QSORT_BSEARCH */
128
-
129
115
  static void init_common_fields(void)
130
116
  {
131
117
  unsigned i;
@@ -142,28 +128,10 @@ static void init_common_fields(void)
142
128
  }
143
129
  rb_global_variable(&cf->value);
144
130
  }
145
-
146
- #if defined(HAVE_QSORT_BSEARCH)
147
- qsort(common_http_fields,
148
- ARRAY_SIZE(common_http_fields),
149
- sizeof(struct common_field),
150
- common_field_cmp);
151
- #endif /* HAVE_QSORT_BSEARCH */
152
131
  }
153
132
 
154
133
  static VALUE find_common_field_value(const char *field, size_t flen)
155
134
  {
156
- #if defined(HAVE_QSORT_BSEARCH)
157
- struct common_field key;
158
- struct common_field *found;
159
- key.name = field;
160
- key.len = (signed long)flen;
161
- found = (struct common_field *)bsearch(&key, common_http_fields,
162
- ARRAY_SIZE(common_http_fields),
163
- sizeof(struct common_field),
164
- common_field_cmp);
165
- return found ? found->value : Qnil;
166
- #else /* !HAVE_QSORT_BSEARCH */
167
135
  unsigned i;
168
136
  struct common_field *cf = common_http_fields;
169
137
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -171,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
171
139
  return cf->value;
172
140
  }
173
141
  return Qnil;
174
- #endif /* !HAVE_QSORT_BSEARCH */
175
142
  }
176
143
 
177
144
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -400,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
400
367
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
401
368
 
402
369
  if(puma_parser_has_error(http)) {
403
- rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
370
+ rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
404
371
  } else {
405
372
  return INT2FIX(puma_parser_nread(http));
406
373
  }
@@ -467,7 +434,6 @@ VALUE HttpParser_body(VALUE self) {
467
434
  return http->body;
468
435
  }
469
436
 
470
- void Init_io_buffer(VALUE puma);
471
437
  void Init_mini_ssl(VALUE mod);
472
438
 
473
439
  void Init_puma_http11()
@@ -497,6 +463,5 @@ void Init_puma_http11()
497
463
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
498
464
  init_common_fields();
499
465
 
500
- Init_io_buffer(mPuma);
501
466
  Init_mini_ssl(mPuma);
502
467
  }
@@ -20,6 +20,11 @@ module Puma
20
20
  end
21
21
 
22
22
  def self.stats
23
+ require 'json'
24
+ @get_stats.stats.to_json
25
+ end
26
+
27
+ def self.stats_hash
23
28
  @get_stats.stats
24
29
  end
25
30
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Puma
6
4
  module App
7
5
  # Check out {#call}'s source code to see what actions this web application
@@ -19,6 +17,10 @@ module Puma
19
17
  return rack_response(403, 'Invalid auth token', 'text/plain')
20
18
  end
21
19
 
20
+ if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
+ require 'json'
22
+ end
23
+
22
24
  case env['PATH_INFO']
23
25
  when /\/stop$/
24
26
  @cli.stop
@@ -54,7 +56,20 @@ module Puma
54
56
  rack_response(200, GC.stat.to_json)
55
57
 
56
58
  when /\/stats$/
57
- rack_response(200, @cli.stats)
59
+ rack_response(200, @cli.stats.to_json)
60
+
61
+ when /\/thread-backtraces$/
62
+ backtraces = []
63
+ @cli.thread_status do |name, backtrace|
64
+ backtraces << { name: name, backtrace: backtrace }
65
+ end
66
+
67
+ rack_response(200, backtraces.to_json)
68
+
69
+ when /\/refork$/
70
+ Process.kill "SIGURG", $$
71
+ rack_response(200, OK_STATUS)
72
+
58
73
  else
59
74
  rack_response 404, "Unsupported action", 'text/plain'
60
75
  end
@@ -6,14 +6,15 @@ require 'socket'
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
8
  require 'puma/minissl/context_builder'
9
+ require 'puma/configuration'
9
10
 
10
11
  module Puma
11
12
  class Binder
12
13
  include Puma::Const
13
14
 
14
- RACK_VERSION = [1,3].freeze
15
+ RACK_VERSION = [1,6].freeze
15
16
 
16
- def initialize(events)
17
+ def initialize(events, conf = Configuration.new)
17
18
  @events = events
18
19
  @listeners = []
19
20
  @inherited_fds = {}
@@ -23,8 +24,8 @@ module Puma
23
24
  @proto_env = {
24
25
  "rack.version".freeze => RACK_VERSION,
25
26
  "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
27
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
28
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
29
  "rack.run_once".freeze => false,
29
30
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
31
 
@@ -43,7 +44,8 @@ module Puma
43
44
  @ios = []
44
45
  end
45
46
 
46
- attr_reader :ios
47
+ attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
48
+ attr_writer :ios, :listeners
47
49
 
48
50
  def env(sock)
49
51
  @envs.fetch(sock, @proto_env)
@@ -53,40 +55,39 @@ module Puma
53
55
  @ios.each { |i| i.close }
54
56
  end
55
57
 
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'
81
- end
82
- end
58
+ def connected_ports
59
+ ios.map { |io| io.addr[1] }.uniq
60
+ end
61
+
62
+ def create_inherited_fds(env_hash)
63
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
64
+ fd, url = v.split(":", 2)
65
+ @inherited_fds[url] = fd.to_i
66
+ end.keys # pass keys back for removal
67
+ end
83
68
 
84
- remove.each do |k|
85
- ENV.delete k
69
+ # systemd socket activation.
70
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
71
+ # LISTEN_PID = PID of the service process, aka us
72
+ # see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
73
+ def create_activated_fds(env_hash)
74
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
75
+ env_hash['LISTEN_FDS'].to_i.times do |index|
76
+ sock = TCPServer.for_fd(socket_activation_fd(index))
77
+ key = begin # Try to parse as a path
78
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
79
+ rescue ArgumentError # Try to parse as a port/ip
80
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
81
+ addr = "[#{addr}]" if addr =~ /\:/
82
+ [:tcp, addr, port]
83
+ end
84
+ @activated_sockets[key] = sock
85
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
86
86
  end
87
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
87
88
  end
88
89
 
89
- def parse(binds, logger)
90
+ def parse(binds, logger, log_msg = 'Listening')
90
91
  binds.each do |str|
91
92
  uri = URI.parse str
92
93
  case uri.scheme
@@ -113,7 +114,7 @@ module Puma
113
114
  i.local_address.ip_unpack.join(':')
114
115
  end
115
116
 
116
- logger.log "* Listening on tcp://#{addr}"
117
+ logger.log "* #{log_msg} on http://#{addr}"
117
118
  end
118
119
  end
119
120
 
@@ -149,7 +150,7 @@ module Puma
149
150
  end
150
151
 
151
152
  io = add_unix_listener path, umask, mode, backlog
152
- logger.log "* Listening on #{str}"
153
+ logger.log "* #{log_msg} on #{str}"
153
154
  end
154
155
 
155
156
  @listeners << [str, io]
@@ -204,12 +205,6 @@ module Puma
204
205
  end
205
206
  end
206
207
 
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
211
- end
212
-
213
208
  # Tell the server to listen on host +host+, port +port+.
214
209
  # If +optimize_for_latency+ is true (the default) then clients connecting
215
210
  # will be optimized for latency over throughput.
@@ -226,20 +221,17 @@ module Puma
226
221
  end
227
222
 
228
223
  host = host[1..-2] if host and host[0..0] == '['
229
- s = TCPServer.new(host, port)
224
+ tcp_server = TCPServer.new(host, port)
230
225
  if optimize_for_latency
231
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
226
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
227
  end
233
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
- s.listen backlog
235
- @connected_port = s.addr[1]
228
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
229
+ tcp_server.listen backlog
236
230
 
237
- @ios << s
238
- s
231
+ @ios << tcp_server
232
+ tcp_server
239
233
  end
240
234
 
241
- attr_reader :connected_port
242
-
243
235
  def inherit_tcp_listener(host, port, fd)
244
236
  if fd.kind_of? TCPServer
245
237
  s = fd
@@ -360,26 +352,37 @@ module Puma
360
352
  end
361
353
 
362
354
  def close_listeners
363
- @listeners.each do |l, io|
364
- io.close
355
+ listeners.each do |l, io|
356
+ io.close unless io.closed? # Ruby 2.2 issue
365
357
  uri = URI.parse(l)
366
358
  next unless uri.scheme == 'unix'
367
359
  unix_path = "#{uri.host}#{uri.path}"
368
- File.unlink unix_path if @unix_paths.include? unix_path
360
+ File.unlink unix_path if unix_paths.include? unix_path
369
361
  end
370
362
  end
371
363
 
372
- def close_unix_paths
373
- @unix_paths.each { |up| File.unlink(up) if File.exist? up }
364
+ def redirects_for_restart
365
+ redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
366
+ redirects[:close_others] = true
367
+ redirects
374
368
  end
375
369
 
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
370
+ def redirects_for_restart_env
371
+ listeners.each_with_object({}).with_index do |(listen, memo), i|
372
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
381
373
  end
382
- redirects
374
+ end
375
+
376
+ private
377
+
378
+ def loopback_addresses
379
+ Socket.ip_address_list.select do |addrinfo|
380
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
381
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
382
+ end
383
+
384
+ def socket_activation_fd(int)
385
+ int + 3 # 3 is the magic number you add to follow the SA protocol
383
386
  end
384
387
  end
385
388
  end