Reconnect and payment-retry policy for MPP-over-WebSocket.
Matches alloy-transport-mpp MppWsConnect plus alloy-pubsub's reconnect
loop (refs/mpp-rs/crates/alloy-transport-mpp/src/ws.rs,
alloy crates/pubsub/src/service.rs):
- Socket-level failures are transient and reconnect with capped exponential backoff (base 3s, cap 30s, max 10 attempts).
- Deterministic MPP failures are fatal and latch — later
connect()equivalents must not retry and must not pay again. - A drop after a credential was sent and before the receipt is fatal, so a rejected or half-open socket cannot amplify payment retries.
- A second challenge while a payment is in flight is fatal.
- Close codes
1012(Restart) and1013(Try Again Later) are the only non-fatal close codes, and only when no credential is awaiting a receipt.
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
handshake_timeout_ms | 1 | Per-phase handshake / pay timeout in milliseconds. | state: value |
delay_ms | 1 | Capped exponential reconnect delay for the current attempt count. | state: value |
reconnect? | 1 | Return true if a socket-level reconnect is still allowed. | state: value |
should_pay? | 1 | Return true if a payment may be created for the current socket. | state: value |
transition | 2 | Apply a connection or payment event and return the next disposition plus updated state. | state: value, event: value |
new | 1 | Build a retry state with mpp-rs / alloy-transport-mpp defaults. | opts: value |
Summary
Functions
Capped exponential reconnect delay for the current attempt count.
Per-phase handshake / pay timeout in milliseconds.
Build a retry state with mpp-rs / alloy-transport-mpp defaults.
Return true if a socket-level reconnect is still allowed.
Return true if a payment may be created for the current socket.
Return true for close codes that may reconnect (1012 Restart, 1013 Again).
Apply a connection or payment event and return the next disposition plus updated state.
Types
@type disposition() :: :continue | {:retry, non_neg_integer()} | {:fatal, atom()}
@type event() :: :pay_started | :credential_sent | :receipt | :challenge | :handshake_timeout | :provider_error | :server_error | :malformed_frame | :binary_frame | :connection_dropped | :server_gone | {:socket_error, term()} | {:close, non_neg_integer() | nil}
@type t() :: %MPP.Client.Transport.WebSocket.Retry{ attempts: non_neg_integer(), awaiting_receipt?: boolean(), fatal?: boolean(), fatal_reason: atom() | nil, handshake_timeout_ms: pos_integer(), max_retries: pos_integer(), pay_count: non_neg_integer(), payment_in_flight?: boolean(), retry_interval_ms: pos_integer() }
Functions
@spec delay_ms(t()) :: non_neg_integer()
Capped exponential reconnect delay for the current attempt count.
Parameters
state- Retry state after a transient failure has incremented attempts (value)
Returns
Delay in milliseconds (integer)
# descripex:contract
%{
params: %{
state: %{
description: "Retry state after a transient failure has incremented attempts",
kind: :value
}
},
returns: %{type: :integer, description: "Delay in milliseconds"}
}
@spec handshake_timeout_ms(t()) :: pos_integer()
Per-phase handshake / pay timeout in milliseconds.
Parameters
state- Retry state (value)
Returns
Timeout in milliseconds (integer)
# descripex:contract
%{
params: %{state: %{description: "Retry state", kind: :value}},
returns: %{type: :integer, description: "Timeout in milliseconds"}
}
Build a retry state with mpp-rs / alloy-transport-mpp defaults.
Parameters
opts- Optional :max_retries, :retry_interval_ms, :handshake_timeout_ms (value)
Returns
MPP.Client.Transport.WebSocket.Retry state (struct)
# descripex:contract
%{
params: %{
opts: %{
description: "Optional :max_retries, :retry_interval_ms, :handshake_timeout_ms",
kind: :value
}
},
returns: %{
type: :struct,
description: "`MPP.Client.Transport.WebSocket.Retry` state"
}
}
Return true if a socket-level reconnect is still allowed.
Parameters
state- Retry state (value)
Returns
false when fatal or the retry budget is exhausted (boolean)
# descripex:contract
%{
params: %{state: %{description: "Retry state", kind: :value}},
returns: %{
type: :boolean,
description: "false when fatal or the retry budget is exhausted"
}
}
Return true if a payment may be created for the current socket.
Parameters
state- Retry state (value)
Returns
false when fatal, a pay is in flight, or a receipt is outstanding (boolean)
# descripex:contract
%{
params: %{state: %{description: "Retry state", kind: :value}},
returns: %{
type: :boolean,
description: "false when fatal, a pay is in flight, or a receipt is outstanding"
}
}
@spec transient_close?(non_neg_integer() | nil) :: boolean()
Return true for close codes that may reconnect (1012 Restart, 1013 Again).
@spec transition(t(), event()) :: {disposition(), t()}
Apply a connection or payment event and return the next disposition plus updated state.
Parameters
state- Retry state (value)event- Lifecycle event (value)
Returns
{disposition, state} (tuple)
# descripex:contract
%{
params: %{
state: %{description: "Retry state", kind: :value},
event: %{description: "Lifecycle event", kind: :value}
},
returns: %{type: :tuple, description: "`{disposition, state}`"}
}