defmodule Edeliver.Relup.PhoenixModification do
@moduledoc """
This module provides default modifications of the relup instructions for phoenix apps.
It performs the following steps:
- __Checking whether the upgrade is possible__
- by checking whether there are processes running old code (from a previous upgrade)
- by checking whether the ranch acceptor processes can be found
- by checking whether ranch connection processes can be found
or canceling the upgrade if one of the checks fails.
- __Preparing the upgrade__
- by suspending all ranch acceptors to avoid accepting new connections / requests during the hot code upgrade
- by suspending phoenix channels
- by waiting a short time until running requests are finished and notifying the remaining that an upgrades starts (this allows to rerun failed requests later)
- __Executing the upgrade__
- by using `:soft_purge` instead of `:brutal_purge` when loading new code
- by suspending all processes running code of changed modules
- by calling `code_change` at this processes
- by resuming that processes
- __Finishing the upgrade__
- by rerunning requests that failed during the upgrade
- by resuming all phoenix channels
- by resuming all ranch acceptors to continue accepting new connections
"""
use Edeliver.Relup.Modification
def modify_relup(instructions = %Instructions{}, config = %{}) do
instructions
# check whether upgrade is possible
|> Edeliver.Relup.Instructions.StartSection.modify_relup(config, :check)
|> Edeliver.Relup.Instructions.CheckProcessesRunningOldCode.modify_relup(config)
|> Edeliver.Relup.Instructions.CheckRanchAcceptors.modify_relup(config)
|> Edeliver.Relup.Instructions.CheckRanchConnections.modify_relup(config)
# prepare the upgrade
|> Edeliver.Relup.Instructions.StartSection.modify_relup(config, :suspend)
|> Edeliver.Relup.Instructions.SuspendRanchAcceptors.modify_relup(config)
|> Edeliver.Relup.Instructions.SuspendChannels.modify_relup(config)
|> Edeliver.Relup.Instructions.FinishRunningRequests.modify_relup(config)
# run the upgrade
|> Edeliver.Relup.Instructions.StartSection.modify_relup(config, :upgrade)
|> Edeliver.Relup.Instructions.SoftPurge.modify_relup(config)
|> Edeliver.Relup.Instructions.SuspendAppProcesses.modify_relup(config)
|> Edeliver.Relup.Instructions.ReloadModules.modify_relup(config)
|> Edeliver.Relup.Instructions.CodeChangeOnAppProcesses.modify_relup(config)
|> Edeliver.Relup.Instructions.ResumeAppProcesses.modify_relup(config)
# resume
|> Edeliver.Relup.Instructions.StartSection.modify_relup(config, :resume)
|> Edeliver.Relup.Instructions.RerunFailedRequests.modify_relup(config)
|> Edeliver.Relup.Instructions.ResumeChannels.modify_relup(config)
|> Edeliver.Relup.Instructions.ResumeRanchAcceptors.modify_relup(config)
|> Edeliver.Relup.Instructions.StartSection.modify_relup(config, :finished)
end
@doc """
Returns true if the current project is a phoenix project
"""
@spec usable?(Edeliver.Relup.Config.t) :: boolean
def usable?(_config = %{}) do
deps = Mix.Project.config[:deps]
List.keymember?(deps, :phoenix, 0) && List.keymember?(deps, :phoenix_html, 0)
end
@doc """
Returns the priority `Edeliver.Relup.Modification.priority_default/0` `+1`.
Unless the module is set by the `RELUP_MODIFICATION_MODULE` env or
the `--relup-mod=` command line option the module with the highest priority
is used (which is also usable).
"""
def priority, do: priority_default() + 1
end