puma 4.3.0

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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1532 -0
  3. data/LICENSE +26 -0
  4. data/README.md +291 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -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/nginx.md +80 -0
  14. data/docs/plugins.md +38 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/docs/tcp_mode.md +96 -0
  19. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  20. data/ext/puma_http11/ext_help.h +15 -0
  21. data/ext/puma_http11/extconf.rb +28 -0
  22. data/ext/puma_http11/http11_parser.c +1044 -0
  23. data/ext/puma_http11/http11_parser.h +65 -0
  24. data/ext/puma_http11/http11_parser.java.rl +145 -0
  25. data/ext/puma_http11/http11_parser.rl +147 -0
  26. data/ext/puma_http11/http11_parser_common.rl +54 -0
  27. data/ext/puma_http11/io_buffer.c +155 -0
  28. data/ext/puma_http11/mini_ssl.c +553 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  31. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  32. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  33. data/ext/puma_http11/puma_http11.c +502 -0
  34. data/lib/puma.rb +31 -0
  35. data/lib/puma/accept_nonblock.rb +29 -0
  36. data/lib/puma/app/status.rb +80 -0
  37. data/lib/puma/binder.rb +385 -0
  38. data/lib/puma/cli.rb +239 -0
  39. data/lib/puma/client.rb +494 -0
  40. data/lib/puma/cluster.rb +554 -0
  41. data/lib/puma/commonlogger.rb +108 -0
  42. data/lib/puma/configuration.rb +362 -0
  43. data/lib/puma/const.rb +235 -0
  44. data/lib/puma/control_cli.rb +289 -0
  45. data/lib/puma/detect.rb +15 -0
  46. data/lib/puma/dsl.rb +740 -0
  47. data/lib/puma/events.rb +156 -0
  48. data/lib/puma/io_buffer.rb +4 -0
  49. data/lib/puma/jruby_restart.rb +84 -0
  50. data/lib/puma/launcher.rb +475 -0
  51. data/lib/puma/minissl.rb +278 -0
  52. data/lib/puma/minissl/context_builder.rb +76 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/rack/builder.rb +301 -0
  57. data/lib/puma/rack/urlmap.rb +93 -0
  58. data/lib/puma/rack_default.rb +9 -0
  59. data/lib/puma/reactor.rb +400 -0
  60. data/lib/puma/runner.rb +192 -0
  61. data/lib/puma/server.rb +1030 -0
  62. data/lib/puma/single.rb +123 -0
  63. data/lib/puma/state_file.rb +31 -0
  64. data/lib/puma/tcp_logger.rb +41 -0
  65. data/lib/puma/thread_pool.rb +328 -0
  66. data/lib/puma/util.rb +124 -0
  67. data/lib/rack/handler/puma.rb +115 -0
  68. data/tools/docker/Dockerfile +16 -0
  69. data/tools/jungle/README.md +19 -0
  70. data/tools/jungle/init.d/README.md +61 -0
  71. data/tools/jungle/init.d/puma +421 -0
  72. data/tools/jungle/init.d/run-puma +18 -0
  73. data/tools/jungle/rc.d/README.md +74 -0
  74. data/tools/jungle/rc.d/puma +61 -0
  75. data/tools/jungle/rc.d/puma.conf +10 -0
  76. data/tools/jungle/upstart/README.md +61 -0
  77. data/tools/jungle/upstart/puma-manager.conf +31 -0
  78. data/tools/jungle/upstart/puma.conf +69 -0
  79. data/tools/trickletest.rb +44 -0
  80. metadata +144 -0
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/runner'
4
+ require 'puma/detect'
5
+ require 'puma/plugin'
6
+
7
+ module Puma
8
+ # This class is instantiated by the `Puma::Launcher` and used
9
+ # to boot and serve a Ruby application when no puma "workers" are needed
10
+ # i.e. only using "threaded" mode. For example `$ puma -t 1:5`
11
+ #
12
+ # At the core of this class is running an instance of `Puma::Server` which
13
+ # gets created via the `start_server` method from the `Puma::Runner` class
14
+ # that this inherits from.
15
+ class Single < Runner
16
+ def stats
17
+ b = @server.backlog || 0
18
+ r = @server.running || 0
19
+ t = @server.pool_capacity || 0
20
+ m = @server.max_threads || 0
21
+ %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
22
+ end
23
+
24
+ def restart
25
+ @server.begin_restart
26
+ end
27
+
28
+ def stop
29
+ @server.stop(false) if @server
30
+ end
31
+
32
+ def halt
33
+ @server.halt
34
+ end
35
+
36
+ def stop_blocked
37
+ log "- Gracefully stopping, waiting for requests to finish"
38
+ @control.stop(true) if @control
39
+ @server.stop(true) if @server
40
+ end
41
+
42
+ def jruby_daemon?
43
+ daemon? and Puma.jruby?
44
+ end
45
+
46
+ def jruby_daemon_start
47
+ require 'puma/jruby_restart'
48
+ JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
49
+ end
50
+
51
+ def run
52
+ already_daemon = false
53
+
54
+ if jruby_daemon?
55
+ require 'puma/jruby_restart'
56
+
57
+ if JRubyRestart.daemon?
58
+ # load and bind before redirecting IO so errors show up on stdout/stderr
59
+ load_and_bind
60
+ redirect_io
61
+ end
62
+
63
+ already_daemon = JRubyRestart.daemon_init
64
+ end
65
+
66
+ output_header "single"
67
+
68
+ if jruby_daemon?
69
+ if already_daemon
70
+ JRubyRestart.perm_daemonize
71
+ else
72
+ pid = nil
73
+
74
+ Signal.trap "SIGUSR2" do
75
+ log "* Started new process #{pid} as daemon..."
76
+
77
+ # Must use exit! so we don't unwind and run the ensures
78
+ # that will be run by the new child (such as deleting the
79
+ # pidfile)
80
+ exit!(true)
81
+ end
82
+
83
+ Signal.trap "SIGCHLD" do
84
+ log "! Error starting new process as daemon, exiting"
85
+ exit 1
86
+ end
87
+
88
+ jruby_daemon_start
89
+ sleep
90
+ end
91
+ else
92
+ if daemon?
93
+ log "* Daemonizing..."
94
+ Process.daemon(true)
95
+ redirect_io
96
+ end
97
+
98
+ load_and_bind
99
+ end
100
+
101
+ Plugins.fire_background
102
+
103
+ @launcher.write_state
104
+
105
+ start_control
106
+
107
+ @server = server = start_server
108
+
109
+ unless daemon?
110
+ log "Use Ctrl-C to stop"
111
+ redirect_io
112
+ end
113
+
114
+ @launcher.events.fire_on_booted!
115
+
116
+ begin
117
+ server.run.join
118
+ rescue Interrupt
119
+ # Swallow it
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Puma
6
+ class StateFile
7
+ def initialize
8
+ @options = {}
9
+ end
10
+
11
+ def save(path)
12
+ File.write path, YAML.dump(@options)
13
+ end
14
+
15
+ def load(path)
16
+ @options = YAML.load File.read(path)
17
+ end
18
+
19
+ FIELDS = %w!control_url control_auth_token pid!
20
+
21
+ FIELDS.each do |f|
22
+ define_method f do
23
+ @options[f]
24
+ end
25
+
26
+ define_method "#{f}=" do |v|
27
+ @options[f] = v
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class TCPLogger
5
+ def initialize(logger, app, quiet=false)
6
+ @logger = logger
7
+ @app = app
8
+ @quiet = quiet
9
+ end
10
+
11
+ FORMAT = "%s - %s"
12
+
13
+ def log(who, str)
14
+ now = Time.now.strftime("%d/%b/%Y %H:%M:%S")
15
+
16
+ log_str = "#{now} - #{who} - #{str}"
17
+
18
+ case @logger
19
+ when IO
20
+ @logger.puts log_str
21
+ when Events
22
+ @logger.log log_str
23
+ end
24
+ end
25
+
26
+ def call(env, socket)
27
+ who = env[Const::REMOTE_ADDR]
28
+ log who, "connected" unless @quiet
29
+
30
+ env['log'] = lambda { |str| log(who, str) }
31
+
32
+ begin
33
+ @app.call env, socket
34
+ rescue Object => e
35
+ log who, "exception: #{e.message} (#{e.class})"
36
+ else
37
+ log who, "disconnected" unless @quiet
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module Puma
6
+ # Internal Docs for A simple thread pool management object.
7
+ #
8
+ # Each Puma "worker" has a thread pool to process requests.
9
+ #
10
+ # First a connection to a client is made in `Puma::Server`. It is wrapped in a
11
+ # `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
12
+ # the whole request is buffered into memory. Once the request is ready, it is passed into
13
+ # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
14
+ #
15
+ # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
16
+ # and proceses it.
17
+ class ThreadPool
18
+ class ForceShutdown < RuntimeError
19
+ end
20
+
21
+ # How long, after raising the ForceShutdown of a thread during
22
+ # forced shutdown mode, to wait for the thread to try and finish
23
+ # up its work before leaving the thread to die on the vine.
24
+ SHUTDOWN_GRACE_TIME = 5 # seconds
25
+
26
+ # Maintain a minimum of +min+ and maximum of +max+ threads
27
+ # in the pool.
28
+ #
29
+ # The block passed is the work that will be performed in each
30
+ # thread.
31
+ #
32
+ def initialize(min, max, *extra, &block)
33
+ @not_empty = ConditionVariable.new
34
+ @not_full = ConditionVariable.new
35
+ @mutex = Mutex.new
36
+
37
+ @todo = []
38
+
39
+ @spawned = 0
40
+ @waiting = 0
41
+
42
+ @min = Integer(min)
43
+ @max = Integer(max)
44
+ @block = block
45
+ @extra = extra
46
+
47
+ @shutdown = false
48
+
49
+ @trim_requested = 0
50
+
51
+ @workers = []
52
+
53
+ @auto_trim = nil
54
+ @reaper = nil
55
+
56
+ @mutex.synchronize do
57
+ @min.times { spawn_thread }
58
+ end
59
+
60
+ @clean_thread_locals = false
61
+ end
62
+
63
+ attr_reader :spawned, :trim_requested, :waiting
64
+ attr_accessor :clean_thread_locals
65
+
66
+ def self.clean_thread_locals
67
+ Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
68
+ Thread.current[key] = nil unless key == :__recursive_key__
69
+ end
70
+ end
71
+
72
+ # How many objects have yet to be processed by the pool?
73
+ #
74
+ def backlog
75
+ @mutex.synchronize { @todo.size }
76
+ end
77
+
78
+ def pool_capacity
79
+ waiting + (@max - spawned)
80
+ end
81
+
82
+ # :nodoc:
83
+ #
84
+ # Must be called with @mutex held!
85
+ #
86
+ def spawn_thread
87
+ @spawned += 1
88
+
89
+ th = Thread.new(@spawned) do |spawned|
90
+ Puma.set_thread_name 'threadpool %03i' % spawned
91
+ todo = @todo
92
+ block = @block
93
+ mutex = @mutex
94
+ not_empty = @not_empty
95
+ not_full = @not_full
96
+
97
+ extra = @extra.map { |i| i.new }
98
+
99
+ while true
100
+ work = nil
101
+
102
+ continue = true
103
+
104
+ mutex.synchronize do
105
+ while todo.empty?
106
+ if @trim_requested > 0
107
+ @trim_requested -= 1
108
+ continue = false
109
+ not_full.signal
110
+ break
111
+ end
112
+
113
+ if @shutdown
114
+ continue = false
115
+ break
116
+ end
117
+
118
+ @waiting += 1
119
+ not_full.signal
120
+ not_empty.wait mutex
121
+ @waiting -= 1
122
+ end
123
+
124
+ work = todo.shift if continue
125
+ end
126
+
127
+ break unless continue
128
+
129
+ if @clean_thread_locals
130
+ ThreadPool.clean_thread_locals
131
+ end
132
+
133
+ begin
134
+ block.call(work, *extra)
135
+ rescue Exception => e
136
+ STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
137
+ end
138
+ end
139
+
140
+ mutex.synchronize do
141
+ @spawned -= 1
142
+ @workers.delete th
143
+ end
144
+ end
145
+
146
+ @workers << th
147
+
148
+ th
149
+ end
150
+
151
+ private :spawn_thread
152
+
153
+ # Add +work+ to the todo list for a Thread to pickup and process.
154
+ def <<(work)
155
+ @mutex.synchronize do
156
+ if @shutdown
157
+ raise "Unable to add work while shutting down"
158
+ end
159
+
160
+ @todo << work
161
+
162
+ if @waiting < @todo.size and @spawned < @max
163
+ spawn_thread
164
+ end
165
+
166
+ @not_empty.signal
167
+ end
168
+ end
169
+
170
+ # This method is used by `Puma::Server` to let the server know when
171
+ # the thread pool can pull more requests from the socket and
172
+ # pass to the reactor.
173
+ #
174
+ # The general idea is that the thread pool can only work on a fixed
175
+ # number of requests at the same time. If it is already processing that
176
+ # number of requests then it is at capacity. If another Puma process has
177
+ # spare capacity, then the request can be left on the socket so the other
178
+ # worker can pick it up and process it.
179
+ #
180
+ # For example: if there are 5 threads, but only 4 working on
181
+ # requests, this method will not wait and the `Puma::Server`
182
+ # can pull a request right away.
183
+ #
184
+ # If there are 5 threads and all 5 of them are busy, then it will
185
+ # pause here, and wait until the `not_full` condition variable is
186
+ # signaled, usually this indicates that a request has been processed.
187
+ #
188
+ # It's important to note that even though the server might accept another
189
+ # request, it might not be added to the `@todo` array right away.
190
+ # For example if a slow client has only sent a header, but not a body
191
+ # then the `@todo` array would stay the same size as the reactor works
192
+ # to try to buffer the request. In that scenario the next call to this
193
+ # method would not block and another request would be added into the reactor
194
+ # by the server. This would continue until a fully bufferend request
195
+ # makes it through the reactor and can then be processed by the thread pool.
196
+ #
197
+ # Returns the current number of busy threads, or +nil+ if shutting down.
198
+ #
199
+ def wait_until_not_full
200
+ @mutex.synchronize do
201
+ while true
202
+ return if @shutdown
203
+
204
+ # If we can still spin up new threads and there
205
+ # is work queued that cannot be handled by waiting
206
+ # threads, then accept more work until we would
207
+ # spin up the max number of threads.
208
+ busy_threads = @spawned - @waiting + @todo.size
209
+ return busy_threads if @max > busy_threads
210
+
211
+ @not_full.wait @mutex
212
+ end
213
+ end
214
+ end
215
+
216
+ # If too many threads are in the pool, tell one to finish go ahead
217
+ # and exit. If +force+ is true, then a trim request is requested
218
+ # even if all threads are being utilized.
219
+ #
220
+ def trim(force=false)
221
+ @mutex.synchronize do
222
+ if (force or @waiting > 0) and @spawned - @trim_requested > @min
223
+ @trim_requested += 1
224
+ @not_empty.signal
225
+ end
226
+ end
227
+ end
228
+
229
+ # If there are dead threads in the pool make them go away while decreasing
230
+ # spawned counter so that new healthy threads could be created again.
231
+ def reap
232
+ @mutex.synchronize do
233
+ dead_workers = @workers.reject(&:alive?)
234
+
235
+ dead_workers.each do |worker|
236
+ worker.kill
237
+ @spawned -= 1
238
+ end
239
+
240
+ @workers.delete_if do |w|
241
+ dead_workers.include?(w)
242
+ end
243
+ end
244
+ end
245
+
246
+ class Automaton
247
+ def initialize(pool, timeout, thread_name, message)
248
+ @pool = pool
249
+ @timeout = timeout
250
+ @thread_name = thread_name
251
+ @message = message
252
+ @running = false
253
+ end
254
+
255
+ def start!
256
+ @running = true
257
+
258
+ @thread = Thread.new do
259
+ Puma.set_thread_name @thread_name
260
+ while @running
261
+ @pool.public_send(@message)
262
+ sleep @timeout
263
+ end
264
+ end
265
+ end
266
+
267
+ def stop
268
+ @running = false
269
+ @thread.wakeup
270
+ end
271
+ end
272
+
273
+ def auto_trim!(timeout=30)
274
+ @auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
275
+ @auto_trim.start!
276
+ end
277
+
278
+ def auto_reap!(timeout=5)
279
+ @reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
280
+ @reaper.start!
281
+ end
282
+
283
+ # Tell all threads in the pool to exit and wait for them to finish.
284
+ #
285
+ def shutdown(timeout=-1)
286
+ threads = @mutex.synchronize do
287
+ @shutdown = true
288
+ @not_empty.broadcast
289
+ @not_full.broadcast
290
+
291
+ @auto_trim.stop if @auto_trim
292
+ @reaper.stop if @reaper
293
+ # dup workers so that we join them all safely
294
+ @workers.dup
295
+ end
296
+
297
+ if timeout == -1
298
+ # Wait for threads to finish without force shutdown.
299
+ threads.each(&:join)
300
+ else
301
+ # Wait for threads to finish after n attempts (+timeout+).
302
+ # If threads are still running, it will forcefully kill them.
303
+ timeout.times do
304
+ threads.delete_if do |t|
305
+ t.join 1
306
+ end
307
+
308
+ if threads.empty?
309
+ break
310
+ else
311
+ sleep 1
312
+ end
313
+ end
314
+
315
+ threads.each do |t|
316
+ t.raise ForceShutdown
317
+ end
318
+
319
+ threads.each do |t|
320
+ t.join SHUTDOWN_GRACE_TIME
321
+ end
322
+ end
323
+
324
+ @spawned = 0
325
+ @workers = []
326
+ end
327
+ end
328
+ end