MPP.Client.Transport.WebSocket.Retry (mpp v0.14.0)

Copy Markdown View Source

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) and 1013 (Try Again Later) are the only non-fatal close codes, and only when no credential is awaiting a receipt.

API Functions

FunctionArityDescriptionParam Kinds
handshake_timeout_ms1Per-phase handshake / pay timeout in milliseconds.state: value
delay_ms1Capped exponential reconnect delay for the current attempt count.state: value
reconnect?1Return true if a socket-level reconnect is still allowed.state: value
should_pay?1Return true if a payment may be created for the current socket.state: value
transition2Apply a connection or payment event and return the next disposition plus updated state.state: value, event: value
new1Build 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

disposition()

@type disposition() :: :continue | {:retry, non_neg_integer()} | {:fatal, atom()}

event()

@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}

t()

@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

delay_ms(retry)

@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"}
}

handshake_timeout_ms(retry)

@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"}
}

new(opts \\ [])

@spec new(keyword()) :: t()

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"
  }
}

reconnect?(state)

@spec reconnect?(t()) :: boolean()

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"
  }
}

should_pay?(state)

@spec should_pay?(t()) :: boolean()

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"
  }
}

transient_close?(code)

@spec transient_close?(non_neg_integer() | nil) :: boolean()

Return true for close codes that may reconnect (1012 Restart, 1013 Again).

transition(state, arg2)

@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}`"}
}