piesync-puma 3.12.6

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