puma 3.12.0 → 4.3.8

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +164 -0
  3. data/README.md +76 -48
  4. data/docs/architecture.md +1 -0
  5. data/docs/deployment.md +24 -4
  6. data/docs/plugins.md +20 -10
  7. data/docs/restart.md +4 -2
  8. data/docs/systemd.md +27 -9
  9. data/docs/tcp_mode.md +96 -0
  10. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  11. data/ext/puma_http11/extconf.rb +13 -0
  12. data/ext/puma_http11/http11_parser.c +40 -63
  13. data/ext/puma_http11/http11_parser.java.rl +21 -37
  14. data/ext/puma_http11/http11_parser.rl +3 -1
  15. data/ext/puma_http11/http11_parser_common.rl +3 -3
  16. data/ext/puma_http11/mini_ssl.c +86 -4
  17. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  18. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
  19. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
  21. data/ext/puma_http11/puma_http11.c +3 -0
  22. data/lib/puma.rb +8 -0
  23. data/lib/puma/accept_nonblock.rb +7 -1
  24. data/lib/puma/app/status.rb +37 -29
  25. data/lib/puma/binder.rb +47 -68
  26. data/lib/puma/cli.rb +6 -0
  27. data/lib/puma/client.rb +244 -199
  28. data/lib/puma/cluster.rb +55 -30
  29. data/lib/puma/commonlogger.rb +2 -0
  30. data/lib/puma/configuration.rb +6 -3
  31. data/lib/puma/const.rb +32 -18
  32. data/lib/puma/control_cli.rb +41 -14
  33. data/lib/puma/detect.rb +2 -0
  34. data/lib/puma/dsl.rb +311 -77
  35. data/lib/puma/events.rb +6 -1
  36. data/lib/puma/io_buffer.rb +3 -6
  37. data/lib/puma/jruby_restart.rb +2 -0
  38. data/lib/puma/launcher.rb +99 -55
  39. data/lib/puma/minissl.rb +37 -17
  40. data/lib/puma/minissl/context_builder.rb +76 -0
  41. data/lib/puma/null_io.rb +2 -0
  42. data/lib/puma/plugin.rb +7 -2
  43. data/lib/puma/plugin/tmp_restart.rb +2 -0
  44. data/lib/puma/rack/builder.rb +4 -1
  45. data/lib/puma/rack/urlmap.rb +2 -0
  46. data/lib/puma/rack_default.rb +2 -0
  47. data/lib/puma/reactor.rb +112 -57
  48. data/lib/puma/runner.rb +13 -3
  49. data/lib/puma/server.rb +119 -48
  50. data/lib/puma/single.rb +5 -3
  51. data/lib/puma/state_file.rb +2 -0
  52. data/lib/puma/tcp_logger.rb +2 -0
  53. data/lib/puma/thread_pool.rb +17 -33
  54. data/lib/puma/util.rb +2 -6
  55. data/lib/rack/handler/puma.rb +6 -3
  56. data/tools/docker/Dockerfile +16 -0
  57. data/tools/jungle/init.d/puma +6 -6
  58. data/tools/trickletest.rb +0 -1
  59. metadata +26 -14
  60. data/lib/puma/compat.rb +0 -14
  61. data/lib/puma/convenient.rb +0 -23
  62. data/lib/puma/daemon_ext.rb +0 -31
  63. data/lib/puma/delegation.rb +0 -11
  64. data/lib/puma/java_io_buffer.rb +0 -45
  65. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/null_io.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Provides an IO-like object that always appears to contain no data.
3
5
  # Used as the value for rack.input when the request has no body.
data/lib/puma/plugin.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class UnknownPlugin < RuntimeError; end
3
5
 
@@ -60,8 +62,11 @@ module Puma
60
62
  end
61
63
 
62
64
  def fire_background
63
- @background.each do |b|
64
- Thread.new(&b)
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
65
70
  end
66
71
  end
67
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/plugin'
2
4
 
3
5
  Puma::Plugin.create do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  end
3
5
 
@@ -110,7 +112,8 @@ module Puma::Rack
110
112
 
111
113
  has_options = false
112
114
  server.valid_options.each do |name, description|
113
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
115
+ next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
116
+
114
117
  info << " -O %-21s %s" % [name, description]
115
118
  has_options = true
116
119
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma::Rack
2
4
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
5
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/handler/puma'
2
4
 
3
5
  module Rack::Handler
data/lib/puma/reactor.rb CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/util'
2
4
  require 'puma/minissl'
3
5
 
6
+ require 'nio'
7
+
4
8
  module Puma
5
9
  # Internal Docs, Not a public interface.
6
10
  #
@@ -16,12 +20,13 @@ module Puma
16
20
  #
17
21
  # ## Reactor Flow
18
22
  #
19
- # A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
20
- # The reactor stores the request in an array and calls `IO.select` on the array in a loop.
23
+ # A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
24
+ # which stores it in an array and waits for any of the connections to be ready for reading.
21
25
  #
22
- # When the request is written to by the client then the `IO.select` will "wake up" and
26
+ # The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev, Java NIO or
27
+ # just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
23
28
  # return the references to any objects that caused it to "wake". The reactor
24
- # then loops through each of these request objects, and sees if they're complete. If they
29
+ # then loops through each of these request objects, and sees if they're complete. If they
25
30
  # have a full header and body then the reactor passes the request to a thread pool.
26
31
  # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
27
32
  #
@@ -36,7 +41,7 @@ module Puma
36
41
  # Creates an instance of Puma::Reactor
37
42
  #
38
43
  # The `server` argument is an instance of `Puma::Server`
39
- # this is used to write a response for "low level errors"
44
+ # that is used to write a response for "low level errors"
40
45
  # when there is an exception inside of the reactor.
41
46
  #
42
47
  # The `app_pool` is an instance of `Puma::ThreadPool`.
@@ -47,6 +52,8 @@ module Puma
47
52
  @events = server.events
48
53
  @app_pool = app_pool
49
54
 
55
+ @selector = NIO::Selector.new
56
+
50
57
  @mutex = Mutex.new
51
58
 
52
59
  # Read / Write pipes to wake up internal while loop
@@ -55,24 +62,26 @@ module Puma
55
62
  @sleep_for = DefaultSleepFor
56
63
  @timeouts = []
57
64
 
58
- @sockets = [@ready]
65
+ mon = @selector.register(@ready, :r)
66
+ mon.value = @ready
67
+
68
+ @monitors = [mon]
59
69
  end
60
70
 
61
71
  private
62
72
 
63
-
64
73
  # Until a request is added via the `add` method this method will internally
65
74
  # loop, waiting on the `sockets` array objects. The only object in this
66
75
  # array at first is the `@ready` IO object, which is the read end of a pipe
67
76
  # connected to `@trigger` object. When `@trigger` is written to, then the loop
68
- # will break on `IO.select` and return an array.
77
+ # will break on `NIO::Selector#select` and return an array.
69
78
  #
70
79
  # ## When a request is added:
71
80
  #
72
81
  # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
73
82
  # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
74
83
  #
75
- # When that happens, the internal loop stops blocking at `IO.select` and returns a reference
84
+ # When that happens, the internal loop stops blocking at `NIO::Selector#select` and returns a reference
76
85
  # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
77
86
  # When `@trigger` is written-to, the loop "wakes" and the `ready`
78
87
  # variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
@@ -88,11 +97,11 @@ module Puma
88
97
  # to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
89
98
  #
90
99
  # Since the `Puma::Client` in this example has data that has not been read yet,
91
- # the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
100
+ # the `NIO::Selector#select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
92
101
  # `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
93
102
  #
94
103
  # Each element in the first entry is iterated over. The `Puma::Client` object is not
95
- # the `@ready` pipe, so the reactor checks to see if it has the fully header and body with
104
+ # the `@ready` pipe, so the reactor checks to see if it has the full header and body with
96
105
  # the `Puma::Client#try_to_finish` method. If the full request has been sent,
97
106
  # then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
98
107
  # can pick up the request and begin to execute application logic. This is done
@@ -100,56 +109,93 @@ module Puma
100
109
  #
101
110
  # If the request body is not present then nothing will happen, and the loop will iterate
102
111
  # again. When the client sends more data to the socket the `Puma::Client` object will
103
- # wake up the `IO.select` and it can again be checked to see if it's ready to be
112
+ # wake up the `NIO::Selector#select` and it can again be checked to see if it's ready to be
104
113
  # passed to the thread pool.
105
114
  #
106
115
  # ## Time Out Case
107
116
  #
108
- # In addition to being woken via a write to one of the sockets the `IO.select` will
117
+ # In addition to being woken via a write to one of the sockets the `NIO::Selector#select` will
109
118
  # periodically "time out" of the sleep. One of the functions of this is to check for
110
119
  # any requests that have "timed out". At the end of the loop it's checked to see if
111
- # the first element in the `@timeout` array has exceed it's allowed time. If so,
112
- # the client object is removed from the timeout aray, a 408 response is written.
113
- # Then it's connection is closed, and the object is removed from the `sockets` array
120
+ # the first element in the `@timeout` array has exceed its allowed time. If so,
121
+ # the client object is removed from the timeout array, a 408 response is written.
122
+ # Then its connection is closed, and the object is removed from the `sockets` array
114
123
  # that watches for new data.
115
124
  #
116
125
  # This behavior loops until all the objects that have timed out have been removed.
117
126
  #
118
- # Once all the timeouts have been processed, the next duration of the `IO.select` sleep
127
+ # Once all the timeouts have been processed, the next duration of the `NIO::Selector#select` sleep
119
128
  # will be set to be equal to the amount of time it will take for the next timeout to occur.
120
129
  # This calculation happens in `calculate_sleep`.
121
130
  def run_internal
122
- sockets = @sockets
131
+ monitors = @monitors
132
+ selector = @selector
123
133
 
124
134
  while true
125
135
  begin
126
- ready = IO.select sockets, nil, nil, @sleep_for
136
+ ready = selector.select @sleep_for
127
137
  rescue IOError => e
128
138
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
129
- if sockets.any? { |socket| socket.closed? }
139
+ if monitors.any? { |mon| mon.value.closed? }
130
140
  STDERR.puts "Error in select: #{e.message} (#{e.class})"
131
141
  STDERR.puts e.backtrace
132
- sockets = sockets.reject { |socket| socket.closed? }
142
+
143
+ monitors.reject! do |mon|
144
+ if mon.value.closed?
145
+ selector.deregister mon.value
146
+ true
147
+ end
148
+ end
149
+
133
150
  retry
134
151
  else
135
152
  raise
136
153
  end
137
154
  end
138
155
 
139
- if ready and reads = ready[0]
140
- reads.each do |c|
141
- if c == @ready
156
+ if ready
157
+ ready.each do |mon|
158
+ if mon.value == @ready
142
159
  @mutex.synchronize do
143
160
  case @ready.read(1)
144
161
  when "*"
145
- sockets += @input
162
+ @input.each do |c|
163
+ mon = nil
164
+ begin
165
+ begin
166
+ mon = selector.register(c, :r)
167
+ rescue ArgumentError
168
+ # There is a bug where we seem to be registering an already registered
169
+ # client. This code deals with this situation but I wish we didn't have to.
170
+ monitors.delete_if { |submon| submon.value.to_io == c.to_io }
171
+ selector.deregister(c)
172
+ mon = selector.register(c, :r)
173
+ end
174
+ rescue IOError
175
+ # Means that the io is closed, so we should ignore this request
176
+ # entirely
177
+ else
178
+ mon.value = c
179
+ @timeouts << mon if c.timeout_at
180
+ monitors << mon
181
+ end
182
+ end
146
183
  @input.clear
184
+
185
+ @timeouts.sort! { |a,b| a.value.timeout_at <=> b.value.timeout_at }
186
+ calculate_sleep
147
187
  when "c"
148
- sockets.delete_if do |s|
149
- if s == @ready
188
+ monitors.reject! do |submon|
189
+ if submon.value == @ready
150
190
  false
151
191
  else
152
- s.close
192
+ submon.value.close
193
+ begin
194
+ selector.deregister submon.value
195
+ rescue IOError
196
+ # nio4r on jruby seems to throw an IOError here if the IO is closed, so
197
+ # we need to swallow it.
198
+ end
153
199
  true
154
200
  end
155
201
  end
@@ -158,40 +204,48 @@ module Puma
158
204
  end
159
205
  end
160
206
  else
207
+ c = mon.value
208
+
161
209
  # We have to be sure to remove it from the timeout
162
210
  # list or we'll accidentally close the socket when
163
211
  # it's in use!
164
212
  if c.timeout_at
165
213
  @mutex.synchronize do
166
- @timeouts.delete c
214
+ @timeouts.delete mon
167
215
  end
168
216
  end
169
217
 
170
218
  begin
171
219
  if c.try_to_finish
172
220
  @app_pool << c
173
- sockets.delete c
221
+ clear_monitor mon
174
222
  end
175
223
 
176
224
  # Don't report these to the lowlevel_error handler, otherwise
177
225
  # will be flooding them with errors when persistent connections
178
226
  # are closed.
179
227
  rescue ConnectionError
180
- c.write_500
228
+ c.write_error(500)
181
229
  c.close
182
230
 
183
- sockets.delete c
231
+ clear_monitor mon
184
232
 
185
233
  # SSL handshake failure
186
234
  rescue MiniSSL::SSLError => e
187
235
  @server.lowlevel_error(e, c.env)
188
236
 
189
237
  ssl_socket = c.io
190
- addr = ssl_socket.peeraddr.last
238
+ begin
239
+ addr = ssl_socket.peeraddr.last
240
+ # EINVAL can happen when browser closes socket w/security exception
241
+ rescue IOError, Errno::EINVAL
242
+ addr = "<unknown>"
243
+ end
244
+
191
245
  cert = ssl_socket.peercert
192
246
 
193
247
  c.close
194
- sockets.delete c
248
+ clear_monitor mon
195
249
 
196
250
  @events.ssl_error @server, addr, cert, e
197
251
 
@@ -199,19 +253,19 @@ module Puma
199
253
  rescue HttpParserError => e
200
254
  @server.lowlevel_error(e, c.env)
201
255
 
202
- c.write_400
256
+ c.write_error(400)
203
257
  c.close
204
258
 
205
- sockets.delete c
259
+ clear_monitor mon
206
260
 
207
261
  @events.parse_error @server, c.env, e
208
262
  rescue StandardError => e
209
263
  @server.lowlevel_error(e, c.env)
210
264
 
211
- c.write_500
265
+ c.write_error(500)
212
266
  c.close
213
267
 
214
- sockets.delete c
268
+ clear_monitor mon
215
269
  end
216
270
  end
217
271
  end
@@ -221,11 +275,13 @@ module Puma
221
275
  @mutex.synchronize do
222
276
  now = Time.now
223
277
 
224
- while @timeouts.first.timeout_at < now
225
- c = @timeouts.shift
226
- c.write_408 if c.in_data_phase
278
+ while @timeouts.first.value.timeout_at < now
279
+ mon = @timeouts.shift
280
+ c = mon.value
281
+ c.write_error(408) if c.in_data_phase
227
282
  c.close
228
- sockets.delete c
283
+
284
+ clear_monitor mon
229
285
 
230
286
  break if @timeouts.empty?
231
287
  end
@@ -236,6 +292,11 @@ module Puma
236
292
  end
237
293
  end
238
294
 
295
+ def clear_monitor(mon)
296
+ @selector.deregister mon.value
297
+ @monitors.delete mon
298
+ end
299
+
239
300
  public
240
301
 
241
302
  def run
@@ -247,6 +308,7 @@ module Puma
247
308
 
248
309
  def run_in_thread
249
310
  @thread = Thread.new do
311
+ Puma.set_thread_name "reactor"
250
312
  begin
251
313
  run_internal
252
314
  rescue StandardError => e
@@ -260,7 +322,7 @@ module Puma
260
322
  end
261
323
  end
262
324
 
263
- # The `calculate_sleep` sets the value that the `IO.select` will
325
+ # The `calculate_sleep` sets the value that the `NIO::Selector#select` will
264
326
  # sleep for in the main reactor loop when no sockets are being written to.
265
327
  #
266
328
  # The values kept in `@timeouts` are sorted so that the first timeout
@@ -274,7 +336,7 @@ module Puma
274
336
  if @timeouts.empty?
275
337
  @sleep_for = DefaultSleepFor
276
338
  else
277
- diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
339
+ diff = @timeouts.first.value.timeout_at.to_f - Time.now.to_f
278
340
 
279
341
  if diff < 0.0
280
342
  @sleep_for = 0
@@ -291,18 +353,18 @@ module Puma
291
353
  # object.
292
354
  #
293
355
  # The main body of the reactor loop is in `run_internal` and it
294
- # will sleep on `IO.select`. When a new connection is added to the
295
- # reactor it cannot be added directly to the `sockets` aray, because
296
- # the `IO.select` will not be watching for it yet.
356
+ # will sleep on `NIO::Selector#select`. When a new connection is added to the
357
+ # reactor it cannot be added directly to the `sockets` array, because
358
+ # the `NIO::Selector#select` will not be watching for it yet.
297
359
  #
298
- # Instead what needs to happen is that `IO.select` needs to be woken up,
360
+ # Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
299
361
  # the contents of `@input` added to the `sockets` array, and then
300
- # another call to `IO.select` needs to happen. Since the `Puma::Client`
362
+ # another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
301
363
  # object can be read immediately, it does not block, but instead returns
302
364
  # right away.
303
365
  #
304
366
  # This behavior is accomplished by writing to `@trigger` which wakes up
305
- # the `IO.select` and then there is logic to detect the value of `*`,
367
+ # the `NIO::Selector#select` and then there is logic to detect the value of `*`,
306
368
  # pull the contents from `@input` and add them to the sockets array.
307
369
  #
308
370
  # If the object passed in has a timeout value in `timeout_at` then
@@ -313,13 +375,6 @@ module Puma
313
375
  @mutex.synchronize do
314
376
  @input << c
315
377
  @trigger << "*"
316
-
317
- if c.timeout_at
318
- @timeouts << c
319
- @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
320
-
321
- calculate_sleep
322
- end
323
378
  end
324
379
  end
325
380
 
data/lib/puma/runner.rb CHANGED
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/server'
2
4
  require 'puma/const'
5
+ require 'puma/minissl/context_builder'
3
6
 
4
7
  module Puma
5
8
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
@@ -12,6 +15,7 @@ module Puma
12
15
  @options = cli.options
13
16
  @app = nil
14
17
  @control = nil
18
+ @started_at = Time.now
15
19
  end
16
20
 
17
21
  def daemon?
@@ -50,17 +54,23 @@ module Puma
50
54
 
51
55
  uri = URI.parse str
52
56
 
53
- app = Puma::App::Status.new @launcher
54
-
55
57
  if token = @options[:control_auth_token]
56
- app.auth_token = token unless token.empty? or token == :none
58
+ token = nil if token.empty? || token == 'none'
57
59
  end
58
60
 
61
+ app = Puma::App::Status.new @launcher, token
62
+
59
63
  control = Puma::Server.new app, @launcher.events
60
64
  control.min_threads = 0
61
65
  control.max_threads = 1
62
66
 
63
67
  case uri.scheme
68
+ when "ssl"
69
+ log "* Starting control server on #{str}"
70
+ params = Util.parse_query uri.query
71
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
+
73
+ control.add_ssl_listener uri.host, uri.port, ctx
64
74
  when "tcp"
65
75
  log "* Starting control server on #{str}"
66
76
  control.add_tcp_listener uri.host, uri.port