1.4 KiB · text History 4b6fe3b
defmodule OhMyExOhIrcd.IRC.HLC do
  @moduledoc """
  Tiny Hybrid Logical Clock for tagging cluster-wide events.

  A timestamp is `{wall_ms, logical, node}`. Sorting these gives a
  total order that:

    * tracks wall-clock time when clocks agree;
    * stays monotonic across a node even if the wall clock jumps backward;
    * breaks ties by `node` so two events with identical clocks still
      sort consistently across the cluster.

  Only useful for ordering CRDT log entries on heal — not a substitute
  for proper consensus.
  """

  use Agent

  @type t :: {non_neg_integer(), non_neg_integer(), node()}

  def start_link(_),
    do: Agent.start_link(fn -> {0, 0} end, name: __MODULE__)

  @spec now() :: t()
  def now do
    wall = System.system_time(:millisecond)

    Agent.get_and_update(__MODULE__, fn {last_wall, last_logical} ->
      if wall > last_wall do
        {{wall, 0, node()}, {wall, 0}}
      else
        {{last_wall, last_logical + 1, node()}, {last_wall, last_logical + 1}}
      end
    end)
  end

  @doc "Observe an external timestamp; advance our clock so future stamps stay > it."
  @spec observe(t()) :: :ok
  def observe({wall, logical, _node}) do
    Agent.update(__MODULE__, fn {our_wall, our_logical} ->
      cond do
        wall > our_wall -> {wall, logical}
        wall == our_wall and logical > our_logical -> {wall, logical}
        true -> {our_wall, our_logical}
      end
    end)
  end
end