FixAlchemy.SessionStore behaviour (FIXAlchemy v0.2.2)

View Source

Storage for a session's sequence numbers, keyed by session identity rather than by process.

A session that must resume its numbering after a reconnect, a restart, or a crash loads it from here at logon and reports every number it uses. The identity is the FIX session key — BeginString, SenderCompID, TargetCompID — so the same session finds its own numbers whichever process is running it.

Sessions have no store unless one is configured, in which case numbering starts at 1 on every connection and nothing is written.

FixAlchemy.Server.start_link(
  session_store: FixAlchemy.SessionStore.Ets,
  session_store_opts: [table: :my_fix_sequences],
  ...
)

Rules an implementation must follow

  • record_outbound/2 and record_inbound/2 are called once for every message the session sends and accepts. What is written, and when, is the implementation's choice; the engine does not wait on either call.
  • load/1 may return a number higher than any the session actually used. It must never return a lower one — the counterparty rejects a MsgSeqNum below what it expects, and the session ends.
  • A gap between what load/1 returns and the last number used is answered by the session with SequenceReset-GapFill, and needs nothing from the implementation.
  • close/1 is the only point at which anything held back is guaranteed to be flushed. It is not called when the node dies.

Implementing

defmodule MyApp.CubSessionStore do
  @behaviour FixAlchemy.SessionStore

  @impl true
  def open(key, opts), do: {:ok, {Keyword.fetch!(opts, :db), key}}

  @impl true
  def load({db, key}) do
    CubDB.get(db, key, %{next_outbound: 1, expected_inbound: 1})
  end

  @impl true
  def record_outbound({db, key} = handle, seq) do
    CubDB.put(db, key, %{load(handle) | next_outbound: seq + 1})
  end

  # record_inbound/2, reset/1 and close/1 likewise
end

Summary

Types

Whatever open/2 returns; passed back to every other callback.

BeginString, SenderCompID, TargetCompID.

Callbacks

Flush anything held and release the handle. Called when the session ends.

The numbers this session resumes from.

Open storage for one session. Called once, before the session sends anything.

Record that seq has been accepted from the counterparty.

Record that seq has been assigned to an outbound message.

Return both directions to 1, as a Logon with ResetSeqNumFlag requires.

Functions

The numbers a session starts from when it has never been stored.

Types

handle()

@type handle() :: term()

Whatever open/2 returns; passed back to every other callback.

key()

@type key() :: {binary(), binary(), binary()}

BeginString, SenderCompID, TargetCompID.

sequences()

@type sequences() :: %{next_outbound: pos_integer(), expected_inbound: pos_integer()}

Callbacks

close(handle)

@callback close(handle()) :: :ok

Flush anything held and release the handle. Called when the session ends.

load(handle)

@callback load(handle()) :: sequences()

The numbers this session resumes from.

Returns %{next_outbound: 1, expected_inbound: 1} for a session never seen before.

open(key, opts)

@callback open(key(), opts :: keyword()) :: {:ok, handle()} | {:error, term()}

Open storage for one session. Called once, before the session sends anything.

record_inbound(handle, seq)

@callback record_inbound(handle(), seq :: pos_integer()) :: :ok

Record that seq has been accepted from the counterparty.

record_outbound(handle, seq)

@callback record_outbound(handle(), seq :: pos_integer()) :: :ok

Record that seq has been assigned to an outbound message.

reset(handle)

@callback reset(handle()) :: :ok

Return both directions to 1, as a Logon with ResetSeqNumFlag requires.

Functions

initial()

@spec initial() :: sequences()

The numbers a session starts from when it has never been stored.