defmodule Edeliver.Relup.Instructions.SuspendRanchAcceptors do
@moduledoc """
This upgrade instruction suspends the ranch acceptors
to avoid that new connections will be accepted. It will be
inserted right after the "point of no return". When the
upgrade is done, the
`Edeliver.Relup.Instructions.ResumeRanchAcceptors`
instruction reenables the acceptors again. To make sure
that the ranch acceptors are found, use this instruction
after the
`Edeliver.Relup.Instructions.CheckRanchAcceptors`
instruction which will abort the upgrade if the acceptors
cannot be found. Because real suspending of ranch acceptors
is not possible because ranch acceptors do not handle sys
messages, they are actually terminated. Unfortunately the ranch
acceptor supervisor cannot be suspended in addition to avoid
starting new acceptors, because supervisors can't be suspended
because the supervision tree is used to find processes which
uses callback modules. Since no acceptors are started dynamically
this can be ignored. Use the
`Edeliver.Relup.Instructions.ResumeRanchAcceptors`
instruction at the end of your instructions list to re-enable
accepting tcp connection when the upgrade is done.
"""
use Edeliver.Relup.RunnableInstruction
alias Edeliver.Relup.Instructions.CheckRanchAcceptors
@doc """
Appends this instruction to the instructions after the
"point of no return" but before any instruction which
loads or unloads new code, (re-)starts or stops
any running processes, or (re-)starts or stops any
application or the emulator.
"""
def insert_where, do: &append_after_point_of_no_return/2
@doc """
Returns name of the application. This name is taken as argument
for the `run/1` function and is required to access the acceptor processes
through the supervision tree
"""
def arguments(_instructions = %Instructions{}, _config = %{name: name}) when is_atom(name) do
name
end
def arguments(_instructions = %Instructions{}, _config = %{name: name}) when is_binary(name) do
name |> String.to_atom
end
@doc """
This module requires the `Edeliver.Relup.Instructions.CheckRanchAcceptors` module
which must be loaded before this instruction for upgrades and unload after this
instruction for downgrades.
"""
@spec dependencies() :: [Edeliver.Relup.Instructions.CheckRanchAcceptors]
def dependencies do
[Edeliver.Relup.Instructions.CheckRanchAcceptors]
end
@doc """
Suspends all ranch acceptors to avoid handling new requests / connections
during the upgrade. Because suspending of ranch acceptors is not possible
they are terminated. In addition the ranch acceptor supervisor is suspended
to avoid starting new acceptors.
"""
@spec run(otp_application_name::atom) :: :ok
def run(otp_application_name) do
info "Suspending ranch socket acceptors..."
ranch_listener_sup = CheckRanchAcceptors.ranch_listener_sup(otp_application_name)
assume true = is_pid(ranch_listener_sup), "Failed to suspend ranch socket acceptors. Ranch listener supervisor not found."
ranch_acceptors_sup = CheckRanchAcceptors.ranch_acceptors_sup(ranch_listener_sup)
assume true = is_pid(ranch_acceptors_sup), "Failed to suspend ranch socket acceptors. Ranch acceptors supervisor not found."
assume [_|_] = acceptors = CheckRanchAcceptors.ranch_acceptors(ranch_acceptors_sup), "Failed to suspend ranch socket acceptors. No acceptor processes found."
acceptors_count = Enum.count(acceptors)
info "Stopping #{inspect acceptors_count} ranch socket acceptors..."
assume true = Enum.all?(acceptors, fn acceptor ->
Supervisor.terminate_child(ranch_acceptors_sup, acceptor) == :ok
end), "Failed to suspend ranch socket acceptors."
info "Suspended #{inspect acceptors_count} ranch acceptors."
end
end