gitlab-puma 4.3.1.gitlab.2

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.
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