Current section

Files

Jump to
erldns src erldns_packet_cache.erl
Raw

src/erldns_packet_cache.erl

%% Copyright (c) 2012-2020, DNSimple Corporation
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(erldns_packet_cache).
-moduledoc """
A basic packet cache that is used to avoid multiple lookups for the
same question received within the cache TTL.
The cache is swept for old cache data at regular intervals.
## Configuration
```erlang
{erldns, [
{packet_cache, #{
enabled => boolean(), %% defaults to true
ttl => non_neg_integer(), %% Seconds, defaults to 30
}}
]}
```
## Telemetry events
See `m:segmented_cache` for telemetry events under this module name.
""".
-include_lib("dns_erlang/include/dns.hrl").
-export([
start_link/0,
get/1,
get/2,
put/2,
clear/0
]).
-define(DEFAULT_CACHE_BUCKETS, 3).
-define(DEFAULT_CACHE_TTL, 30).
-doc false.
-spec start_link() -> any().
start_link() ->
Config = #{
scope => erldns,
segment_num => ?DEFAULT_CACHE_BUCKETS,
ttl => {seconds, packet_cache_default_ttl() div ?DEFAULT_CACHE_BUCKETS}
},
segmented_cache:start_link(?MODULE, Config).
-doc "Try to retrieve a cached response for the given question.".
-spec get(dns:questions() | {dns:questions(), dns:additional()}) ->
dns:message() | {error, cache_expired | cache_miss}.
get(Key) ->
get(Key, undefined).
-doc "Try to retrieve a cached response for the given question sent by the given host.".
-spec get(dns:questions() | {dns:questions(), dns:additional()}, undefined | inet:ip_address()) ->
dns:message() | {error, cache_expired | cache_miss}.
get(Key, _Host) ->
case segmented_cache:get_entry(?MODULE, Key) of
#dns_message{} = Value ->
Value;
not_found ->
{error, cache_miss}
end.
-doc """
Put the response in the cache for the given question.
Returns if a new record was actually inserted, meaning a duplicate would return false.
""".
-spec put({dns:questions(), dns:additional()}, dns:message()) -> boolean().
put(Key, Response) ->
case packet_cache_enabled() of
true ->
segmented_cache:put_entry(?MODULE, Key, Response);
false ->
false
end.
-doc "Clear the cache".
-spec clear() -> any().
clear() ->
segmented_cache:delete_pattern(?MODULE, '_').
-spec packet_cache_enabled() -> boolean().
packet_cache_enabled() ->
case application:get_env(erldns, packet_cache, #{}) of
#{enabled := Bool} when is_boolean(Bool) ->
Bool;
_ ->
true
end.
-spec packet_cache_default_ttl() -> pos_integer().
packet_cache_default_ttl() ->
case application:get_env(erldns, packet_cache, #{}) of
#{ttl := TTL} when is_integer(TTL), 0 < TTL ->
TTL;
_ ->
?DEFAULT_CACHE_TTL
end.