lib/session/server_session.ex

defmodule Mongo.Session.ServerSession do
  @moduledoc """
  This module represents the server-side session. There are three fields:

    * `last_use` - The timestamp for the last use of this server session
    * `txn_num` - The current transaction number
    * `session_id` - The session id of this server session

  When a transaction is active, all operations in that transaction
  use the same transaction number.

  Transaction number is also used outside of transactions for
  retryable writes. In this case, each write operation has its own
  transaction number, but retries of a write operation use the same
  transaction number as the first write (which is how the server
  knows that subsequent writes are retries and should be ignored if
  the first write succeeded on the server but was not read by the
  client, for example).
  """

  alias Mongo.Session.ServerSession

  @type t :: %__MODULE__{
          last_use: integer,
          txn_num: non_neg_integer,
          session_id: BSON.Binary.t()
        }

  defstruct last_use: 0, txn_num: 0, session_id: nil

  @doc """
  Create a new server session.
  """
  @spec new() :: ServerSession.t()
  def new() do
    %ServerSession{session_id: Mongo.uuid(), last_use: System.monotonic_time(:second)}
  end

  @doc """
  Update the last_use attribute of the server session to now.
  """
  @spec set_last_use(ServerSession.t()) :: ServerSession.t()
  def set_last_use(%ServerSession{} = session) do
    %ServerSession{session | last_use: System.monotonic_time(:second)}
  end

  @doc """
  Increment the current transaction number and return the new value.
  """
  @spec next_txn_num(ServerSession.t()) :: ServerSession.t()
  def next_txn_num(%ServerSession{:txn_num => txn_num} = session) do
    %ServerSession{session | txn_num: txn_num + 1}
  end

  @doc """
    Return true, if the server session will time out. In this case the session
    can be removed from the queue.
  """
  @spec about_to_expire?(ServerSession.t(), integer) :: boolean
  @compile {:inline, about_to_expire?: 2}
  def about_to_expire?(%ServerSession{:last_use => last_use}, logical_session_timeout) do
    System.monotonic_time(:second) - last_use >= logical_session_timeout
  end

  defimpl Inspect, for: ServerSession do
    def inspect(%ServerSession{last_use: last_use, txn_num: txn, session_id: session_id}, _opts) do
      "#ServerSession(" <> inspect(DateTime.from_unix(last_use)) <> ", " <> to_string(txn) <> ", session_id: " <> inspect(session_id) <> ")"
    end
  end
end