puma-simon 3.7.1

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