puma-simon 3.7.1

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