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 to start/1 or start_link/1 and updated via set_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 with System.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

Types

Custom attribute

Wrapper for a value

t()

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

Link to this type

attr()
attr() ::
  :inserted_at
  | :used
  | :used_at
  | :updated
  | :updated_at
  | :missed
  | :missed_at
  | :expired?
  | :ttl
  | custom()

Link to this type

custom()
custom() :: atom()

Custom attribute.

Link to this type

default()
default() :: term()

Link to this type

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()
}

Wrapper for a value.

Link to this type

key()
key() :: any()

Link to this type

reason()
reason() :: term()

Link to this type

value()
value() :: any()

Link to this section Functions

Link to this function

delete(ep, key, opts \\ [cast: true, !: :max, raw: false])
delete(ep(), key(), keyword() | timeout()) :: ep()

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 via AgentMap.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)
%{}
Link to this function

fetch(ep, key, opts \\ [!: :now])
fetch(ep(), key(), keyword() | timeout()) ::
  {:ok, value() | item()} | {:error, :missing | :expired}

Fetches the value for a specific key.

Returns:

  • {:ok, value};

  • {:error, :missing} if item with such key is missing or :value attribute is not present;

  • {:error, :expired} if value is present, but is expired. You can still retrive an expired value via get(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 this key;

  • :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}
Link to this function

fetch!(ep, key, opts \\ [!: :now])
fetch!(ep(), key(), keyword() | timeout()) :: value() | item() | no_return()

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 this key;

  • :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
Link to this function

get(ep, key, opts \\ [!: :min, raw: false])
get(ep(), key(), keyword()) :: value() | item() | default()

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 if key is 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
Link to this function

get(ep, key, fun, opts)
get(ep(), key(), (value() | default() -> get), keyword() | timeout()) :: get
when get: var
get(ep(), key(), (item() -> get), keyword()) :: get when get: var

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 for key if it's missing or expired;

  • !: priority, :avg;

  • !: :now — to execute call in a separate Task spawned from server, passing current value (see AgentMap.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
Link to this function

put(ep, key, value, opts \\ [cast: true, !: :max])
put(ep(), key(), value(), keyword() | timeout()) :: ep()

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_at or :updated_at, during which this item considered as not expired;

  • attrs: [attr] | nil, get_prop(ep, :attrs) — custom attribute list for the key;

  • 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
Link to this function

start(opts \\ [])
start(keyword()) :: GenServer.on_start()

Start as an unlinked process.

See start_link/1 for details.

Link to this function

start_link(opts \\ [])
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 :sys module;

  • :spawn_opt — is passed as options to the underlying process as in Process.spawn/4;

  • :timeout, 5000ExPirate is 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_at during 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.

Link to this function

stop(ep, reason \\ :normal, timeout \\ :infinity)
stop(ep(), reason :: term(), timeout()) :: :ok

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