ExPirate v0.1.0 ExPirate
Yo-ho-ho! ExPirate is a layer on top of the AgentMap library that provides
TTL and statistics.
Supported attributes
Each stored value is wrapped in a map ("item"). This item may contain the following keys ("attributes"):
value: term;ttl: pos_integer— the period (in ms) counted from the insertion or the last update during which this item can be used. A default TTL value can be given as option tostart/1orstart_link/1and updated viaset_prop/3;expired?: (item -> boolean)— indicator of item to be expired (used instead of TTL).
— this attributes are user customized. Also, ExPirate recognizes and
automatically updates the following properties of item:
inserted_at: integer— is stamped withSystem.monotonic_time(:millisecond)the first time item appears;updated: pos_integer(times) — is an update count;updated_at: integer— is stamped every time when item is changed;:used(_at)— is stamped every time when value is requested;:missed(_at)— is stamped every time when value is missing.
By default, ExPirate stores only :inserted_at (and later :updated_at)
attribute for items with TTL. To turn on other statistics provide attrs: [:updated | :updated_at | :inserted_at | :used | …] option on
start.
iex> {:ok, ep} = ExPirate.start() # ttl: ∞
...>
iex> ep
...> |> put(:key, 42)
...> |> get(:key)
42
iex> get(ep, :key, raw: true)
%{value: 42}
#
iex> ep
...> |> put(:key, 24, ttl: 5000)
...> |> get(:key, raw: true)
...> |> Map.keys()
[:ttl, :updated_at, :value]
Expired items
To decide which item is expired, either "time to live" attribute (:ttl) is
used or a custom indicator (:expired?).
TTL is a period of time in milliseconds, counted from insert (:inserted_at
attribute) or from the last update (:updated_at). ExPirate will never
return or use an expired item in calculations:
iex> {:ok, ep} = ExPirate.start()
...>
iex> ep
...> |> put(:key, 42, ttl: 20)
...> |> fetch(:key)
{:ok, 42}
#
iex> sleep(30)
...>
iex> fetch(ep, :key)
{:error, :expired}
#
iex> put(ep, :key, 43) # no TTL is given
...> #
iex> sleep(30)
iex> fetch(ep, :key)
{:ok, 43}
Also, can be used a custom indicator — an unary function that takes item as an argument:
iex> {:ok, ep} =
...> ExPirate.start(ttl: 20, attrs: [:used])
...>
iex> ep
...> |> put(:key, 42, expired?: & &1[:used] > 1)
...> |> fetch(:key)
{:ok, 42} # used: 1
#
iex> sleep(50)
iex> fetch(ep, :key)
{:ok, 42} # used: 2, … still there!
#
iex> sleep(50) # !
iex> fetch(ep, :key)
{:error, :expired} # used: 2
Link to this section Summary
Functions
Deletes value for key while keeps statistics
Fetches the value for a specific key
Fetches the value for a specific key, erroring out if value is missing or
expired
Returns the value for a specific key
Gets a value via the given fun
Puts value under the key. Statistics is updated
Start as an unlinked process
Starts instance that is linked to the current process
Synchronously stops the ExPirate instance with the given reason
Link to this section Types
attr()
attr() ::
:inserted_at
| :used
| :used_at
| :updated
| :updated_at
| :missed
| :missed_at
| :expired?
| :ttl
| custom()
attr() :: :inserted_at | :used | :used_at | :updated | :updated_at | :missed | :missed_at | :expired? | :ttl | custom()
custom()
custom() :: atom()
custom() :: atom()
Custom attribute.
default()
default() :: term()
default() :: term()
ep()
ep() :: ExPirate.t()
ep() :: ExPirate.t()
item()
item() :: %{
optional(:value) => term(),
optional(:used_at | :missed_at | :updated_at | :inserted_at) => integer(),
optional(:used | :updated | :missed) => non_neg_integer(),
optional(:expired?) => (item() -> boolean()),
optional(:ttl) => pos_integer(),
optional(custom()) => term()
}
item() :: %{
optional(:value) => term(),
optional(:used_at | :missed_at | :updated_at | :inserted_at) => integer(),
optional(:used | :updated | :missed) => non_neg_integer(),
optional(:expired?) => (item() -> boolean()),
optional(:ttl) => pos_integer(),
optional(custom()) => term()
}
Wrapper for a value.
key()
key() :: any()
key() :: any()
reason()
reason() :: term()
reason() :: term()
t()
t() :: AgentMap.t()
t() :: AgentMap.t()
value()
value() :: any()
value() :: any()
Link to this section Functions
delete(ep, key, opts \\ [cast: true, !: :max, raw: false])
Deletes value for key while keeps statistics.
Returns without waiting for the actual deletion to occur.
Default priority for this call is :max.
Options
cast: false— to return only after the actual delete;raw: true— to delete item viaAgentMap.delete/3;!: priority,:max;:timeout,5000.
Examples
iex> {:ok, ep} =
...> ExPirate.start(attrs: [:missed])
...>
iex> ep
...> |> put(:key, 42)
...> |> delete(:key)
...> |> fetch(:key)
{:error, :missing}
#
iex> get(ep, :key, raw: true) # statistics
%{missed: 1}
#
iex> ep
...> |> delete(:key, raw: true, cast: false)
...> |> get(:key, raw: true)
%{}
fetch(ep, key, opts \\ [!: :now])
Fetches the value for a specific key.
Returns:
{:ok, value};{:error, :missing}if item with suchkeyis missing or:valueattribute is not present;{:error, :expired}if value is present, but is expired. You can still retrive an expired value viaget(ep, key, raw: true).value.
Options
raw: true— to return value with statistics, wrapped in an item map;!: priority,:now— to return only when calls with priorities higher than given are finished for thiskey;:timeout,5000.
Examples
iex> {:ok, ep} =
...> ExPirate.start(attrs: [:missed, :used, :updated])
...>
iex> fetch(ep, :a)
{:error, :missing}
#
iex> ep
...> |> put(:a, 42, ttl: 20, cast: false)
...> |> fetch(:a)
{:ok, 42}
#
iex> sleep(20)
iex> fetch(ep, :a)
{:error, :expired}
#
iex> fetch(ep, :a, raw: true)
{:error, :expired}
#
iex> ep
...> |> get(:a, raw: true)
...> |> Map.delete(:inserted_at)
%{used: 1, missed: 3, ttl: 20}
fetch!(ep, key, opts \\ [!: :now])
Fetches the value for a specific key, erroring out if value is missing or
expired.
Returns current value or raises a KeyError.
See fetch/3.
Options
!: priority,:now— to return only when calls with higher priorities are finished to execute for thiskey;:timeout,5000.
Examples
iex> {:ok, ep} =
...> ExPirate.start()
iex> ep
...> |> put(:a, 1)
...> |> fetch!(:a)
1
iex> fetch!(ep, :b)
** (KeyError) key :b not found
iex> {:ok, ep} =
...> ExPirate.start(ttl: 20)
iex> ep
...> |> put(:a, 42)
...> |> fetch!(:a)
42
#
iex> sleep(30) # !
iex> fetch!(ep, :a)
** (ValueError) value for the key :a is expired
iex> {:ok, ep} =
...> ExPirate.start(attrs: [:inserted_at])
iex> ep
...> |> put(:a, 1)
...> |> delete(:a)
...> |> fetch!(:a)
** (KeyError) key :a not found
get(ep, key, opts \\ [!: :min, raw: false])
Returns the value for a specific key.
This call has the :min priority. As so, the value is retrived only after all
other calls for key are completed.
See get/4.
Options
:default,nil— value to return ifkeyis missing or expired;raw: true— to return item instead of just value;!: priority,:min;:timeout,5000.
Examples
iex> {:ok, ep} =
...> ExPirate.start(attrs: [:used])
...>
iex> get(ep, :key)
nil # used: 0
#
iex> ep
...> |> put(:key, 42, expired?: & &1[:used] > 0)
...> |> get(:key)
42 # used: 1
#
iex> sleep(30) # !
iex> get(ep, :key)
nil # used: 1
iex> get(ep, :key, default: 0)
0 # used: 1
iex> get(ep, :key, raw: true, default: 0).value
0 # used: 1
#
#
iex> ep
...> |> get(:key, raw: true)
...> |> Map.take([:value, :used])
%{used: 1} # no value is returned
# as it's expired
iex> ep
...> |> AgentMap.get(:key)
...> |> Map.take([:value, :used])
%{value: 42, used: 1} # but it's still there
get(ep, key, fun, opts)
Gets a value via the given fun.
A callback fun is sent to an instance that invokes it, passing as an
argument the value associated with key. The result of an invocation is
returned from this function. This call does not change value, and so, workers
execute a series of get-calls as a parallel Tasks.
Options
:default,nil— value forkeyif it's missing or expired;!: priority,:avg;!: :now— to execute call in a separateTaskspawned from server, passing current value (seeAgentMap.get/4);:timeout,5000.
Examples
iex> {:ok, ep} = ExPirate.start()
iex> get(ep, :a, & &1)
nil
iex> ep
...> |> put(:a, 42)
...> |> get(:a, & &1 + 1)
43
iex> get(ep, :b, & &1 + 1, default: 0)
1
put(ep, key, value, opts \\ [cast: true, !: :max])
Puts value under the key. Statistics is updated.
Returns without waiting for the actual put.
Default priority for this call is :max.
Options
expired?: (item -> boolean),get_prop(ep, :expired?)— a custom indicator that item is expired;ttl: pos_integer,get_prop(ep, :ttl)— period (in ms) counted from:inserted_ator:updated_at, during which this item considered as not expired;attrs: [attr] | nil,get_prop(ep, :attrs)— custom attribute list for thekey;cast: false— to return after the actual put;!: priority,:max;:timeout,5000.
Examples
iex> {:ok, ep} =
...> ExPirate.start(attrs: [:used])
...>
iex> ep
...> |> put(:key, 42, expired?: & &1[:used] > 1) # used: 0
...> |> get(:key)
42 # used: 1
iex> fetch(ep, :key)
{:ok, 42} # used: 2
#
iex> sleep(50) # !
iex> fetch(ep, :key)
{:error, :expired}
#
iex> ep
...> |> put(:key, 43)
...> |> get(:key, raw: true)
%{value: 43, used: 2} # used: 3
start(opts \\ [])
start(keyword()) :: GenServer.on_start()
start(keyword()) :: GenServer.on_start()
Start as an unlinked process.
See start_link/1 for details.
start_link(opts \\ [])
start_link(keyword()) :: GenServer.on_start()
start_link(keyword()) :: GenServer.on_start()
Starts instance that is linked to the current process.
Options
name: term— is used for registration as described in the module documentation;:debug— is used to invoke the corresponding function in:sysmodule;:spawn_opt— is passed as options to the underlying process as inProcess.spawn/4;:timeout,5000—ExPirateis allowed to spend at most the given number of milliseconds on the whole process of initialization or it will be terminated;attrs: [:updated, :updated_at, :used, :used_at, :missed, :missed_at],[]— attributes that are stored and automatically updated;expired?: (item -> boolean)— custom indicator of expired item;ttl: pos_integer— default period (in ms) counted from:updated_atduring which item is considered fresh.
Return values
If an instance is successfully created and initialized, the function returns
{:ok, pid}, where pid is the PID of the server. If a server with the
specified name already exists, the function returns {:error, {:already_started, pid}} with the PID of that process.
stop(ep, reason \\ :normal, timeout \\ :infinity)
Synchronously stops the ExPirate instance with the given reason.
Returns :ok if terminated with the given reason. If it terminates with
another reason, the call will exit.
This function keeps OTP semantics regarding error reporting. If the reason is
any other than :normal, :shutdown or {:shutdown, _}, an error report
will be logged.
Examples
iex> {:ok, pid} = ExPirate.start_link()
iex> ExPirate.stop(pid)
:ok