Commit 9a4ca79b authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'improve-graceful-shutdown-hooks' into 'master'

Improve support for graceful shutdown on Puma/Unicorn

See merge request gitlab-org/gitlab!19223
parents 4a65acd7 a85c10fe
...@@ -70,8 +70,8 @@ if defined?(::Unicorn) || defined?(::Puma) ...@@ -70,8 +70,8 @@ if defined?(::Unicorn) || defined?(::Puma)
Gitlab::Metrics::Exporter::WebExporter.instance.start Gitlab::Metrics::Exporter::WebExporter.instance.start
end end
Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do Gitlab::Cluster::LifecycleEvents.on_before_graceful_shutdown do
# We need to ensure that before we re-exec server # We need to ensure that before we re-exec or shutdown server
# we do stop the exporter # we do stop the exporter
Gitlab::Metrics::Exporter::WebExporter.instance.stop Gitlab::Metrics::Exporter::WebExporter.instance.stop
end end
......
# frozen_string_literal: true
if defined?(::Puma) && ::Puma.cli_config.options[:workers].to_i.zero?
raise 'Puma is only supported in Cluster-mode: workers > 0'
end
...@@ -33,7 +33,7 @@ module Gitlab ...@@ -33,7 +33,7 @@ module Gitlab
# #
# Sidekiq/Puma Single: This is called immediately. # Sidekiq/Puma Single: This is called immediately.
# #
# - on_before_phased_restart: # - on_before_graceful_shutdown:
# #
# Unicorn/Puma Cluster: This will be called before a graceful # Unicorn/Puma Cluster: This will be called before a graceful
# shutdown of workers starts happening. # shutdown of workers starts happening.
...@@ -75,9 +75,9 @@ module Gitlab ...@@ -75,9 +75,9 @@ module Gitlab
end end
# Read the config/initializers/cluster_events_before_phased_restart.rb # Read the config/initializers/cluster_events_before_phased_restart.rb
def on_before_phased_restart(&block) def on_before_graceful_shutdown(&block)
# Defer block execution # Defer block execution
(@master_phased_restart ||= []) << block (@master_graceful_shutdown ||= []) << block
end end
def on_before_master_restart(&block) def on_before_master_restart(&block)
...@@ -108,8 +108,8 @@ module Gitlab ...@@ -108,8 +108,8 @@ module Gitlab
end end
end end
def do_before_phased_restart def do_before_graceful_shutdown
@master_phased_restart&.each do |block| @master_graceful_shutdown&.each do |block|
block.call block.call
end end
end end
......
...@@ -8,8 +8,12 @@ module Gitlab ...@@ -8,8 +8,12 @@ module Gitlab
raise 'missing method Puma::Cluster#stop_workers' unless base.method_defined?(:stop_workers) raise 'missing method Puma::Cluster#stop_workers' unless base.method_defined?(:stop_workers)
end end
# This looks at internal status of `Puma::Cluster`
# https://github.com/puma/puma/blob/v3.12.1/lib/puma/cluster.rb#L333
def stop_workers def stop_workers
Gitlab::Cluster::LifecycleEvents.do_before_phased_restart if @status == :stop # rubocop:disable Gitlab/ModuleWithInstanceVariables
Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
end
super super
end end
......
...@@ -5,11 +5,26 @@ module Gitlab ...@@ -5,11 +5,26 @@ module Gitlab
module Mixins module Mixins
module UnicornHttpServer module UnicornHttpServer
def self.prepended(base) def self.prepended(base)
raise 'missing method Unicorn::HttpServer#reexec' unless base.method_defined?(:reexec) unless base.method_defined?(:reexec) && base.method_defined?(:stop)
raise 'missing method Unicorn::HttpServer#reexec or Unicorn::HttpServer#stop'
end
end end
def reexec def reexec
Gitlab::Cluster::LifecycleEvents.do_before_phased_restart Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
super
end
# The stop on non-graceful shutdown is executed twice:
# `#stop(false)` and `#stop`.
#
# The first stop will wipe-out all workers, so we need to check
# the flag and a list of workers
def stop(graceful = true)
if graceful && @workers.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
end
super super
end end
......
...@@ -8,15 +8,28 @@ describe Gitlab::Cluster::Mixins::PumaCluster do ...@@ -8,15 +8,28 @@ describe Gitlab::Cluster::Mixins::PumaCluster do
PUMA_STARTUP_TIMEOUT = 30 PUMA_STARTUP_TIMEOUT = 30
context 'when running Puma in Cluster-mode' do context 'when running Puma in Cluster-mode' do
%i[USR1 USR2 INT HUP].each do |signal| using RSpec::Parameterized::TableSyntax
it "for #{signal} does execute phased restart block" do
where(:signal, :exitstatus, :termsig) do
# executes phased restart block
:USR1 | 140 | nil
:USR2 | 140 | nil
:INT | 140 | nil
:HUP | 140 | nil
# does not execute phased restart block
:TERM | nil | 15
end
with_them do
it 'properly handles process lifecycle' do
with_puma(workers: 1) do |pid| with_puma(workers: 1) do |pid|
Process.kill(signal, pid) Process.kill(signal, pid)
child_pid, child_status = Process.wait2(pid) child_pid, child_status = Process.wait2(pid)
expect(child_pid).to eq(pid) expect(child_pid).to eq(pid)
expect(child_status).to be_exited expect(child_status.exitstatus).to eq(exitstatus)
expect(child_status.exitstatus).to eq(140) expect(child_status.termsig).to eq(termsig)
end end
end end
end end
...@@ -62,8 +75,12 @@ describe Gitlab::Cluster::Mixins::PumaCluster do ...@@ -62,8 +75,12 @@ describe Gitlab::Cluster::Mixins::PumaCluster do
Puma::Cluster.prepend(#{described_class}) Puma::Cluster.prepend(#{described_class})
Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do mutex = Mutex.new
exit(140)
Gitlab::Cluster::LifecycleEvents.on_before_graceful_shutdown do
mutex.synchronize do
exit(140)
end
end end
# redirect stderr to stdout # redirect stderr to stdout
......
...@@ -5,31 +5,30 @@ require 'spec_helper' ...@@ -5,31 +5,30 @@ require 'spec_helper'
# For easier debugging set `UNICORN_DEBUG=1` # For easier debugging set `UNICORN_DEBUG=1`
describe Gitlab::Cluster::Mixins::UnicornHttpServer do describe Gitlab::Cluster::Mixins::UnicornHttpServer do
UNICORN_STARTUP_TIMEOUT = 10 UNICORN_STARTUP_TIMEOUT = 30
context 'when running Unicorn' do context 'when running Unicorn' do
%i[USR2].each do |signal| using RSpec::Parameterized::TableSyntax
it "for #{signal} does execute phased restart block" do
with_unicorn(workers: 1) do |pid|
Process.kill(signal, pid)
child_pid, child_status = Process.wait2(pid) where(:signal, :exitstatus, :termsig) do
expect(child_pid).to eq(pid) # executes phased restart block
expect(child_status).to be_exited :USR2 | 140 | nil
expect(child_status.exitstatus).to eq(140) :QUIT | 140 | nil
end
end # does not execute phased restart block
:INT | 0 | nil
:TERM | 0 | nil
end end
%i[QUIT TERM INT].each do |signal| with_them do
it "for #{signal} does not execute phased restart block" do it 'properly handles process lifecycle' do
with_unicorn(workers: 1) do |pid| with_unicorn(workers: 1) do |pid|
Process.kill(signal, pid) Process.kill(signal, pid)
child_pid, child_status = Process.wait2(pid) child_pid, child_status = Process.wait2(pid)
expect(child_pid).to eq(pid) expect(child_pid).to eq(pid)
expect(child_status).to be_exited expect(child_status.exitstatus).to eq(exitstatus)
expect(child_status.exitstatus).to eq(0) expect(child_status.termsig).to eq(termsig)
end end
end end
end end
...@@ -74,8 +73,12 @@ describe Gitlab::Cluster::Mixins::UnicornHttpServer do ...@@ -74,8 +73,12 @@ describe Gitlab::Cluster::Mixins::UnicornHttpServer do
Unicorn::HttpServer.prepend(#{described_class}) Unicorn::HttpServer.prepend(#{described_class})
Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do mutex = Mutex.new
exit(140)
Gitlab::Cluster::LifecycleEvents.on_before_graceful_shutdown do
mutex.synchronize do
exit(140)
end
end end
# redirect stderr to stdout # redirect stderr to stdout
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment