piesync-puma 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1429 -0
  3. data/LICENSE +26 -0
  4. data/README.md +280 -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 +36 -0
  9. data/docs/deployment.md +91 -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 +28 -0
  15. data/docs/restart.md +39 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +272 -0
  18. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  19. data/ext/puma_http11/ext_help.h +15 -0
  20. data/ext/puma_http11/extconf.rb +15 -0
  21. data/ext/puma_http11/http11_parser.c +1071 -0
  22. data/ext/puma_http11/http11_parser.h +65 -0
  23. data/ext/puma_http11/http11_parser.java.rl +161 -0
  24. data/ext/puma_http11/http11_parser.rl +149 -0
  25. data/ext/puma_http11/http11_parser_common.rl +54 -0
  26. data/ext/puma_http11/io_buffer.c +155 -0
  27. data/ext/puma_http11/mini_ssl.c +494 -0
  28. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +470 -0
  30. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +352 -0
  31. data/ext/puma_http11/puma_http11.c +500 -0
  32. data/lib/puma.rb +23 -0
  33. data/lib/puma/accept_nonblock.rb +23 -0
  34. data/lib/puma/app/status.rb +74 -0
  35. data/lib/puma/binder.rb +413 -0
  36. data/lib/puma/cli.rb +235 -0
  37. data/lib/puma/client.rb +480 -0
  38. data/lib/puma/cluster.rb +531 -0
  39. data/lib/puma/commonlogger.rb +108 -0
  40. data/lib/puma/compat.rb +14 -0
  41. data/lib/puma/configuration.rb +361 -0
  42. data/lib/puma/const.rb +239 -0
  43. data/lib/puma/control_cli.rb +264 -0
  44. data/lib/puma/convenient.rb +25 -0
  45. data/lib/puma/daemon_ext.rb +33 -0
  46. data/lib/puma/delegation.rb +13 -0
  47. data/lib/puma/detect.rb +15 -0
  48. data/lib/puma/dsl.rb +518 -0
  49. data/lib/puma/events.rb +153 -0
  50. data/lib/puma/io_buffer.rb +9 -0
  51. data/lib/puma/java_io_buffer.rb +47 -0
  52. data/lib/puma/jruby_restart.rb +84 -0
  53. data/lib/puma/launcher.rb +433 -0
  54. data/lib/puma/minissl.rb +285 -0
  55. data/lib/puma/null_io.rb +44 -0
  56. data/lib/puma/plugin.rb +117 -0
  57. data/lib/puma/plugin/tmp_restart.rb +34 -0
  58. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  59. data/lib/puma/rack/builder.rb +299 -0
  60. data/lib/puma/rack/urlmap.rb +91 -0
  61. data/lib/puma/rack_default.rb +7 -0
  62. data/lib/puma/reactor.rb +347 -0
  63. data/lib/puma/runner.rb +184 -0
  64. data/lib/puma/server.rb +1072 -0
  65. data/lib/puma/single.rb +123 -0
  66. data/lib/puma/state_file.rb +31 -0
  67. data/lib/puma/tcp_logger.rb +41 -0
  68. data/lib/puma/thread_pool.rb +346 -0
  69. data/lib/puma/util.rb +129 -0
  70. data/lib/rack/handler/puma.rb +115 -0
  71. data/tools/jungle/README.md +19 -0
  72. data/tools/jungle/init.d/README.md +61 -0
  73. data/tools/jungle/init.d/puma +421 -0
  74. data/tools/jungle/init.d/run-puma +18 -0
  75. data/tools/jungle/rc.d/README.md +74 -0
  76. data/tools/jungle/rc.d/puma +61 -0
  77. data/tools/jungle/rc.d/puma.conf +10 -0
  78. data/tools/jungle/upstart/README.md +61 -0
  79. data/tools/jungle/upstart/puma-manager.conf +31 -0
  80. data/tools/jungle/upstart/puma.conf +69 -0
  81. data/tools/trickletest.rb +45 -0
  82. metadata +131 -0
@@ -0,0 +1,531 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/runner'
4
+ require 'puma/util'
5
+ require 'puma/plugin'
6
+
7
+ require 'time'
8
+
9
+ module Puma
10
+ # This class is instantiated by the `Puma::Launcher` and used
11
+ # to boot and serve a Ruby application when puma "workers" are needed
12
+ # i.e. when using multi-processes. For example `$ puma -w 5`
13
+ #
14
+ # At the core of this class is running an instance of `Puma::Server` which
15
+ # gets created via the `start_server` method from the `Puma::Runner` class
16
+ # that this inherits from.
17
+ #
18
+ # An instance of this class will spawn the number of processes passed in
19
+ # via the `spawn_workers` method call. Each worker will have it's own
20
+ # instance of a `Puma::Server`.
21
+ class Cluster < Runner
22
+ WORKER_CHECK_INTERVAL = 5
23
+
24
+ def initialize(cli, events)
25
+ super cli, events
26
+
27
+ @phase = 0
28
+ @workers = []
29
+ @next_check = nil
30
+
31
+ @phased_state = :idle
32
+ @phased_restart = false
33
+ end
34
+
35
+ def stop_workers
36
+ log "- Gracefully shutting down workers..."
37
+ @workers.each { |x| x.term }
38
+
39
+ begin
40
+ @workers.each { |w| Process.waitpid(w.pid) }
41
+ rescue Interrupt
42
+ log "! Cancelled waiting for workers"
43
+ end
44
+ end
45
+
46
+ def start_phased_restart
47
+ @phase += 1
48
+ log "- Starting phased worker restart, phase: #{@phase}"
49
+
50
+ # Be sure to change the directory again before loading
51
+ # the app. This way we can pick up new code.
52
+ dir = @launcher.restart_dir
53
+ log "+ Changing to #{dir}"
54
+ Dir.chdir dir
55
+ end
56
+
57
+ def redirect_io
58
+ super
59
+
60
+ @workers.each { |x| x.hup }
61
+ end
62
+
63
+ class Worker
64
+ def initialize(idx, pid, phase, options)
65
+ @index = idx
66
+ @pid = pid
67
+ @phase = phase
68
+ @stage = :started
69
+ @signal = "TERM"
70
+ @options = options
71
+ @first_term_sent = nil
72
+ @last_checkin = Time.now
73
+ @last_status = '{}'
74
+ @dead = false
75
+ end
76
+
77
+ attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
78
+
79
+ def booted?
80
+ @stage == :booted
81
+ end
82
+
83
+ def boot!
84
+ @last_checkin = Time.now
85
+ @stage = :booted
86
+ end
87
+
88
+ def dead?
89
+ @dead
90
+ end
91
+
92
+ def dead!
93
+ @dead = true
94
+ end
95
+
96
+ def ping!(status)
97
+ @last_checkin = Time.now
98
+ @last_status = status
99
+ end
100
+
101
+ def ping_timeout?(which)
102
+ Time.now - @last_checkin > which
103
+ end
104
+
105
+ def term
106
+ begin
107
+ if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
108
+ @signal = "KILL"
109
+ else
110
+ @first_term_sent ||= Time.now
111
+ end
112
+
113
+ Process.kill @signal, @pid
114
+ rescue Errno::ESRCH
115
+ end
116
+ end
117
+
118
+ def kill
119
+ Process.kill "KILL", @pid
120
+ rescue Errno::ESRCH
121
+ end
122
+
123
+ def hup
124
+ Process.kill "HUP", @pid
125
+ rescue Errno::ESRCH
126
+ end
127
+ end
128
+
129
+ def spawn_workers
130
+ diff = @options[:workers] - @workers.size
131
+ return if diff < 1
132
+
133
+ master = Process.pid
134
+
135
+ diff.times do
136
+ idx = next_worker_index
137
+ @launcher.config.run_hooks :before_worker_fork, idx
138
+
139
+ pid = fork { worker(idx, master) }
140
+ if !pid
141
+ log "! Complete inability to spawn new workers detected"
142
+ log "! Seppuku is the only choice."
143
+ exit! 1
144
+ end
145
+
146
+ debug "Spawned worker: #{pid}"
147
+ @workers << Worker.new(idx, pid, @phase, @options)
148
+
149
+ @launcher.config.run_hooks :after_worker_fork, idx
150
+ end
151
+
152
+ if diff > 0
153
+ @phased_state = :idle
154
+ end
155
+ end
156
+
157
+ def cull_workers
158
+ diff = @workers.size - @options[:workers]
159
+ return if diff < 1
160
+
161
+ debug "Culling #{diff.inspect} workers"
162
+
163
+ workers_to_cull = @workers[-diff,diff]
164
+ debug "Workers to cull: #{workers_to_cull.inspect}"
165
+
166
+ workers_to_cull.each do |worker|
167
+ log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
168
+ worker.term
169
+ end
170
+ end
171
+
172
+ def next_worker_index
173
+ all_positions = 0...@options[:workers]
174
+ occupied_positions = @workers.map { |w| w.index }
175
+ available_positions = all_positions.to_a - occupied_positions
176
+ available_positions.first
177
+ end
178
+
179
+ def all_workers_booted?
180
+ @workers.count { |w| !w.booted? } == 0
181
+ end
182
+
183
+ def check_workers(force=false)
184
+ return if !force && @next_check && @next_check >= Time.now
185
+
186
+ @next_check = Time.now + WORKER_CHECK_INTERVAL
187
+
188
+ any = false
189
+
190
+ @workers.each do |w|
191
+ next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
192
+ if w.ping_timeout?(@options[:worker_timeout])
193
+ log "! Terminating timed out worker: #{w.pid}"
194
+ w.kill
195
+ any = true
196
+ end
197
+ end
198
+
199
+ # If we killed any timed out workers, try to catch them
200
+ # during this loop by giving the kernel time to kill them.
201
+ sleep 1 if any
202
+
203
+ while @workers.any?
204
+ pid = Process.waitpid(-1, Process::WNOHANG)
205
+ break unless pid
206
+
207
+ @workers.delete_if { |w| w.pid == pid }
208
+ end
209
+
210
+ @workers.delete_if(&:dead?)
211
+
212
+ cull_workers
213
+ spawn_workers
214
+
215
+ if all_workers_booted?
216
+ # If we're running at proper capacity, check to see if
217
+ # we need to phase any workers out (which will restart
218
+ # in the right phase).
219
+ #
220
+ w = @workers.find { |x| x.phase != @phase }
221
+
222
+ if w
223
+ if @phased_state == :idle
224
+ @phased_state = :waiting
225
+ log "- Stopping #{w.pid} for phased upgrade..."
226
+ end
227
+
228
+ w.term
229
+ log "- #{w.signal} sent to #{w.pid}..."
230
+ end
231
+ end
232
+ end
233
+
234
+ def wakeup!
235
+ return unless @wakeup
236
+
237
+ begin
238
+ @wakeup.write "!" unless @wakeup.closed?
239
+ rescue SystemCallError, IOError
240
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
241
+ end
242
+ end
243
+
244
+ def worker(index, master)
245
+ title = "puma: cluster worker #{index}: #{master}"
246
+ title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
247
+ $0 = title
248
+
249
+ Signal.trap "SIGINT", "IGNORE"
250
+
251
+ @workers = []
252
+ @master_read.close
253
+ @suicide_pipe.close
254
+
255
+ Thread.new do
256
+ IO.select [@check_pipe]
257
+ log "! Detected parent died, dying"
258
+ exit! 1
259
+ end
260
+
261
+ # If we're not running under a Bundler context, then
262
+ # report the info about the context we will be using
263
+ if !ENV['BUNDLE_GEMFILE']
264
+ if File.exist?("Gemfile")
265
+ log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
266
+ elsif File.exist?("gems.rb")
267
+ log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
268
+ end
269
+ end
270
+
271
+ # Invoke any worker boot hooks so they can get
272
+ # things in shape before booting the app.
273
+ @launcher.config.run_hooks :before_worker_boot, index
274
+
275
+ server = start_server
276
+
277
+ Signal.trap "SIGTERM" do
278
+ server.stop
279
+ end
280
+
281
+ begin
282
+ @worker_write << "b#{Process.pid}\n"
283
+ rescue SystemCallError, IOError
284
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
285
+ STDERR.puts "Master seems to have exited, exiting."
286
+ return
287
+ end
288
+
289
+ Thread.new(@worker_write) do |io|
290
+ base_payload = "p#{Process.pid}"
291
+
292
+ while true
293
+ sleep WORKER_CHECK_INTERVAL
294
+ begin
295
+ b = server.backlog || 0
296
+ r = server.running || 0
297
+ t = server.pool_capacity || 0
298
+ m = server.max_threads || 0
299
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
300
+ io << payload
301
+ rescue IOError
302
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
303
+ break
304
+ end
305
+ end
306
+ end
307
+
308
+ server.run.join
309
+
310
+ # Invoke any worker shutdown hooks so they can prevent the worker
311
+ # exiting until any background operations are completed
312
+ @launcher.config.run_hooks :before_worker_shutdown, index
313
+ ensure
314
+ @worker_write << "t#{Process.pid}\n" rescue nil
315
+ @worker_write.close
316
+ end
317
+
318
+ def restart
319
+ @restart = true
320
+ stop
321
+ end
322
+
323
+ def phased_restart
324
+ return false if @options[:preload_app]
325
+
326
+ @phased_restart = true
327
+ wakeup!
328
+
329
+ true
330
+ end
331
+
332
+ def stop
333
+ @status = :stop
334
+ wakeup!
335
+ end
336
+
337
+ def stop_blocked
338
+ @status = :stop if @status == :run
339
+ wakeup!
340
+ @control.stop(true) if @control
341
+ Process.waitall
342
+ end
343
+
344
+ def halt
345
+ @status = :halt
346
+ wakeup!
347
+ end
348
+
349
+ def reload_worker_directory
350
+ dir = @launcher.restart_dir
351
+ log "+ Changing to #{dir}"
352
+ Dir.chdir dir
353
+ end
354
+
355
+ def stats
356
+ old_worker_count = @workers.count { |w| w.phase != @phase }
357
+ booted_worker_count = @workers.count { |w| w.booted? }
358
+ worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
359
+ %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
360
+ end
361
+
362
+ def preload?
363
+ @options[:preload_app]
364
+ end
365
+
366
+ # We do this in a separate method to keep the lambda scope
367
+ # of the signals handlers as small as possible.
368
+ def setup_signals
369
+ Signal.trap "SIGCHLD" do
370
+ wakeup!
371
+ end
372
+
373
+ Signal.trap "TTIN" do
374
+ @options[:workers] += 1
375
+ wakeup!
376
+ end
377
+
378
+ Signal.trap "TTOU" do
379
+ @options[:workers] -= 1 if @options[:workers] >= 2
380
+ wakeup!
381
+ end
382
+
383
+ master_pid = Process.pid
384
+
385
+ Signal.trap "SIGTERM" do
386
+ # The worker installs their own SIGTERM when booted.
387
+ # Until then, this is run by the worker and the worker
388
+ # should just exit if they get it.
389
+ if Process.pid != master_pid
390
+ log "Early termination of worker"
391
+ exit! 0
392
+ else
393
+ stop_workers
394
+ stop
395
+
396
+ raise SignalException, "SIGTERM"
397
+ end
398
+ end
399
+ end
400
+
401
+ def run
402
+ @status = :run
403
+
404
+ output_header "cluster"
405
+
406
+ log "* Process workers: #{@options[:workers]}"
407
+
408
+ before = Thread.list
409
+
410
+ if preload?
411
+ log "* Preloading application"
412
+ load_and_bind
413
+
414
+ after = Thread.list
415
+
416
+ if after.size > before.size
417
+ threads = (after - before)
418
+ if threads.first.respond_to? :backtrace
419
+ log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot:"
420
+ threads.each do |t|
421
+ log "! #{t.inspect} - #{t.backtrace ? t.backtrace.first : ''}"
422
+ end
423
+ else
424
+ log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot"
425
+ end
426
+ end
427
+ else
428
+ log "* Phased restart available"
429
+
430
+ unless @launcher.config.app_configured?
431
+ error "No application configured, nothing to run"
432
+ exit 1
433
+ end
434
+
435
+ @launcher.binder.parse @options[:binds], self
436
+ end
437
+
438
+ read, @wakeup = Puma::Util.pipe
439
+
440
+ setup_signals
441
+
442
+ # Used by the workers to detect if the master process dies.
443
+ # If select says that @check_pipe is ready, it's because the
444
+ # master has exited and @suicide_pipe has been automatically
445
+ # closed.
446
+ #
447
+ @check_pipe, @suicide_pipe = Puma::Util.pipe
448
+
449
+ if daemon?
450
+ log "* Daemonizing..."
451
+ Process.daemon(true)
452
+ else
453
+ log "Use Ctrl-C to stop"
454
+ end
455
+
456
+ redirect_io
457
+
458
+ Plugins.fire_background
459
+
460
+ @launcher.write_state
461
+
462
+ start_control
463
+
464
+ @master_read, @worker_write = read, @wakeup
465
+
466
+ @launcher.config.run_hooks :before_fork, nil
467
+
468
+ spawn_workers
469
+
470
+ Signal.trap "SIGINT" do
471
+ stop
472
+ end
473
+
474
+ @launcher.events.fire_on_booted!
475
+
476
+ begin
477
+ force_check = false
478
+
479
+ while @status == :run
480
+ begin
481
+ if @phased_restart
482
+ start_phased_restart
483
+ @phased_restart = false
484
+ end
485
+
486
+ check_workers force_check
487
+
488
+ force_check = false
489
+
490
+ res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
491
+
492
+ if res
493
+ req = read.read_nonblock(1)
494
+
495
+ next if !req || req == "!"
496
+
497
+ result = read.gets
498
+ pid = result.to_i
499
+
500
+ if w = @workers.find { |x| x.pid == pid }
501
+ case req
502
+ when "b"
503
+ w.boot!
504
+ log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
505
+ force_check = true
506
+ when "t"
507
+ w.dead!
508
+ force_check = true
509
+ when "p"
510
+ w.ping!(result.sub(/^\d+/,'').chomp)
511
+ end
512
+ else
513
+ log "! Out-of-sync worker list, no #{pid} worker"
514
+ end
515
+ end
516
+
517
+ rescue Interrupt
518
+ @status = :stop
519
+ end
520
+ end
521
+
522
+ stop_workers unless @status == :halt
523
+ ensure
524
+ @check_pipe.close
525
+ @suicide_pipe.close
526
+ read.close
527
+ @wakeup.close
528
+ end
529
+ end
530
+ end
531
+ end