Packages
ferricstore
0.11.5
0.11.14
0.11.12
0.11.11
0.11.10
0.11.9
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.0
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.0
0.1.0
FerricFlow durable workflows and queues with native-protocol storage, Raft durability, and Bitcask persistence.
Current section
Files
Jump to
Current section
Files
lib/ferricstore/command_time.ex
defmodule Ferricstore.CommandTime do
@moduledoc """
Shared clock for command code that can run both outside and inside Raft apply.
Outside Raft, command code uses the cluster-adjusted HLC time. Inside Raft
apply, relative expiry and other time-derived command results must use the
timestamp stored in the log entry so every replica computes the same state.
"""
alias Ferricstore.HLC
@apply_now_key :ferricstore_raft_apply_now_ms
@apply_wall_key :ferricstore_raft_apply_wall_ms
@apply_now_unset :__ferricstore_raft_apply_now_unset__
@doc """
Returns the command time in milliseconds.
If a Raft state machine apply scope installed a stamped log-entry time, that
value is returned. Otherwise this falls back to the local cluster-adjusted
HLC clock.
"""
@spec now_ms() :: non_neg_integer()
def now_ms do
Process.get(@apply_now_key) || HLC.now_ms()
end
@doc false
@spec apply_now_ms() :: {:ok, non_neg_integer()} | :none
def apply_now_ms do
case Process.get(@apply_now_key, @apply_now_unset) do
@apply_now_unset -> :none
now_ms -> {:ok, now_ms}
end
end
@doc false
@spec apply_wall_ms() :: {:ok, non_neg_integer()} | :none
def apply_wall_ms do
case Process.get(@apply_wall_key, @apply_now_unset) do
@apply_now_unset -> :none
wall_ms -> {:ok, wall_ms}
end
end
@doc """
Runs `fun` with a stamped Raft apply time visible to command modules.
"""
@spec with_now_ms(non_neg_integer(), (-> result)) :: result when result: term()
def with_now_ms(now_ms, fun)
when is_integer(now_ms) and now_ms >= 0 and is_function(fun, 0) do
previous_now = Process.get(@apply_now_key, @apply_now_unset)
previous_wall = Process.get(@apply_wall_key, @apply_now_unset)
Process.put(@apply_now_key, now_ms)
Process.delete(@apply_wall_key)
try do
fun.()
after
restore_process_value(@apply_now_key, previous_now)
restore_process_value(@apply_wall_key, previous_wall)
end
end
@doc false
@spec with_expiry_context(non_neg_integer(), non_neg_integer(), (-> result)) :: result
when result: term()
def with_expiry_context(now_ms, wall_ms, fun)
when is_integer(now_ms) and now_ms >= 0 and is_integer(wall_ms) and wall_ms >= 0 and
is_function(fun, 0) do
previous_now = Process.get(@apply_now_key, @apply_now_unset)
previous_wall = Process.get(@apply_wall_key, @apply_now_unset)
Process.put(@apply_now_key, now_ms)
Process.put(@apply_wall_key, wall_ms)
try do
fun.()
after
restore_process_value(@apply_now_key, previous_now)
restore_process_value(@apply_wall_key, previous_wall)
end
end
defp restore_process_value(key, @apply_now_unset), do: Process.delete(key)
defp restore_process_value(key, value), do: Process.put(key, value)
end