puma 7.1.0 → 7.2.0

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.
@@ -8,25 +8,12 @@
8
8
 
9
9
  #include "ruby.h"
10
10
  #include "ruby/encoding.h"
11
- #include "ext_help.h"
12
11
  #include <assert.h>
13
12
  #include <string.h>
14
13
  #include <ctype.h>
15
14
  #include "http11_parser.h"
16
15
 
17
- #ifndef MANAGED_STRINGS
18
-
19
- #ifndef RSTRING_PTR
20
- #define RSTRING_PTR(s) (RSTRING(s)->ptr)
21
- #endif
22
- #ifndef RSTRING_LEN
23
- #define RSTRING_LEN(s) (RSTRING(s)->len)
24
- #endif
25
-
26
- #define rb_extract_chars(e, sz) (*sz = RSTRING_LEN(e), RSTRING_PTR(e))
27
- #define rb_free_chars(e) /* nothing */
28
-
29
- #endif
16
+ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
30
17
 
31
18
  static VALUE eHttpParserError;
32
19
 
@@ -51,8 +38,8 @@ static VALUE global_request_path;
51
38
  /** Defines global strings in the init method. */
52
39
  static inline void DEF_GLOBAL(VALUE *var, const char *cstr)
53
40
  {
54
- rb_global_variable(var);
55
- *var = rb_enc_interned_str_cstr(cstr, rb_utf8_encoding());
41
+ rb_global_variable(var);
42
+ *var = rb_enc_interned_str_cstr(cstr, rb_utf8_encoding());
56
43
  }
57
44
 
58
45
  /* Defines the maximum allowed lengths for various input elements.*/
@@ -77,10 +64,10 @@ DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
77
64
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
78
65
 
79
66
  struct common_field {
80
- const size_t len;
81
- const char *name;
67
+ const size_t len;
68
+ const char *name;
82
69
  int raw;
83
- VALUE value;
70
+ VALUE value;
84
71
  };
85
72
 
86
73
  /*
@@ -91,42 +78,42 @@ struct common_field {
91
78
  static struct common_field common_http_fields[] = {
92
79
  # define f(N) { (sizeof(N) - 1), N, 0, Qnil }
93
80
  # define fr(N) { (sizeof(N) - 1), N, 1, Qnil }
94
- f("ACCEPT"),
95
- f("ACCEPT_CHARSET"),
96
- f("ACCEPT_ENCODING"),
97
- f("ACCEPT_LANGUAGE"),
98
- f("ALLOW"),
99
- f("AUTHORIZATION"),
100
- f("CACHE_CONTROL"),
101
- f("CONNECTION"),
102
- f("CONTENT_ENCODING"),
103
- fr("CONTENT_LENGTH"),
104
- fr("CONTENT_TYPE"),
105
- f("COOKIE"),
106
- f("DATE"),
107
- f("EXPECT"),
108
- f("FROM"),
109
- f("HOST"),
110
- f("IF_MATCH"),
111
- f("IF_MODIFIED_SINCE"),
112
- f("IF_NONE_MATCH"),
113
- f("IF_RANGE"),
114
- f("IF_UNMODIFIED_SINCE"),
115
- f("KEEP_ALIVE"), /* Firefox sends this */
116
- f("MAX_FORWARDS"),
117
- f("PRAGMA"),
118
- f("PROXY_AUTHORIZATION"),
119
- f("RANGE"),
120
- f("REFERER"),
121
- f("TE"),
122
- f("TRAILER"),
123
- f("TRANSFER_ENCODING"),
124
- f("UPGRADE"),
125
- f("USER_AGENT"),
126
- f("VIA"),
127
- f("X_FORWARDED_FOR"), /* common for proxies */
128
- f("X_REAL_IP"), /* common for proxies */
129
- f("WARNING")
81
+ f("ACCEPT"),
82
+ f("ACCEPT_CHARSET"),
83
+ f("ACCEPT_ENCODING"),
84
+ f("ACCEPT_LANGUAGE"),
85
+ f("ALLOW"),
86
+ f("AUTHORIZATION"),
87
+ f("CACHE_CONTROL"),
88
+ f("CONNECTION"),
89
+ f("CONTENT_ENCODING"),
90
+ fr("CONTENT_LENGTH"),
91
+ fr("CONTENT_TYPE"),
92
+ f("COOKIE"),
93
+ f("DATE"),
94
+ f("EXPECT"),
95
+ f("FROM"),
96
+ f("HOST"),
97
+ f("IF_MATCH"),
98
+ f("IF_MODIFIED_SINCE"),
99
+ f("IF_NONE_MATCH"),
100
+ f("IF_RANGE"),
101
+ f("IF_UNMODIFIED_SINCE"),
102
+ f("KEEP_ALIVE"), /* Firefox sends this */
103
+ f("MAX_FORWARDS"),
104
+ f("PRAGMA"),
105
+ f("PROXY_AUTHORIZATION"),
106
+ f("RANGE"),
107
+ f("REFERER"),
108
+ f("TE"),
109
+ f("TRAILER"),
110
+ f("TRANSFER_ENCODING"),
111
+ f("UPGRADE"),
112
+ f("USER_AGENT"),
113
+ f("VIA"),
114
+ f("X_FORWARDED_FOR"), /* common for proxies */
115
+ f("X_REAL_IP"), /* common for proxies */
116
+ f("WARNING")
130
117
  # undef f
131
118
  };
132
119
 
@@ -140,10 +127,10 @@ static void init_common_fields(void)
140
127
  for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
141
128
  rb_global_variable(&cf->value);
142
129
  if(cf->raw) {
143
- cf->value = rb_str_new(cf->name, cf->len);
130
+ cf->value = rb_enc_interned_str(cf->name, cf->len, rb_utf8_encoding());
144
131
  } else {
145
132
  memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
146
- cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
133
+ cf->value = rb_enc_interned_str(tmp, HTTP_PREFIX_LEN + cf->len, rb_utf8_encoding());
147
134
  }
148
135
  }
149
136
  }
@@ -163,7 +150,7 @@ static int is_ows(const char c) {
163
150
  return c == ' ' || c == '\t';
164
151
  }
165
152
 
166
- void http_field(puma_parser* hp, const char *field, size_t flen,
153
+ static void http_field(puma_parser* hp, const char *field, size_t flen,
167
154
  const char *value, size_t vlen)
168
155
  {
169
156
  VALUE f = Qnil;
@@ -186,7 +173,7 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
186
173
  memcpy(hp->buf, HTTP_PREFIX, HTTP_PREFIX_LEN);
187
174
  memcpy(hp->buf + HTTP_PREFIX_LEN, field, flen);
188
175
 
189
- f = rb_str_new(hp->buf, new_size);
176
+ f = rb_enc_interned_str(hp->buf, new_size, rb_utf8_encoding());
190
177
  }
191
178
 
192
179
  while (vlen > 0 && is_ows(value[vlen - 1])) vlen--;
@@ -208,7 +195,7 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
208
195
  }
209
196
  }
210
197
 
211
- void request_method(puma_parser* hp, const char *at, size_t length)
198
+ static void request_method(puma_parser* hp, const char *at, size_t length)
212
199
  {
213
200
  VALUE val = Qnil;
214
201
 
@@ -216,7 +203,7 @@ void request_method(puma_parser* hp, const char *at, size_t length)
216
203
  rb_hash_aset(hp->request, global_request_method, val);
217
204
  }
218
205
 
219
- void request_uri(puma_parser* hp, const char *at, size_t length)
206
+ static void request_uri(puma_parser* hp, const char *at, size_t length)
220
207
  {
221
208
  VALUE val = Qnil;
222
209
 
@@ -226,7 +213,7 @@ void request_uri(puma_parser* hp, const char *at, size_t length)
226
213
  rb_hash_aset(hp->request, global_request_uri, val);
227
214
  }
228
215
 
229
- void fragment(puma_parser* hp, const char *at, size_t length)
216
+ static void fragment(puma_parser* hp, const char *at, size_t length)
230
217
  {
231
218
  VALUE val = Qnil;
232
219
 
@@ -236,7 +223,7 @@ void fragment(puma_parser* hp, const char *at, size_t length)
236
223
  rb_hash_aset(hp->request, global_fragment, val);
237
224
  }
238
225
 
239
- void request_path(puma_parser* hp, const char *at, size_t length)
226
+ static void request_path(puma_parser* hp, const char *at, size_t length)
240
227
  {
241
228
  VALUE val = Qnil;
242
229
 
@@ -246,7 +233,7 @@ void request_path(puma_parser* hp, const char *at, size_t length)
246
233
  rb_hash_aset(hp->request, global_request_path, val);
247
234
  }
248
235
 
249
- void query_string(puma_parser* hp, const char *at, size_t length)
236
+ static void query_string(puma_parser* hp, const char *at, size_t length)
250
237
  {
251
238
  VALUE val = Qnil;
252
239
 
@@ -256,7 +243,7 @@ void query_string(puma_parser* hp, const char *at, size_t length)
256
243
  rb_hash_aset(hp->request, global_query_string, val);
257
244
  }
258
245
 
259
- void server_protocol(puma_parser* hp, const char *at, size_t length)
246
+ static void server_protocol(puma_parser* hp, const char *at, size_t length)
260
247
  {
261
248
  VALUE val = rb_str_new(at, length);
262
249
  rb_hash_aset(hp->request, global_server_protocol, val);
@@ -265,36 +252,42 @@ void server_protocol(puma_parser* hp, const char *at, size_t length)
265
252
  /** Finalizes the request header to have a bunch of stuff that's
266
253
  needed. */
267
254
 
268
- void header_done(puma_parser* hp, const char *at, size_t length)
255
+ static void header_done(puma_parser* hp, const char *at, size_t length)
269
256
  {
270
257
  hp->body = rb_str_new(at, length);
271
258
  }
272
259
 
273
260
 
274
- void HttpParser_free(void *data) {
275
- TRACE();
261
+ static void HttpParser_mark(void *ptr) {
262
+ puma_parser *hp = ptr;
263
+ rb_gc_mark_movable(hp->request);
264
+ rb_gc_mark_movable(hp->body);
265
+ }
276
266
 
277
- if(data) {
278
- xfree(data);
279
- }
267
+ static size_t HttpParser_size(const void *ptr) {
268
+ return sizeof(puma_parser);
280
269
  }
281
270
 
282
- void HttpParser_mark(void *ptr) {
271
+ static void HttpParser_compact(void *ptr) {
283
272
  puma_parser *hp = ptr;
284
- if(hp->request) rb_gc_mark(hp->request);
285
- if(hp->body) rb_gc_mark(hp->body);
273
+ hp->request = rb_gc_location(hp->request);
274
+ hp->body = rb_gc_location(hp->body);
286
275
  }
287
276
 
288
- const rb_data_type_t HttpParser_data_type = {
289
- "HttpParser",
290
- { HttpParser_mark, HttpParser_free, 0 },
291
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
277
+ static const rb_data_type_t HttpParser_data_type = {
278
+ .wrap_struct_name = "Puma::HttpParser",
279
+ .function = {
280
+ .dmark = HttpParser_mark,
281
+ .dfree = RUBY_TYPED_DEFAULT_FREE,
282
+ .dsize = HttpParser_size,
283
+ .dcompact = HttpParser_compact,
284
+ },
285
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
292
286
  };
293
287
 
294
- VALUE HttpParser_alloc(VALUE klass)
288
+ static VALUE HttpParser_alloc(VALUE klass)
295
289
  {
296
290
  puma_parser *hp = ALLOC_N(puma_parser, 1);
297
- TRACE();
298
291
  hp->http_field = http_field;
299
292
  hp->request_method = request_method;
300
293
  hp->request_uri = request_uri;
@@ -310,16 +303,25 @@ VALUE HttpParser_alloc(VALUE klass)
310
303
  return TypedData_Wrap_Struct(klass, &HttpParser_data_type, hp);
311
304
  }
312
305
 
306
+ static inline puma_parser *HttpParser_unwrap(VALUE self)
307
+ {
308
+ puma_parser *http;
309
+ TypedData_Get_Struct(self, puma_parser, &HttpParser_data_type, http);
310
+ if (http == NULL) {
311
+ rb_raise(rb_eArgError, "%s", "NULL http_parser found");
312
+ }
313
+ return http;
314
+ }
315
+
313
316
  /**
314
317
  * call-seq:
315
318
  * parser.new -> parser
316
319
  *
317
320
  * Creates a new parser.
318
321
  */
319
- VALUE HttpParser_init(VALUE self)
322
+ static VALUE HttpParser_init(VALUE self)
320
323
  {
321
- puma_parser *http = NULL;
322
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
324
+ puma_parser *http = HttpParser_unwrap(self);
323
325
  puma_parser_init(http);
324
326
 
325
327
  return self;
@@ -333,10 +335,9 @@ VALUE HttpParser_init(VALUE self)
333
335
  * Resets the parser to it's initial state so that you can reuse it
334
336
  * rather than making new ones.
335
337
  */
336
- VALUE HttpParser_reset(VALUE self)
338
+ static VALUE HttpParser_reset(VALUE self)
337
339
  {
338
- puma_parser *http = NULL;
339
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
340
+ puma_parser *http = HttpParser_unwrap(self);
340
341
  puma_parser_init(http);
341
342
 
342
343
  return Qnil;
@@ -350,10 +351,9 @@ VALUE HttpParser_reset(VALUE self)
350
351
  * Finishes a parser early which could put in a "good" or bad state.
351
352
  * You should call reset after finish it or bad things will happen.
352
353
  */
353
- VALUE HttpParser_finish(VALUE self)
354
+ static VALUE HttpParser_finish(VALUE self)
354
355
  {
355
- puma_parser *http = NULL;
356
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
356
+ puma_parser *http = HttpParser_unwrap(self);
357
357
  puma_parser_finish(http);
358
358
 
359
359
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
@@ -377,26 +377,22 @@ VALUE HttpParser_finish(VALUE self)
377
377
  * the parsing from that position. It needs all of the original data as well
378
378
  * so you have to append to the data buffer as you read.
379
379
  */
380
- VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
380
+ static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
381
381
  {
382
- puma_parser *http = NULL;
382
+ puma_parser *http = HttpParser_unwrap(self);
383
383
  int from = 0;
384
384
  char *dptr = NULL;
385
385
  long dlen = 0;
386
386
 
387
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
388
-
389
387
  from = FIX2INT(start);
390
- dptr = rb_extract_chars(data, &dlen);
388
+ RSTRING_GETMEM(data, dptr, dlen);
391
389
 
392
390
  if(from >= dlen) {
393
- rb_free_chars(dptr);
394
391
  rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
395
392
  } else {
396
393
  http->request = req_hash;
397
394
  puma_parser_execute(http, dptr, dlen, from);
398
395
 
399
- rb_free_chars(dptr);
400
396
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
401
397
 
402
398
  if(puma_parser_has_error(http)) {
@@ -415,10 +411,9 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
415
411
  *
416
412
  * Tells you whether the parser is in an error state.
417
413
  */
418
- VALUE HttpParser_has_error(VALUE self)
414
+ static VALUE HttpParser_has_error(VALUE self)
419
415
  {
420
- puma_parser *http = NULL;
421
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
416
+ puma_parser *http = HttpParser_unwrap(self);
422
417
 
423
418
  return puma_parser_has_error(http) ? Qtrue : Qfalse;
424
419
  }
@@ -430,10 +425,9 @@ VALUE HttpParser_has_error(VALUE self)
430
425
  *
431
426
  * Tells you whether the parser is finished or not and in a good state.
432
427
  */
433
- VALUE HttpParser_is_finished(VALUE self)
428
+ static VALUE HttpParser_is_finished(VALUE self)
434
429
  {
435
- puma_parser *http = NULL;
436
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
430
+ puma_parser *http = HttpParser_unwrap(self);
437
431
 
438
432
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
439
433
  }
@@ -446,10 +440,9 @@ VALUE HttpParser_is_finished(VALUE self)
446
440
  * Returns the amount of data processed so far during this processing cycle. It is
447
441
  * set to 0 on initialize or reset calls and is incremented each time execute is called.
448
442
  */
449
- VALUE HttpParser_nread(VALUE self)
443
+ static VALUE HttpParser_nread(VALUE self)
450
444
  {
451
- puma_parser *http = NULL;
452
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
445
+ puma_parser *http = HttpParser_unwrap(self);
453
446
 
454
447
  return INT2FIX(http->nread);
455
448
  }
@@ -460,9 +453,8 @@ VALUE HttpParser_nread(VALUE self)
460
453
  *
461
454
  * If the request included a body, returns it.
462
455
  */
463
- VALUE HttpParser_body(VALUE self) {
464
- puma_parser *http = NULL;
465
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
456
+ static VALUE HttpParser_body(VALUE self) {
457
+ puma_parser *http = HttpParser_unwrap(self);
466
458
 
467
459
  return http->body;
468
460
  }
@@ -471,7 +463,7 @@ VALUE HttpParser_body(VALUE self) {
471
463
  void Init_mini_ssl(VALUE mod);
472
464
  #endif
473
465
 
474
- void Init_puma_http11(void)
466
+ RUBY_FUNC_EXPORTED void Init_puma_http11(void)
475
467
  {
476
468
  #ifdef HAVE_RB_EXT_RACTOR_SAFE
477
469
  rb_ext_ractor_safe(true);
@@ -7,13 +7,16 @@ module Puma
7
7
  # can respond to.
8
8
  class Status
9
9
  OK_STATUS = '{ "status": "ok" }'.freeze
10
+ READ_ONLY_COMMANDS = %w[gc-stats stats].freeze
10
11
 
11
12
  # @param launcher [::Puma::Launcher]
12
13
  # @param token [String, nil] the token used for authentication
14
+ # @param data_only [Boolean] if true, restrict to read-only data commands
13
15
  #
14
- def initialize(launcher, token = nil)
16
+ def initialize(launcher, token: nil, data_only: false)
15
17
  @launcher = launcher
16
18
  @auth_token = token
19
+ @enabled_commands = READ_ONLY_COMMANDS if data_only
17
20
  end
18
21
 
19
22
  # most commands call methods in `::Puma::Launcher` based on command in
@@ -25,8 +28,13 @@ module Puma
25
28
 
26
29
  # resp_type is processed by following case statement, return
27
30
  # is a number (status) or a string used as the body of a 200 response
31
+ command = env['PATH_INFO'][/\/([^\/]+)$/, 1]
32
+ if @enabled_commands && !@enabled_commands.include?(command)
33
+ return rack_response(404, "Command #{command.inspect} unavailable", 'text/plain')
34
+ end
35
+
28
36
  resp_type =
29
- case env['PATH_INFO'][/\/([^\/]+)$/, 1]
37
+ case command
30
38
  when 'stop'
31
39
  @launcher.stop ; 200
32
40
 
@@ -14,7 +14,7 @@ module Puma
14
14
  class Worker < Puma::Runner # :nodoc:
15
15
  attr_reader :index, :master
16
16
 
17
- def initialize(index:, master:, launcher:, pipes:, server: nil)
17
+ def initialize(index:, master:, launcher:, pipes:, app: nil)
18
18
  super(launcher)
19
19
 
20
20
  @index = index
@@ -23,7 +23,8 @@ module Puma
23
23
  @worker_write = pipes[:worker_write]
24
24
  @fork_pipe = pipes[:fork_pipe]
25
25
  @wakeup = pipes[:wakeup]
26
- @server = server
26
+ @app = app
27
+ @server = nil
27
28
  @hook_data = {}
28
29
  end
29
30
 
@@ -57,7 +58,7 @@ module Puma
57
58
  @config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
58
59
 
59
60
  begin
60
- server = @server ||= start_server
61
+ @server = start_server
61
62
  rescue Exception => e
62
63
  log "! Unable to start worker"
63
64
  log e
@@ -85,7 +86,7 @@ module Puma
85
86
  if idx == -1 # stop server
86
87
  if restart_server.length > 0
87
88
  restart_server.clear
88
- server.begin_restart(true)
89
+ @server.begin_restart(true)
89
90
  @config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
90
91
  end
91
92
  elsif idx == -2 # refork cycle is done
@@ -103,7 +104,7 @@ module Puma
103
104
  Signal.trap "SIGTERM" do
104
105
  @worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
105
106
  restart_server.clear
106
- server.stop
107
+ @server.stop
107
108
  restart_server << false
108
109
  end
109
110
 
@@ -115,7 +116,7 @@ module Puma
115
116
  end
116
117
 
117
118
  while restart_server.pop
118
- server_thread = server.run
119
+ server_thread = @server.run
119
120
 
120
121
  if @log_writer.debug? && index == 0
121
122
  debug_loaded_extensions "Loaded Extensions - worker 0:"
@@ -129,13 +130,13 @@ module Puma
129
130
  begin
130
131
  payload = base_payload.dup
131
132
 
132
- hsh = server.stats
133
+ hsh = @server.stats
133
134
  hsh.each do |k, v|
134
135
  payload << %Q! "#{k}":#{v || 0},!
135
136
  end
136
137
  # sub call properly adds 'closing' string
137
138
  io << payload.sub(/,\z/, " }\n")
138
- server.reset_max
139
+ @server.reset_max
139
140
  rescue IOError
140
141
  break
141
142
  end
@@ -164,7 +165,7 @@ module Puma
164
165
  launcher: @launcher,
165
166
  pipes: { check_pipe: @check_pipe,
166
167
  worker_write: @worker_write },
167
- server: @server
168
+ app: @app
168
169
  new_worker.run
169
170
  end
170
171
 
data/lib/puma/cluster.rb CHANGED
@@ -186,7 +186,7 @@ module Puma
186
186
  # we need to phase any workers out (which will restart
187
187
  # in the right phase).
188
188
  #
189
- w = @workers.find { |x| x.phase != @phase }
189
+ w = @workers.find { |x| x.phase < @phase }
190
190
 
191
191
  if w
192
192
  if refork
@@ -221,12 +221,11 @@ module Puma
221
221
  pipes[:wakeup] = @wakeup
222
222
  end
223
223
 
224
- server = start_server if preload?
225
224
  new_worker = Worker.new index: index,
226
225
  master: master,
227
226
  launcher: @launcher,
228
227
  pipes: pipes,
229
- server: server
228
+ app: (app if preload?)
230
229
  new_worker.run
231
230
  end
232
231
 
@@ -197,7 +197,7 @@ module Puma
197
197
  @clamped = false
198
198
  end
199
199
 
200
- attr_reader :plugins, :events, :hooks
200
+ attr_reader :plugins, :events, :hooks, :_options
201
201
 
202
202
  def options
203
203
  raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
@@ -238,18 +238,14 @@ module Puma
238
238
  min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
239
239
  max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
240
240
  persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
241
- workers = if env['WEB_CONCURRENCY'] == 'auto'
242
- require_processor_counter
243
- ::Concurrent.available_processor_count
244
- else
245
- env['WEB_CONCURRENCY']
246
- end
241
+ workers_env = env['WEB_CONCURRENCY']
242
+ workers = workers_env && workers_env.strip != "" ? parse_workers(workers_env.strip) : nil
247
243
 
248
244
  {
249
245
  min_threads: min && min != "" && Integer(min),
250
246
  max_threads: max && max != "" && Integer(max),
251
247
  persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
252
- workers: workers && workers != "" && Integer(workers),
248
+ workers: workers,
253
249
  environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
254
250
  }
255
251
  end
@@ -380,12 +376,23 @@ module Puma
380
376
  require 'concurrent/utility/processor_counter'
381
377
  rescue LoadError
382
378
  warn <<~MESSAGE
383
- WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
379
+ WEB_CONCURRENCY=auto or workers(:auto) requires the "concurrent-ruby" gem to be installed.
384
380
  Please add "concurrent-ruby" to your Gemfile.
385
381
  MESSAGE
386
382
  raise
387
383
  end
388
384
 
385
+ def parse_workers(value)
386
+ if value == :auto || value == 'auto'
387
+ require_processor_counter
388
+ Integer(::Concurrent.available_processor_count)
389
+ else
390
+ Integer(value)
391
+ end
392
+ rescue ArgumentError, TypeError
393
+ raise ArgumentError, "workers must be an Integer or :auto"
394
+ end
395
+
389
396
  # Load and use the normal Rack builder if we can, otherwise
390
397
  # fallback to our minimal version.
391
398
  def rack_builder
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "7.1.0"
104
- CODE_NAME = "Neon Witch"
103
+ PUMA_VERSION = VERSION = "7.2.0"
104
+ CODE_NAME = "On The Corner"
105
105
 
106
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
data/lib/puma/dsl.rb CHANGED
@@ -216,6 +216,8 @@ module Puma
216
216
  # activate_control_app 'unix:///var/run/pumactl.sock', { auth_token: '12345' }
217
217
  # @example
218
218
  # activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true }
219
+ # @example
220
+ # activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true, data_only: true}
219
221
  #
220
222
  def activate_control_app(url="auto", opts={})
221
223
  if url == "auto"
@@ -240,6 +242,7 @@ module Puma
240
242
 
241
243
  @options[:control_auth_token] = auth_token
242
244
  @options[:control_url_umask] = opts[:umask] if opts[:umask]
245
+ @options[:control_data_only] = opts[:data_only] if opts[:data_only]
243
246
  end
244
247
 
245
248
  # Load additional configuration from a file.
@@ -666,21 +669,27 @@ module Puma
666
669
  @options[:state_permission] = permission
667
670
  end
668
671
 
669
- # How many worker processes to run. Typically this is set to
670
- # the number of available cores.
672
+ # How many worker processes to run. Typically this is set to the number of
673
+ # available cores.
671
674
  #
672
675
  # The default is the value of the environment variable +WEB_CONCURRENCY+ if
673
- # set, otherwise 0.
676
+ # set, otherwise 0. Passing +:auto+ will set the value to
677
+ # +Concurrent.available_processor_count+ (requires the concurrent-ruby gem).
678
+ # On some platforms (e.g. under CPU quotas) this may be fractional, and Puma
679
+ # will round down. If it rounds down to 0, Puma will run in single mode and
680
+ # cluster-only hooks like +before_worker_boot+ will not execute.
681
+ # If you rely on cluster-only hooks, set an explicit worker count.
674
682
  #
675
- # @note Cluster mode only.
683
+ # A value of 0 or nil means run in single mode.
676
684
  #
677
685
  # @example
678
686
  # workers 2
687
+ # workers :auto
679
688
  #
680
689
  # @see Puma::Cluster
681
690
  #
682
691
  def workers(count)
683
- @options[:workers] = count.to_i
692
+ @options[:workers] = count.nil? ? 0 : @config.send(:parse_workers, count)
684
693
  end
685
694
 
686
695
  # Disable warning message when running in cluster mode with a single worker.
@@ -995,6 +1004,7 @@ module Puma
995
1004
  # The default is +true+ if your app uses more than 1 worker.
996
1005
  #
997
1006
  # @note Cluster mode only.
1007
+ # @note When using `fork_worker`, this only applies to worker 0.
998
1008
  #
999
1009
  # @example
1000
1010
  # preload_app!
@@ -1378,7 +1388,7 @@ module Puma
1378
1388
  #
1379
1389
  # The default is +:auto+.
1380
1390
  #
1381
- # @see https://github.com/socketry/nio4r/blob/master/lib/nio/selector.rb
1391
+ # @see https://github.com/socketry/nio4r/blob/main/lib/nio/selector.rb
1382
1392
  #
1383
1393
  def io_selector_backend(backend)
1384
1394
  @options[:io_selector_backend] = backend.to_sym