Graceful shutdown: Signal.trap and ConditionVariable

Before the code: what graceful shutdown actually means

Picture a restaurant at closing time. The good waiter does not yell stop and dump plates on the floor. They stop seating new guests, let the people already eating finish, then turn the lights off. That is graceful shutdown for a server. When the operating system sends a stop signal, you want the server to stop accepting new connections, let the writes already in flight finish, then quit. In Ruby you wire this with Signal.trap, a ConditionVariable that broadcasts the shutdown, and an in-flight counter that knows when every handler has finished.

Stop accepting, drain, exit

A clean shutdown has three phases: stop accepting new work, let in-flight work finish, then exit the process.

Quiz: Quiz

Loading practice…

Why this is Ruby's step 9: Threads already give us per-connection isolation. The remaining production gap is shutdown - drain in-flight handlers, snapshot state, exit cleanly.

09-graceful-shutdown/server.rb
ruby
%w[INT TERM].each do |sig|
  Signal.trap(sig) do
    $shutdown_mu.synchronize { $shutdown = true; $shutdown_cv.broadcast }
    begin; server.close; rescue; end
  end
end

$shutdown_mu.synchronize { $shutdown_cv.wait($shutdown_mu) until $shutdown }
puts "  [shutdown] signal received, draining..."

Trap signals + close the listener + broadcast on the ConditionVariable.

09-graceful-shutdown/server.rb
ruby
deadline = Time.now + 10
$in_flight_mu.synchronize do
  while $in_flight > 0 && Time.now < deadline
    remaining = deadline - Time.now
    $in_flight_cv.wait($in_flight_mu, remaining)
  end
end

# then snapshot RDB
n = snapshot_atomic
puts "  [shutdown] snapshot wrote #{n} keys"

In-flight counter via ConditionVariable. Wait until 0 or deadline.

ConditionVariable is event-driven. Threads waiting on cv.wait are woken instantly by cv.broadcast. Polling would either delay shutdown (long sleep) or burn CPU (short sleep). The CV is the cheap, correct primitive.

Quiz: Quiz

Loading practice…