GenServer + monotonic_time TTLs
GenServer is Agent + arbitrary message handling. We need handle_info for active expiry sweeps in the exercises. State grows to %{store: %{}, expires: %{}}.
defp check_expired(state, key) do
case Map.get(state.expires, key) do
nil -> {state, false}
ts ->
if now_ms() >= ts do
{%{state | store: Map.delete(state.store, key), expires: Map.delete(state.expires, key)}, true}
else
{state, false}
end
end
end
defp now_ms, do: System.monotonic_time(:millisecond)check_expired runs inside every read. Returns a new state.
System.monotonic_time is immune to NTP jumps. The number is meaningless on its own (a count of native units since BEAM startup) but the difference between two readings is meaningful. For TTL deadlines, that is exactly what we want.
def handle_call({:ttl, key}, _from, state) do
{state, expired} = check_expired(state, key)
reply =
cond do
expired -> -2
not Map.has_key?(state.store, key) -> -2
not Map.has_key?(state.expires, key) -> -1
true ->
remaining = max(0, Map.fetch!(state.expires, key) - now_ms())
div(remaining, 1000)
end
{:reply, reply, state}
endTTL returns remaining seconds; -1 means no TTL; -2 means missing.
Quiz: Quiz
Loading practice…