Packages
mob_dev
0.3.23
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mob_dev/bench/reconnector.ex
defmodule MobDev.Bench.Reconnector do
@moduledoc """
Auto-reconnect logic for the bench's BEAM dist connection.
When a probe says the dist connection has dropped (`:alive_epmd_only` or
`:alive_dist_only` after an RPC timeout), we want to attempt to reconnect
automatically rather than leaving the bench in a stuck state for the rest
of the run.
This module is *pure logic* — no GenServer, no timers. The bench polling
loop calls `tick/2` once per cycle, which decides whether to attempt a
reconnect based on the current reachability and elapsed time since the
last attempt. This keeps the reconnect logic testable and lets the
caller control the cadence.
## Backoff schedule (defaults)
1st attempt: immediate
2nd attempt: 2 s after 1st
3rd attempt: 4 s after 2nd
4th attempt: 8 s after 3rd
Subsequent: 30 s cap
Reset to immediate on a successful reconnect.
"""
alias MobDev.Bench.Probe
defstruct [
:node,
:cookie,
:attempts,
:last_attempt_ms,
:total_reconnects,
:max_backoff_ms
]
@type t :: %__MODULE__{
node: atom(),
cookie: atom(),
attempts: non_neg_integer(),
last_attempt_ms: integer() | nil,
total_reconnects: non_neg_integer(),
max_backoff_ms: pos_integer()
}
@default_backoffs [0, 2_000, 4_000, 8_000, 16_000]
@default_max_backoff_ms 30_000
@doc """
Initialise a reconnector for `node` with cookie. Optional `:max_backoff_ms`.
"""
@spec new(atom(), atom(), keyword()) :: t()
def new(node, cookie, opts \\ []) do
%__MODULE__{
node: node,
cookie: cookie,
attempts: 0,
last_attempt_ms: nil,
total_reconnects: 0,
max_backoff_ms: Keyword.get(opts, :max_backoff_ms, @default_max_backoff_ms)
}
end
@doc """
Decide whether the caller should attempt a reconnect right now, given the
current probe state and current time. Returns `{action, updated_reconnector}`.
Actions:
- `:no_action` — connection is healthy or it's not time yet
- `:attempt` — caller should try `Node.connect(reconnector.node)` now
After a successful reconnect, call `record_success/1` to reset the backoff.
"""
@spec tick(t(), Probe.t() | atom(), integer()) :: {:no_action | :attempt, t()}
def tick(reconnector, %Probe{} = probe, now_ms) do
tick(reconnector, probe.reachability, now_ms)
end
def tick(%__MODULE__{} = r, reachability, now_ms) when is_atom(reachability) do
cond do
# Connection is healthy — reset attempts.
reachability == :alive_rpc ->
{:no_action, %{r | attempts: 0}}
# Not enough time has passed since the last attempt.
r.last_attempt_ms != nil and now_ms - r.last_attempt_ms < current_backoff_ms(r) ->
{:no_action, r}
# Time to try.
true ->
{:attempt, %{r | attempts: r.attempts + 1, last_attempt_ms: now_ms}}
end
end
@doc """
Record that the most recent reconnect attempt succeeded — resets the
backoff counter and bumps the total_reconnects counter.
"""
@spec record_success(t()) :: t()
def record_success(%__MODULE__{} = r) do
%{r | attempts: 0, total_reconnects: r.total_reconnects + 1}
end
@doc """
Returns the backoff (in ms) that applies to the *next* attempt.
iex> r = MobDev.Bench.Reconnector.new(:node@host, :secret)
iex> MobDev.Bench.Reconnector.current_backoff_ms(r)
0
iex> r = %{MobDev.Bench.Reconnector.new(:node@host, :secret) | attempts: 3}
iex> MobDev.Bench.Reconnector.current_backoff_ms(r)
8000
"""
@spec current_backoff_ms(t()) :: non_neg_integer()
def current_backoff_ms(%__MODULE__{attempts: attempts, max_backoff_ms: max}) do
case Enum.at(@default_backoffs, attempts) do
nil -> max
ms -> min(ms, max)
end
end
end