Skip to content

Commit

Permalink
Merge pull request #14 from zackehh/issue-6
Browse files Browse the repository at this point in the history
Refactor worker implementations around a CRUD interface
  • Loading branch information
whitfin committed Apr 16, 2016
2 parents 9443af4 + eefc9b1 commit 6a0f808
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 370 deletions.
62 changes: 32 additions & 30 deletions lib/cachex/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,6 @@ defmodule Cachex.Util do
# A small collection of utilities for use throughout the library. Mainly things
# to do with response formatting and generally just common functions.

@doc """
Consistency wrapper around current time in millis.
"""
def now, do: :os.system_time(1000)

@doc """
Lazy wrapper for creating an :error tuple.
"""
def error(value), do: { :error, value }

@doc """
Lazy wrapper for creating an :ok tuple.
"""
def ok(value), do: { :ok, value }

@doc """
Lazy wrapper for creating a :noreply tuple.
"""
def noreply(state), do: { :noreply, state }
def noreply(_value, state), do: { :noreply, state }

@doc """
Lazy wrapper for creating a :reply tuple.
"""
def reply(value, state), do: { :reply, value, state }

@doc """
Appends a string to an atom and returns as an atom.
"""
Expand Down Expand Up @@ -83,6 +57,11 @@ defmodule Cachex.Util do
if result, do: ok(true), else: error(false)
end

@doc """
Lazy wrapper for creating an :error tuple.
"""
def error(value), do: { :error, value }

@doc """
Retrieves a fallback value for a given key, using either the provided function
or using the default fallback implementation.
Expand Down Expand Up @@ -190,14 +169,16 @@ defmodule Cachex.Util do
|> handle_transaction
end
end
def handle_transaction({ :atomic, { :error, _ } = err}), do: err
def handle_transaction({ :atomic, { :ok, _ } = res}), do: res
def handle_transaction({ :atomic, { :loaded, _ } = res}), do: res
def handle_transaction({ :atomic, { :missing, _ } = res}), do: res
def handle_transaction({ :atomic, { :error, _ } = err }), do: err
def handle_transaction({ :atomic, { :ok, _ } = res }), do: res
def handle_transaction({ :atomic, { :loaded, _ } = res }), do: res
def handle_transaction({ :atomic, { :missing, _ } = res }), do: res
def handle_transaction({ :atomic, value }), do: ok(value)
def handle_transaction({ :aborted, reason }), do: error(reason)
def handle_transaction({ :atomic, _value }, value), do: ok(value)
def handle_transaction({ :aborted, reason }, _value), do: error(reason)
def handle_transaction(fun, pos) when is_function(fun) and is_number(pos),
do: fun |> handle_transaction |> elem(pos)

@doc """
Small utility to figure out if a document has expired based on the last touched
Expand Down Expand Up @@ -268,6 +249,27 @@ defmodule Cachex.Util do
|> to_string
end

@doc """
Lazy wrapper for creating a :noreply tuple.
"""
def noreply(state), do: { :noreply, state }
def noreply(_value, state), do: { :noreply, state }

@doc """
Consistency wrapper around current time in millis.
"""
def now, do: :os.system_time(1000)

@doc """
Lazy wrapper for creating an :ok tuple.
"""
def ok(value), do: { :ok, value }

@doc """
Lazy wrapper for creating a :reply tuple.
"""
def reply(value, state), do: { :reply, value, state }

@doc """
Returns a selection to return the designated value for all rows. Enables things
like finding all stored keys and all stored values.
Expand Down
87 changes: 56 additions & 31 deletions lib/cachex/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ defmodule Cachex.Worker do
cache: nil, # the cache name
options: %Options{ } # the options of this cache

# define some types
@type record :: { atom, any, number, number | nil, any }

@doc """
Simple initialization for use in the main owner process in order to start an
instance of a worker. All options are passed throught to the initialization
Expand Down Expand Up @@ -69,7 +72,22 @@ defmodule Cachex.Worker do
"""
def get(%__MODULE__{ } = state, key, options \\ []) when is_list(options) do
do_action(state, { :get, key, options }, fn ->
state.actions.get(state, key, options)
case state.actions.read(state, key) do
{ _cache, ^key, _touched, _ttl, value } ->
{ :ok, value }
_unrecognised_value ->
fb_fun =
options
|> Util.get_opt_function(:fallback)

case Util.get_fallback(state, key, fb_fun) do
{ :ok, new_value } ->
{ :missing, new_value }
{ :loaded, new_value } = result ->
set(state, key, new_value)
result
end
end
end)
end

Expand Down Expand Up @@ -117,7 +135,15 @@ defmodule Cachex.Worker do
"""
def set(%__MODULE__{ } = state, key, value, options \\ []) when is_list(options) do
do_action(state, { :set, key, value, options }, fn ->
state.actions.set(state, key, value, options)
ttl =
options
|> Util.get_opt_number(:ttl)

record =
state
|> Util.create_record(key, value, ttl)

state.actions.write(state, record)
end)
end

Expand All @@ -127,7 +153,7 @@ defmodule Cachex.Worker do
def update(%__MODULE__{ } = state, key, value, options \\ []) when is_list(options) do
do_action(state, { :update, key, value, options }, fn ->
with { :ok, true } <- check_exists(state, key) do
state.actions.update(state, key, value, options)
state.actions.update(state, key, [{ 5, value }])
end
end)
end
Expand All @@ -137,7 +163,7 @@ defmodule Cachex.Worker do
"""
def del(%__MODULE__{ } = state, key, options \\ []) when is_list(options) do
do_action(state, { :del, key, options }, fn ->
state.actions.del(state, key, options)
state.actions.delete(state, key)
end)
end

Expand Down Expand Up @@ -176,12 +202,10 @@ defmodule Cachex.Worker do
"""
def exists?(%__MODULE__{ } = state, key, options \\ []) when is_list(options) do
do_action(state, { :exists?, key, options }, fn ->
case :ets.lookup(state.cache, key) do
[{ _cache, ^key, touched, ttl, _value }] ->
expired = Util.has_expired?(touched, ttl)
expired && del(state, key)
{ :ok, !expired }
_unrecognised_val ->
case state.actions.read(state, key) do
{ _cache, ^key, _touched, _ttl, _value } ->
{ :ok, true }
_unrecognised_value ->
{ :ok, false }
end
end)
Expand All @@ -193,11 +217,10 @@ defmodule Cachex.Worker do
def expire(%__MODULE__{ } = state, key, expiration, options \\ []) when is_list(options) do
do_action(state, { :expire, key, expiration, options }, fn ->
with { :ok, true } <- check_exists(state, key) do
case expiration do
val when val == nil or val > 0 ->
state.actions.expire(state, key, expiration, options)
_expired_already ->
del(state, key)
if expiration == nil or expiration > 0 do
state.actions.update(state, key, [{ 3, Util.now() }, { 4, expiration }])
else
del(state, key, via: :purge)
end
end
end)
Expand Down Expand Up @@ -236,7 +259,7 @@ defmodule Cachex.Worker do
def refresh(%__MODULE__{ } = state, key, options \\ []) when is_list(options) do
do_action(state, { :refresh, key, options }, fn ->
with { :ok, true } <- check_exists(state, key) do
state.actions.refresh(state, key, options)
state.actions.update(state, key, [{ 3, Util.now() }])
end
end)
end
Expand Down Expand Up @@ -293,31 +316,32 @@ defmodule Cachex.Worker do
"""
def ttl(%__MODULE__{ } = state, key, options \\ []) when is_list(options) do
do_action(state, { :ttl, key, options }, fn ->
state.actions.ttl(state, key, options)
case state.actions.read(state, key) do
{ _cache, ^key, _touched, nil, _value } ->
{ :ok, nil }
{ _cache, ^key, touched, ttl, _value } ->
{ :ok, touched + ttl - Util.now() }
_unrecognised_value ->
{ :missing, nil }
end
end)
end

###
# Behaviours to enforce on all types of workers.
###

@doc """
Invoked when retrieving a key from the cache. This callback should be implemented
such that either a key is returned, or a nil value is returned. A status should
also be provided which is any of `:ok`, `:missing`, or `:loaded` to represent
how the key was retrieved.
"""
@callback get(__MODULE__, any, list) :: { :ok | :loaded | :missing, any }
@callback set(__MODULE__, any, any, list) :: { :ok, true | false }
@callback update(__MODULE__, any, any, list) :: { :ok, true | false }
@callback del(__MODULE__, any, list) :: { :ok, true | false }
# CRUD
@callback write(__MODULE__, record) :: { :ok, true | false }
@callback read(__MODULE__, any) :: record | nil
@callback update(__MODULE__, any, [ { number, any } ]) :: { :ok, true | false }
@callback delete(__MODULE__, any) :: { :ok, true | false }

# Bonus
@callback clear(__MODULE__, list) :: { :ok, number }
@callback expire(__MODULE__, any, number, list) :: { :ok, true | false }
@callback keys(__MODULE__, list) :: { :ok, list }
@callback incr(__MODULE__, any, list) :: { :ok, number }
@callback refresh(__MODULE__, any, list) :: { :ok, true | false }
@callback take(__MODULE__, any, list) :: { :ok | :missing, any }
@callback ttl(__MODULE__, any, list) :: { :ok | :missing, any }

###
# GenServer delegate functions for call/cast.
Expand Down Expand Up @@ -398,6 +422,7 @@ defmodule Cachex.Worker do

message = case options[:via] do
nil -> message
val when is_tuple(val) -> val
val -> put_elem(message, 0, val)
end

Expand All @@ -413,7 +438,7 @@ defmodule Cachex.Worker do
if notify do
case state.options.post_hooks do
[] -> nil;
li -> Notifier.notify(li, message, result)
li -> Notifier.notify(li, message, options[:hook_result] || result)
end
end

Expand Down
Loading

0 comments on commit 6a0f808

Please sign in to comment.