vpndetection_cache (vpndetection v3.4.2)

View Source

The per-client result cache: an LRU with a TTL, owned by one process.

An ETS table dies with the process that created it, so a cache that is meant to outlive the code building the client needs an owner of its own. That owner is also the serialization point the LRU needs, because recency is kept in a second table that has to stay in step with the first.

Reads do NOT go through it. get/2 reads the entry table directly, so the N workers of a batch never queue behind one process to find out they have a hit.

The owner also keeps the board of requests in the air, so concurrent misses for one address share one request. A caller that misses boards the address: the first to do so leads and sends it, every later one waits, and the leader lands the answer, which caches a success and hands the answer to each waiter in one step. A leader that dies before it lands sends its waiters back to ask again rather than failing them with it.

Summary

Types

What a waiter hears: the leader's answer, or retry when the leader died first.

Functions

Take addresses the caller leads off the board without an answer, sending their waiters back to ask again: for a leader that failed before it had one.

Wait for the addresses boarded as wait under Ref. Each answers its leader's answer, or retry when the leader, or the cache itself, went away before it landed one.

Board addresses that missed: each is a fresh answer, lead (send it, then land/2 it) or wait (await/3 it under Ref).

Read an address, if it is held and still fresh.

Land the answers to addresses the caller leads. A success is cached, and every answer reaches the address's waiters; an error is cached for none.

Start a cache holding at most Max addresses for TtlMs each.

Types

answer/0

-type answer() :: {ok, vpndetection_result:result()} | {error, vpndetection_error:error()}.

What a waiter hears: the leader's answer, or retry when the leader died first.

cache/0

-type cache() :: #{pid := pid(), entries := ets:table()}.

outcome/0

-type outcome() :: answer() | retry.

Functions

abandon(_, Ips)

-spec abandon(cache(), [binary()]) -> ok.

Take addresses the caller leads off the board without an answer, sending their waiters back to ask again: for a leader that failed before it had one.

await(Cache, Ref, Ips)

-spec await(cache(), reference(), [binary()]) -> #{binary() => outcome()}.

Wait for the addresses boarded as wait under Ref. Each answers its leader's answer, or retry when the leader, or the cache itself, went away before it landed one.

board(_, Ips, Ref)

-spec board(cache(), [binary()], reference()) ->
               [{binary(), {ok, vpndetection_result:result()} | lead | wait}].

Board addresses that missed: each is a fresh answer, lead (send it, then land/2 it) or wait (await/3 it under Ref).

The cache is read again here, under the owner, because a request that lands caches its answer in the same step it leaves the board: a miss here with no flight means nobody is in the air. A cache whose owner has gone answers lead for every address, and the caller sends them all itself.

get(_, Ip)

-spec get(cache(), binary()) -> {ok, vpndetection_result:result()} | miss.

Read an address, if it is held and still fresh.

A cache whose owner has gone answers miss rather than raising: a miss is always safe, and taking a caller's request path down because an optimization went away would not be.

handle_call(_, From, State)

handle_cast(_, State)

handle_info(Message, State)

init(_)

land(_, Answers)

-spec land(cache(), [{binary(), answer()}]) -> ok.

Land the answers to addresses the caller leads. A success is cached, and every answer reaches the address's waiters; an error is cached for none.

put(_, Ip, Result)

-spec put(cache(), binary(), vpndetection_result:result()) -> ok.

start_link(Max, TtlMs)

-spec start_link(pos_integer(), pos_integer()) -> {ok, cache()}.

Start a cache holding at most Max addresses for TtlMs each.

Linked, so a cache cannot outlive the process that built the client and leak a process plus two ETS tables that nothing will ever collect. Build clients in something long lived, and call vpndetection:close/1 when done.

stop(_)

-spec stop(cache()) -> ok.

terminate(Reason, State)