gitlab-puma 4.3.1.gitlab.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1537 -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 +242 -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 +1053 -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 +348 -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 +147 -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,348 @@
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 busy
79
+ spawned - waiting
80
+ end
81
+
82
+ def pool_capacity
83
+ waiting + (@max - spawned)
84
+ end
85
+
86
+ # :nodoc:
87
+ #
88
+ # Must be called with @mutex held!
89
+ #
90
+ def spawn_thread
91
+ @spawned += 1
92
+
93
+ th = Thread.new(@spawned) do |spawned|
94
+ Puma.set_thread_name 'threadpool %03i' % spawned
95
+ todo = @todo
96
+ block = @block
97
+ mutex = @mutex
98
+ not_empty = @not_empty
99
+ not_full = @not_full
100
+
101
+ extra = @extra.map { |i| i.new }
102
+
103
+ while true
104
+ work = nil
105
+
106
+ continue = true
107
+
108
+ mutex.synchronize do
109
+ while todo.empty?
110
+ if @trim_requested > 0
111
+ @trim_requested -= 1
112
+ continue = false
113
+ not_full.signal
114
+ break
115
+ end
116
+
117
+ if @shutdown
118
+ continue = false
119
+ break
120
+ end
121
+
122
+ @waiting += 1
123
+ not_full.signal
124
+ not_empty.wait mutex
125
+ @waiting -= 1
126
+ end
127
+
128
+ work = todo.shift if continue
129
+ end
130
+
131
+ break unless continue
132
+
133
+ if @clean_thread_locals
134
+ ThreadPool.clean_thread_locals
135
+ end
136
+
137
+ begin
138
+ block.call(work, *extra)
139
+ rescue Exception => e
140
+ STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
141
+ end
142
+ end
143
+
144
+ mutex.synchronize do
145
+ @spawned -= 1
146
+ @workers.delete th
147
+ end
148
+ end
149
+
150
+ @workers << th
151
+
152
+ th
153
+ end
154
+
155
+ private :spawn_thread
156
+
157
+ # Add +work+ to the todo list for a Thread to pickup and process.
158
+ def <<(work)
159
+ @mutex.synchronize do
160
+ if @shutdown
161
+ raise "Unable to add work while shutting down"
162
+ end
163
+
164
+ @todo << work
165
+
166
+ if @waiting < @todo.size and @spawned < @max
167
+ spawn_thread
168
+ end
169
+
170
+ @not_empty.signal
171
+ end
172
+ end
173
+
174
+ # This method is used by `Puma::Server` to let the server know when
175
+ # the thread pool can pull more requests from the socket and
176
+ # pass to the reactor.
177
+ #
178
+ # The general idea is that the thread pool can only work on a fixed
179
+ # number of requests at the same time. If it is already processing that
180
+ # number of requests then it is at capacity. If another Puma process has
181
+ # spare capacity, then the request can be left on the socket so the other
182
+ # worker can pick it up and process it.
183
+ #
184
+ # For example: if there are 5 threads, but only 4 working on
185
+ # requests, this method will not wait and the `Puma::Server`
186
+ # can pull a request right away.
187
+ #
188
+ # If there are 5 threads and all 5 of them are busy, then it will
189
+ # pause here, and wait until the `not_full` condition variable is
190
+ # signaled, usually this indicates that a request has been processed.
191
+ #
192
+ # It's important to note that even though the server might accept another
193
+ # request, it might not be added to the `@todo` array right away.
194
+ # For example if a slow client has only sent a header, but not a body
195
+ # then the `@todo` array would stay the same size as the reactor works
196
+ # to try to buffer the request. In that scenario the next call to this
197
+ # method would not block and another request would be added into the reactor
198
+ # by the server. This would continue until a fully bufferend request
199
+ # makes it through the reactor and can then be processed by the thread pool.
200
+ #
201
+ # Returns the current number of busy threads, or +nil+ if shutting down.
202
+ #
203
+ def wait_until_not_full
204
+ @mutex.synchronize do
205
+ while true
206
+ return if @shutdown
207
+
208
+ # If we can still spin up new threads and there
209
+ # is work queued that cannot be handled by waiting
210
+ # threads, then accept more work until we would
211
+ # spin up the max number of threads.
212
+ busy_threads = @spawned - @waiting + @todo.size
213
+ return busy_threads if @max > busy_threads
214
+
215
+ @not_full.wait @mutex
216
+ end
217
+ end
218
+ end
219
+
220
+ def wait_for_threads_to_finish(tick_time: 0.001, max_wait_ticks: 3)
221
+ @mutex.synchronize do
222
+ max_wait_ticks.times do |tick|
223
+ return if @shutdown
224
+
225
+ # if we do wait for more than busy threads
226
+ # and what is in backlog, stop
227
+ break if tick >= busy + @todo.size
228
+
229
+ # this will be signaled once a request finishes,
230
+ # which can happen earlier than tick time
231
+ @not_full.wait @mutex, tick_time
232
+ end
233
+ end
234
+ end
235
+
236
+ # If too many threads are in the pool, tell one to finish go ahead
237
+ # and exit. If +force+ is true, then a trim request is requested
238
+ # even if all threads are being utilized.
239
+ #
240
+ def trim(force=false)
241
+ @mutex.synchronize do
242
+ if (force or @waiting > 0) and @spawned - @trim_requested > @min
243
+ @trim_requested += 1
244
+ @not_empty.signal
245
+ end
246
+ end
247
+ end
248
+
249
+ # If there are dead threads in the pool make them go away while decreasing
250
+ # spawned counter so that new healthy threads could be created again.
251
+ def reap
252
+ @mutex.synchronize do
253
+ dead_workers = @workers.reject(&:alive?)
254
+
255
+ dead_workers.each do |worker|
256
+ worker.kill
257
+ @spawned -= 1
258
+ end
259
+
260
+ @workers.delete_if do |w|
261
+ dead_workers.include?(w)
262
+ end
263
+ end
264
+ end
265
+
266
+ class Automaton
267
+ def initialize(pool, timeout, thread_name, message)
268
+ @pool = pool
269
+ @timeout = timeout
270
+ @thread_name = thread_name
271
+ @message = message
272
+ @running = false
273
+ end
274
+
275
+ def start!
276
+ @running = true
277
+
278
+ @thread = Thread.new do
279
+ Puma.set_thread_name @thread_name
280
+ while @running
281
+ @pool.public_send(@message)
282
+ sleep @timeout
283
+ end
284
+ end
285
+ end
286
+
287
+ def stop
288
+ @running = false
289
+ @thread.wakeup
290
+ end
291
+ end
292
+
293
+ def auto_trim!(timeout=30)
294
+ @auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
295
+ @auto_trim.start!
296
+ end
297
+
298
+ def auto_reap!(timeout=5)
299
+ @reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
300
+ @reaper.start!
301
+ end
302
+
303
+ # Tell all threads in the pool to exit and wait for them to finish.
304
+ #
305
+ def shutdown(timeout=-1)
306
+ threads = @mutex.synchronize do
307
+ @shutdown = true
308
+ @not_empty.broadcast
309
+ @not_full.broadcast
310
+
311
+ @auto_trim.stop if @auto_trim
312
+ @reaper.stop if @reaper
313
+ # dup workers so that we join them all safely
314
+ @workers.dup
315
+ end
316
+
317
+ if timeout == -1
318
+ # Wait for threads to finish without force shutdown.
319
+ threads.each(&:join)
320
+ else
321
+ # Wait for threads to finish after n attempts (+timeout+).
322
+ # If threads are still running, it will forcefully kill them.
323
+ timeout.times do
324
+ threads.delete_if do |t|
325
+ t.join 1
326
+ end
327
+
328
+ if threads.empty?
329
+ break
330
+ else
331
+ sleep 1
332
+ end
333
+ end
334
+
335
+ threads.each do |t|
336
+ t.raise ForceShutdown
337
+ end
338
+
339
+ threads.each do |t|
340
+ t.join SHUTDOWN_GRACE_TIME
341
+ end
342
+ end
343
+
344
+ @spawned = 0
345
+ @workers = []
346
+ end
347
+ end
348
+ end