new async api, returns reference to operation

This commit is contained in:
Dmitry Kolesnikov 2015-05-10 14:02:58 +03:00
parent 572050affc
commit ff404f45a8
8 changed files with 451 additions and 486 deletions

243
Makefile
View File

@ -1,52 +1,237 @@
.PHONY: deps test ## @author Dmitry Kolesnikov, <dmkolesnikov@gmail.com>
## @copyright (c) 2012 - 2014 Dmitry Kolesnikov. All Rights Reserved
##
## @description
## Makefile to build and release Erlang applications using
## rebar, reltool, etc (see README for details)
##
## application version schema (based on semantic version)
## ${APP}-${VSN}+${GIT}.${ARCH}.${PLAT}
##
## @version 0.8.2
.PHONY: test rel deps all pkg
ifeq ($(id),) #####################################################################
export id=cache ##
## application config
##
#####################################################################
ROOT = `pwd`
PREFIX ?= /usr/local
APP ?= $(notdir $(CURDIR))
ARCH?= $(shell uname -m)
PLAT?= $(shell uname -s)
HEAD?= $(shell git rev-parse --short HEAD)
TAG = ${HEAD}.${ARCH}.${PLAT}
TEST?= ${APP}
S3 =
GIT ?=
VMI =
NET ?= lo0
USER =
PASS =
## root path to benchmark framework
BB = ../basho_bench
SSHENV = /tmp/ssh-agent.conf
ADDR = $(shell ifconfig ${NET} | sed -En 's/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p')
BRANCH = $(shell git symbolic-ref --short -q HEAD)
## erlang flags (make run only)
EFLAGS = \
-name ${APP}@${ADDR} \
-setcookie nocookie \
-pa ${ROOT}/ebin \
-pa ${ROOT}/deps/*/ebin \
-pa ${ROOT}/apps/*/ebin \
-pa rel/files \
-kernel inet_dist_listen_min 32100 \
-kernel inet_dist_listen_max 32199 \
+P 1000000 \
+K true +A 160 -sbt ts
#####################################################################
##
## internal config
##
#####################################################################
ifeq ($(wildcard rel/reltool.config),)
REL =
VSN =
TAR =
PKG =
else
IID = $(shell cat rel/reltool.config | sed -n 's/{target_dir,.*\"\([^-]*\).*\"}./\1/p')
REL = $(shell cat rel/reltool.config | sed -n 's/{target_dir,.*\"\(.*\)\"}./\1/p')
VSN = $(shell echo ${REL} | sed -n 's/.*-\(.*\)/\1/p')
ifeq (${VSN},)
VSN = $(shell cat rel/reltool.config | sed -n 's/.*{rel,.*\".*\",.*\"\(.*\)\".*/\1/p')
endif
ifeq (${config},)
RFLAGS =
VARIANT =
else
VARIANT = $(addprefix ., $(notdir $(basename ${config})))
RFLAGS = target_dir=${REL}${VARIANT} overlay_vars=${ROOT}/${config}
endif
ifeq (${VSN},)
TAR = ${IID}${VARIANT}+${TAG}.tgz
PKG = ${IID}${VARIANT}+${TAG}.bundle
else
TAR = ${IID}-${VSN}${VARIANT}+${TAG}.tgz
PKG = ${IID}-${VSN}${VARIANT}+${TAG}.bundle
endif
endif endif
FLAGS=\ ## self-extracting bundle wrapper
-name ${id}@127.0.0.1 \ BUNDLE_INIT = PREFIX=${PREFIX}\nREL=${PREFIX}/${REL}${VARIANT}\nAPP=${APP}\nVSN=${VSN}\nLINE=\`grep -a -n 'BUNDLE:\x24' \x240\`\ntail -n +\x24(( \x24{LINE\x25\x25:*} + 1)) \x240 | gzip -vdc - | tar -C ${PREFIX} -xvf - > /dev/null\n
-setcookie nocookie \ BUNDLE_FREE = exit\nBUNDLE:\n
-pa ./deps/*/ebin \ BUILDER = cd /tmp && git clone -b ${BRANCH} ${GIT}/${APP} && cd /tmp/${APP} && make && make rel && sleep 300
-pa ./examples/*/ebin \
-pa ./ebin \
+K true +A 160 -sbt ts
BB=../basho_bench
#####################################################################
##
## build
##
#####################################################################
all: rebar deps compile all: rebar deps compile
compile: compile:
./rebar compile @./rebar compile
deps: deps:
./rebar get-deps @./rebar get-deps
clean: clean:
./rebar clean @./rebar clean ; \
rm -rf test.*-temp-data rm -rf test.*-temp-data ; \
rm -rf tests ; \
rm -rf log ; \
rm -f *.tgz ; \
rm -f *.bundle
distclean: clean distclean: clean
./rebar delete-deps @./rebar delete-deps
test: all unit: all
./rebar skip_deps=true eunit @./rebar skip_deps=true eunit
test:
@erl ${EFLAGS} -run deb test test/${TEST}.config
docs: docs:
./rebar skip_deps=true doc @./rebar skip_deps=true doc
dialyzer: compile #####################################################################
@dialyzer -Wno_return -c ./ebin ##
## release
##
#####################################################################
ifneq (${REL},)
rel: ${TAR}
## assemble VM release
ifeq (${PLAT},$(shell uname -s))
${TAR}:
@./rebar generate ${RFLAGS}; \
cd rel ; tar -zcf ${TAR} ${REL}${VARIANT}/ ; mv ${TAR} ../${TAR} ; cd - ;\
echo "==> tarball: ${TAR}"
else
ifneq (${VMI},)
${TAR}:
@echo "==> docker run ${VMI}" ;\
K=`test ${PASS} && cat ${PASS}` ;\
A=`test ${USER} && echo "mkdir -p /root/.ssh && echo \"$$K\" > /root/.ssh/id_rsa && chmod 0700 /root/.ssh/id_rsa && echo -e \"Host *\n\tUser ${USER}\n\tStrictHostKeyChecking no\n\" > /root/.ssh/config &&"` ;\
I=`docker run -d -a stdout -a stderr ${VMI} /bin/sh -c "$$A ${BUILDER}"` ;\
(docker attach $$I &) ;\
docker cp $$I:/tmp/${APP}/${TAR} . 1> /dev/null 2>&1 ;\
while [ $$? -ne 0 ] ;\
do \
sleep 10 ;\
docker cp $$I:/tmp/${APP}/${TAR} . 1> /dev/null 2>&1 ;\
done ;\
docker kill $$I ;\
docker rm $$I
endif
endif
## package VM release to executable bundle
pkg: rel/deploy.sh ${TAR}
@printf "${BUNDLE_INIT}" > ${PKG} ; \
cat rel/deploy.sh >> ${PKG} ; \
printf "${BUNDLE_FREE}" >> ${PKG} ; \
cat ${TAR} >> ${PKG} ; \
chmod ugo+x ${PKG} ; \
echo "==> bundle: ${PKG}"
## copy 'package' to s3
## copy 'package' to s3
s3: ${PKG}
aws s3 cp ${PKG} ${S3}/${APP}+${TAG}${VARIANT}.bundle
s3-latest: ${PKG}
aws s3 cp ${PKG} ${S3}/${APP}+latest${VARIANT}.bundle
endif
#####################################################################
##
## deploy
##
#####################################################################
ifneq (${host},)
${SSHENV}:
@echo "==> ssh: config keys" ;\
ssh-agent -s > ${SSHENV}
node: ${SSHENV}
@echo "==> deploy: ${host}" ;\
. ${SSHENV} ;\
k=`basename ${pass}` ;\
l=`ssh-add -l | grep $$k` ;\
if [ -z "$$l" ] ; then \
ssh-add ${pass} ;\
fi ;\
rsync -cav --rsh=ssh --progress ${PKG} ${host}:${PKG} ;\
ssh -t ${host} "sudo sh ./${PKG}"
endif
#####################################################################
##
## debug
##
#####################################################################
run: run:
erl ${FLAGS} @erl ${EFLAGS}
rebar:
curl -O https://cloud.github.com/downloads/basho/rebar/rebar
chmod ugo+x rebar
benchmark: benchmark:
$(BB)/basho_bench -N bb@127.0.0.0 -C nocookie priv/${id}.benchmark @echo "==> benchmark: ${TEST}" ;\
$(BB)/priv/summary.r -i tests/current $(BB)/basho_bench -N bb@127.0.0.1 -C nocookie priv/${TEST}.benchmark ;\
$(BB)/priv/summary.r -i tests/current ;\
open tests/current/summary.png open tests/current/summary.png
ifneq (${REL},)
start:
@./rel/${REL}${VARIANT}/bin/${APP} start
stop:
@./rel/${REL}${VARIANT}/bin/${APP} stop
console:
@./rel/${REL}${VARIANT}/bin/${APP} console
attach:
@./rel/${REL}${VARIANT}/bin/${APP} attach
endif
#####################################################################
##
## dependencies
##
#####################################################################
rebar:
@curl -L -O https://github.com/rebar/rebar/wiki/rebar ; \
chmod ugo+x rebar

View File

@ -8,7 +8,10 @@ The write operation always uses youngest segment. The read operation lookup key
The downside is inability to assign precise TTL per single cache entry. TTL is always approximated to nearest segment. (e.g. cache with 60 sec TTL and 10 segments has 6 sec accuracy on TTL) The downside is inability to assign precise TTL per single cache entry. TTL is always approximated to nearest segment. (e.g. cache with 60 sec TTL and 10 segments has 6 sec accuracy on TTL)
## Change log
* 2.0.0 - various changes on asynchronous api, not compatible with version 1.x
* 1.0.1 - production release
## Usage ## Usage
@ -16,8 +19,13 @@ The downside is inability to assign precise TTL per single cache entry. TTL is a
application:start(cache). application:start(cache).
{ok, _} = cache:start_link(my_cache, [{n, 10}, {ttl, 60}]). {ok, _} = cache:start_link(my_cache, [{n, 10}, {ttl, 60}]).
%% synchronous i/o
ok = cache:put(my_cache, <<"my key">>, <<"my value">>). ok = cache:put(my_cache, <<"my key">>, <<"my value">>).
Val = cache:get(my_cache, <<"my key">>). Val = cache:get(my_cache, <<"my key">>).
%% asynchronous i/o
Ref = cache:get_(my_cache, <<"my key">>).
receive {Ref, Val} -> Val end.
``` ```
### configuration via Erlang `sys.config` ### configuration via Erlang `sys.config`

View File

@ -16,7 +16,6 @@
,{get, 5} ,{get, 5}
]}. ]}.
%{cache, {mycache, 'cache@127.0.0.1'}}.
{local, [ {local, [
{ttl, 20} {ttl, 20}
,{n, 10} ,{n, 10}

View File

@ -1,7 +1,7 @@
{application, cache, {application, cache,
[ [
{description, "in-memory cache"}, {description, "in-memory cache"},
{vsn, "1.0.1"}, {vsn, "2.0.0"},
{modules, []}, {modules, []},
{registered, []}, {registered, []},
{applications,[ {applications,[

View File

@ -31,7 +31,7 @@
-include("cache.hrl"). -include("cache.hrl").
-export([start/0]). %% cache management interface
-export([ -export([
start_link/1, start_link/1,
start_link/2, start_link/2,
@ -39,20 +39,27 @@
purge/1, purge/1,
i/1, i/1,
i/2, i/2,
heap/2, heap/2
]).
%% basic cache i/o interface
-export([
put/3, put/3,
put/4, put/4,
put_/3, put_/3,
put_/4, put_/4,
get/2, get/2,
get_/2,
lookup/2, lookup/2,
lookup_/2,
has/2, has/2,
ttl/2, ttl/2,
remove/2, remove/2,
remove_/2, remove_/2
]).
%% extended cache i/o interface
-export([
acc/3, acc/3,
acc_/3, acc_/3,
% memecached like interface
set/3, set/3,
set/4, set/4,
set_/3, set_/3,
@ -72,21 +79,26 @@
delete/2, delete/2,
delete_/2 delete_/2
]). ]).
-export([start/0]).
-export_type([cache/0]). -export_type([cache/0]).
-type(cache() :: atom() | {atom(), atom()} | {global, atom()} | pid()). -type(cache() :: atom() | pid()).
-type(name() :: atom() | {global, atom()}).
-type(key() :: any()). -type(key() :: any()).
-type(entity() :: any()). -type(val() :: any()).
-type(ttl() :: integer()). -type(ttl() :: integer()).
%% %%
%% RnD start application %% RnD start application
start() -> start() ->
application:start(pq),
application:start(cache). application:start(cache).
%%%----------------------------------------------------------------------------
%%%
%%% cache management interface
%%%
%%%----------------------------------------------------------------------------
%% %%
%% start new cache bucket, accepted options: %% start new cache bucket, accepted options:
%% {type, set | ordered_set} - cache segment type (default set) %% {type, set | ordered_set} - cache segment type (default set)
@ -99,7 +111,7 @@ start() ->
%% {stats, function() | {Mod, Fun}} - cache statistic aggregate functor %% {stats, function() | {Mod, Fun}} - cache statistic aggregate functor
%% {heir, atom() | pid()} - heir of evicted cache segments %% {heir, atom() | pid()} - heir of evicted cache segments
-spec(start_link/1 :: (list()) -> {ok, pid()} | {error, any()}). -spec(start_link/1 :: (list()) -> {ok, pid()} | {error, any()}).
-spec(start_link/2 :: (name(), list()) -> {ok, pid()} | {error, any()}). -spec(start_link/2 :: (atom(), list()) -> {ok, pid()} | {error, any()}).
start_link(Opts) -> start_link(Opts) ->
cache_bucket:start_link(Opts). cache_bucket:start_link(Opts).
@ -111,41 +123,21 @@ start_link(Cache, Opts) ->
%% drop cache %% drop cache
-spec(drop/1 :: (cache()) -> ok). -spec(drop/1 :: (cache()) -> ok).
drop(undefined) -> drop(Cache) ->
ok;
drop({global, Cache}) ->
drop(global:whereis_name(Cache));
drop({Cache, Node}) ->
drop(rpc:call(Node, cache, drop, [Cache]));
drop(Cache)
when is_atom(Cache) ->
drop(whereis(Cache));
drop(Cache)
when is_pid(Cache) ->
gen_server:call(Cache, drop). gen_server:call(Cache, drop).
%% %%
%% purge cache %% purge cache
-spec(purge/1 :: (cache()) -> ok). -spec(purge/1 :: (cache()) -> ok).
purge(undefined) -> purge(Cache) ->
ok;
purge({global, Cache}) ->
purge(global:whereis_name(Cache));
purge({Cache, Node}) ->
purge(rpc:call(Node, cache, purge, [Cache]));
purge(Cache)
when is_atom(Cache) ->
purge(whereis(Cache));
purge(Cache)
when is_pid(Cache) ->
gen_server:call(Cache, purge). gen_server:call(Cache, purge).
%% %%
%% return cache meta data %% return cache meta data
%% {heap, [integer()]} - cache segments references %% {heap, [integer()]} - references to cache segments
%% {expire, [integer()]} - cache segments expire times %% {expire, [integer()]} - cache segments expiration times
%% {size, [integer()]} - cardinality of cache segments %% {size, [integer()]} - cardinality of cache segments
%% {memory, [integer()]} - memory occupied by each cache segment %% {memory, [integer()]} - memory occupied by each cache segment
-spec(i/1 :: (cache()) -> list()). -spec(i/1 :: (cache()) -> list()).
@ -165,94 +157,114 @@ heap(Cache, N) ->
gen_server:call(Cache, {heap, N}). gen_server:call(Cache, {heap, N}).
%%%----------------------------------------------------------------------------
%%%
%%% basic cache i/o interface
%%%
%%%----------------------------------------------------------------------------
%% %%
%% synchronous cache put %% synchronous cache put
-spec(put/3 :: (cache(), key(), entity()) -> ok). -spec(put/3 :: (cache(), key(), val()) -> ok).
-spec(put/4 :: (cache(), key(), entity(), ttl()) -> ok). -spec(put/4 :: (cache(), key(), val(), ttl()) -> ok).
put(Cache, Key, Val) -> put(Cache, Key, Val) ->
gen_server:call(Cache, {put, Key, Val}, ?DEF_CACHE_TIMEOUT). cache:put(Cache, Key, Val, undefined).
put(Cache, Key, Val, TTL) -> put(Cache, Key, Val, TTL) ->
gen_server:call(Cache, {put, Key, Val, TTL}, ?DEF_CACHE_TIMEOUT). call(Cache, {put, Key, Val, TTL}).
%% %%
%% asynchronous cache put %% asynchronous cache put
-spec(put_/3 :: (cache(), key(), entity()) -> ok). -spec(put_/3 :: (cache(), key(), val()) -> reference()).
-spec(put_/4 :: (cache(), key(), entity(), ttl()) -> ok). -spec(put_/4 :: (cache(), key(), val(), ttl()) -> reference()).
put_(Cache, Key, Val) -> put_(Cache, Key, Val) ->
gen_server:cast(Cache, {put, Key, Val}). cache:put_(Cache, Key, Val, undefined).
put_(Cache, Key, Val, TTL) -> put_(Cache, Key, Val, TTL) ->
gen_server:cast(Cache, {put, Key, Val, TTL}). cast(Cache, {put, Key, Val, TTL}).
%% %%
%% synchronous get cache entry, the operation prolongs entry ttl %% synchronous cache get, the operation prolongs value ttl
-spec(get/2 :: (cache(), key()) -> entity() | undefined). -spec(get/2 :: (cache(), key()) -> val() | undefined).
get(Cache, Key) -> get(Cache, Key) ->
gen_server:call(Cache, {get, Key}, ?DEF_CACHE_TIMEOUT). call(Cache, {get, Key}).
%% %%
%% synchronous lookup cache entry, the operation do not prolong entry ttl %% asynchronous cache get, the operation prolongs value ttl
-spec(lookup/2 :: (cache(), key()) -> entity() | undefined). -spec(get_/2 :: (cache(), key()) -> reference()).
get_(Cache, Key) ->
cast(Cache, {get, Key}).
%%
%% synchronous cache lookup, the operation do not prolong entry ttl
-spec(lookup/2 :: (cache(), key()) -> val() | undefined).
lookup(Cache, Key) -> lookup(Cache, Key) ->
gen_server:call(Cache, {lookup, Key}, ?DEF_CACHE_TIMEOUT). call(Cache, {lookup, Key}).
%%
%% asynchronous cache lookup, the operation do not prolong entry ttl
-spec(lookup_/2 :: (cache(), key()) -> reference()).
lookup_(Cache, Key) ->
cast(Cache, {lookup, Key}).
%% %%
%% check if cache key exists, %% check if cache key exists,
-spec(has/2 :: (cache(), key()) -> true | false). -spec(has/2 :: (cache(), key()) -> true | false).
has(Cache, Key) -> has(Cache, Key) ->
gen_server:call(Cache, {has, Key}, ?DEF_CACHE_TIMEOUT). call(Cache, {has, Key}).
%% %%
%% check entity at cache and return estimated ttl %% check entity at cache and return estimated ttl
-spec(ttl/2 :: (cache(), key()) -> ttl() | false). -spec(ttl/2 :: (cache(), key()) -> ttl() | false).
ttl(Cache, Key) -> ttl(Cache, Key) ->
gen_server:call(Cache, {ttl, Key}, ?DEF_CACHE_TIMEOUT). call(Cache, {ttl, Key}).
%% %%
%% synchronous remove entry from cache %% synchronous remove entry from cache
-spec(remove/2 :: (cache(), key()) -> ok). -spec(remove/2 :: (cache(), key()) -> ok).
remove(Cache, Key) -> remove(Cache, Key) ->
gen_server:call(Cache, {remove, Key}, ?DEF_CACHE_TIMEOUT). call(Cache, {remove, Key}).
%% %%
%% asynchronous remove entry from cache %% asynchronous remove entry from cache
-spec(remove_/2 :: (cache(), key()) -> ok). -spec(remove_/2 :: (cache(), key()) -> ok).
remove_(Cache, Key) -> remove_(Cache, Key) ->
gen_server:cast(Cache, {remove, Key}). cast(Cache, {remove, Key}).
%%%----------------------------------------------------------------------------
%%%
%%% extended cache i/o interface
%%%
%%%----------------------------------------------------------------------------
%% %%
%% synchronous in-cache accumulator %% synchronous in-cache accumulator
-spec(acc/3 :: (cache(), key(), integer() | [{integer(), integer()}]) -> integer() | undefined). -spec(acc/3 :: (cache(), key(), integer() | [{integer(), integer()}]) -> integer() | undefined).
acc(Cache, Key, Val) -> acc(Cache, Key, Val) ->
gen_server:call(Cache, {acc, Key, Val}, ?DEF_CACHE_TIMEOUT). call(Cache, {acc, Key, Val}).
%% %%
%% asynchronous in-cache accumulator %% asynchronous in-cache accumulator
-spec(acc_/3 :: (cache(), key(), integer() | {integer(), integer()}) -> ok). -spec(acc_/3 :: (cache(), key(), integer() | {integer(), integer()}) -> ok).
acc_(Cache, Key, Val) -> acc_(Cache, Key, Val) ->
gen_server:cast(Cache, {acc, Key, Val}). cast(Cache, {acc, Key, Val}).
%%%----------------------------------------------------------------------------
%%%
%%% memcached-like interface
%%%
%%%----------------------------------------------------------------------------
%% %%
%% synchronous store key/val %% synchronous store key/val
-spec(set/3 :: (cache(), key(), entity()) -> ok). -spec(set/3 :: (cache(), key(), val()) -> ok).
-spec(set/4 :: (cache(), key(), entity(), ttl()) -> ok). -spec(set/4 :: (cache(), key(), val(), ttl()) -> ok).
set(Cache, Key, Val) -> set(Cache, Key, Val) ->
cache:put(Cache, Key, Val). cache:put(Cache, Key, Val).
@ -262,8 +274,8 @@ set(Cache, Key, Val, TTL) ->
%% %%
%% asynchronous store key/val %% asynchronous store key/val
-spec(set_/3 :: (cache(), key(), entity()) -> ok). -spec(set_/3 :: (cache(), key(), val()) -> ok).
-spec(set_/4 :: (cache(), key(), entity(), ttl()) -> ok). -spec(set_/4 :: (cache(), key(), val(), ttl()) -> ok).
set_(Cache, Key, Val) -> set_(Cache, Key, Val) ->
cache:put_(Cache, Key, Val). cache:put_(Cache, Key, Val).
@ -271,84 +283,79 @@ set_(Cache, Key, Val) ->
set_(Cache, Key, Val, TTL) -> set_(Cache, Key, Val, TTL) ->
cache:put_(Cache, Key, Val, TTL). cache:put_(Cache, Key, Val, TTL).
%% %%
%% synchronous store key/val only if cache does not already hold data for this key %% synchronous store key/val only if cache does not already hold data for this key
-spec(add/3 :: (cache(), key(), entity()) -> ok | conflict). -spec(add/3 :: (cache(), key(), val()) -> ok | {error, conflict}).
-spec(add/4 :: (cache(), key(), entity(), ttl()) -> ok | conflict). -spec(add/4 :: (cache(), key(), val(), ttl()) -> ok | {error, conflict}).
add(Cache, Key, Val) -> add(Cache, Key, Val) ->
gen_server:call(Cache, {add, Key, Val}, ?DEF_CACHE_TIMEOUT). cache:add(Cache, Key, Val, undefined).
add(Cache, Key, Val, TTL) -> add(Cache, Key, Val, TTL) ->
gen_server:call(Cache, {add, Key, Val, TTL}, ?DEF_CACHE_TIMEOUT). call(Cache, {add, Key, Val, TTL}).
%% %%
%% asynchronous store key/val only if cache does not already hold data for this key %% asynchronous store key/val only if cache does not already hold data for this key
-spec(add_/3 :: (cache(), key(), entity()) -> ok). -spec(add_/3 :: (cache(), key(), val()) -> reference()).
-spec(add_/4 :: (cache(), key(), entity(), ttl()) -> ok). -spec(add_/4 :: (cache(), key(), val(), ttl()) -> reference()).
add_(Cache, Key, Val) -> add_(Cache, Key, Val) ->
gen_server:cast(Cache, {add, Key, Val}). cache:add_(Cache, Key, Val, undefined).
add_(Cache, Key, Val, TTL) -> add_(Cache, Key, Val, TTL) ->
gen_server:cast(Cache, {add, Key, Val, TTL}). cast(Cache, {add, Key, Val, TTL}).
%% %%
%% synchronous store key/val only if cache does hold data for this key %% synchronous store key/val only if cache does hold data for this key
-spec(replace/3 :: (cache(), key(), entity()) -> ok | not_found). -spec(replace/3 :: (cache(), key(), val()) -> ok | {error, not_found}).
-spec(replace/4 :: (cache(), key(), entity(), ttl()) -> ok | not_found). -spec(replace/4 :: (cache(), key(), val(), ttl()) -> ok | {error, not_found}).
replace(Cache, Key, Val) -> replace(Cache, Key, Val) ->
gen_server:call(Cache, {replace, Key, Val}, ?DEF_CACHE_TIMEOUT). cache:replace(Cache, Key, Val, undefined).
replace(Cache, Key, Val, TTL) -> replace(Cache, Key, Val, TTL) ->
gen_server:call(Cache, {replace, Key, Val, TTL}, ?DEF_CACHE_TIMEOUT). call(Cache, {replace, Key, Val, TTL}).
%% %%
%% asynchronous store key/val only if cache does hold data for this key %% asynchronous store key/val only if cache does hold data for this key
-spec(replace_/3 :: (cache(), key(), entity()) -> ok). -spec(replace_/3 :: (cache(), key(), val()) -> reference()).
-spec(replace_/4 :: (cache(), key(), entity(), ttl()) -> ok). -spec(replace_/4 :: (cache(), key(), val(), ttl()) -> reference()).
replace_(Cache, Key, Val) -> replace_(Cache, Key, Val) ->
gen_server:cast(Cache, {replace, Key, Val}). cache:replace_(Cache, Key, Val).
replace_(Cache, Key, Val, TTL) -> replace_(Cache, Key, Val, TTL) ->
gen_server:cast(Cache, {replace, Key, Val, TTL}). cast(Cache, {replace, Key, Val, TTL}).
%% %%
%% synchronously add data to existing key after existing data, the operation do not prolong entry ttl %% synchronously add data to existing key after existing data, the operation do not prolong entry ttl
-spec(append/3 :: (cache(), key(), entity()) -> ok | not_found). -spec(append/3 :: (cache(), key(), val()) -> ok | {error, not_found}).
append(Cache, Key, Val) -> append(Cache, Key, Val) ->
gen_server:call(Cache, {append, Key, Val}, ?DEF_CACHE_TIMEOUT). call(Cache, {append, Key, Val}).
%% %%
%% asynchronously add data to existing key after existing data, the operation do not prolong entry ttl %% asynchronously add data to existing key after existing data, the operation do not prolong entry ttl
-spec(append_/3 :: (cache(), key(), entity()) -> ok). -spec(append_/3 :: (cache(), key(), val()) -> reference()).
append_(Cache, Key, Val) -> append_(Cache, Key, Val) ->
gen_server:cast(Cache, {append, Key, Val}). cast(Cache, {append, Key, Val}).
%% %%
%% synchronously add data to existing key before existing data %% synchronously add data to existing key before existing data
-spec(prepend/3 :: (cache(), key(), entity()) -> ok | not_found). -spec(prepend/3 :: (cache(), key(), val()) -> ok | {error, not_found}).
prepend(Cache, Key, Val) -> prepend(Cache, Key, Val) ->
gen_server:call(Cache, {prepend, Key, Val}, ?DEF_CACHE_TIMEOUT). call(Cache, {prepend, Key, Val}).
%% %%
%% asynchronously add data to existing key before existing data %% asynchronously add data to existing key before existing data
-spec(prepend_/3 :: (cache(), key(), entity()) -> ok). -spec(prepend_/3 :: (cache(), key(), val()) -> reference()).
prepend_(Cache, Key, Val) -> prepend_(Cache, Key, Val) ->
gen_server:cast(Cache, {prepend, Key, Val}). gen_server:cast(Cache, {prepend, Key, Val}).
%% %%
%% synchronous remove entry from cache %% synchronous remove entry from cache
-spec(delete/2 :: (cache(), key()) -> ok). -spec(delete/2 :: (cache(), key()) -> ok).
@ -363,3 +370,22 @@ delete(Cache, Key) ->
delete_(Cache, Key) -> delete_(Cache, Key) ->
cache:remove_(Cache, Key). cache:remove_(Cache, Key).
%%%----------------------------------------------------------------------------
%%%
%%% private
%%%
%%%----------------------------------------------------------------------------
%%
%% synchronous call to server
call(Pid, Req) ->
gen_server:call(Pid, Req, ?CONFIG_TIMEOUT).
%%
%% asynchronous call
cast(Pid, Req) ->
Ref = erlang:make_ref(),
erlang:send(Pid, {'$gen_call', {self(), Ref}, Req}, [noconnect]),
Ref.

View File

@ -33,4 +33,4 @@
-define(DEF_CACHE_CHECK, 20000). -define(DEF_CACHE_CHECK, 20000).
%% default cache i/o timeout %% default cache i/o timeout
-define(DEF_CACHE_TIMEOUT, 60000). -define(CONFIG_TIMEOUT, 30000).

View File

@ -103,83 +103,64 @@ terminate(_Reason, State) ->
%%% %%%
%%%---------------------------------------------------------------------------- %%%----------------------------------------------------------------------------
handle_call({put, Key, Val}, _, S) -> handle_call({put, Key, Val, TTL}, _, State) ->
{reply, ok, cache_put(Key, Val, S)}; {reply, ok, cache_put(Key, Val, TTL, State)};
handle_call({put, Key, Val, TTL}, _, S) -> handle_call({get, Key}, _, State) ->
{reply, ok, cache_put(Key, Val, cache_util:now() + TTL, S)}; {reply, cache_get(Key, State), State};
handle_call({get, Key}, _, S) -> handle_call({lookup, Key}, _, State) ->
{reply, cache_get(Key, S), S}; {reply, cache_lookup(Key, State), State};
handle_call({lookup, Key}, _, S) -> handle_call({has, Key}, _, State) ->
{reply, cache_lookup(Key, S), S}; {reply, cache_has(Key, State), State};
handle_call({has, Key}, _, S) -> handle_call({ttl, Key}, _, State) ->
{reply, cache_has(Key, S), S}; {reply, cache_ttl(Key, State), State};
handle_call({ttl, Key}, _, S) -> handle_call({remove, Key}, _, State) ->
{reply, cache_ttl(Key, S), S}; {reply, ok, cache_remove(Key, State)};
handle_call({remove, Key}, _, S) -> handle_call({acc, Key, Val}, _, State0) ->
{reply, ok, cache_remove(Key, S)}; {Result, State1} = cache_acc(Key, Val, State0),
{reply, Result, State1};
handle_call({acc, Key, Val}, _, S) -> handle_call({add, Key, Val, TTL}, _, State) ->
{Reply, NS} = cache_acc(Key, Val, S), case cache_has(Key, State) of
{reply, Reply, NS};
handle_call({add, Key, Val}, _, S) ->
case cache_has(Key, S) of
true -> true ->
{reply, conflict, S}; {reply, {error, conflict}, State};
false -> false ->
{reply, ok, cache_put(Key, Val, S)} {reply, ok, cache_put(Key, Val, TTL, State)}
end; end;
handle_call({add, Key, Val, TTL}, _, S) -> handle_call({replace, Key, Val, TTL}, _, State) ->
case cache_has(Key, S) of case cache_has(Key, State) of
true -> true ->
{reply, conflict, S}; {reply, ok, cache_put(Key, Val, TTL, State)};
false -> false ->
{reply, ok, cache_put(Key, Val, cache_util:now() + TTL, S)} {reply, {error, not_found}, State}
end; end;
handle_call({replace, Key, Val}, _, S) -> handle_call({prepend, Key, Val}, _, State) ->
case cache_has(Key, S) of
true ->
{reply, ok, cache_put(Key, Val, S)};
false ->
{reply, not_found, S}
end;
handle_call({replace, Key, Val, TTL}, _, S) ->
case cache_has(Key, S) of
true ->
{reply, ok, cache_put(Key, Val, cache_util:now() + TTL, S)};
false ->
{reply, not_found, S}
end;
handle_call({prepend, Key, Val}, _, S) ->
% @todo: reduce one write % @todo: reduce one write
case cache_get(Key, S) of case cache_get(Key, State) of
undefined -> undefined ->
{reply, ok, cache_put(Key, [Val], S)}; {reply, ok, cache_put(Key, [Val], undefined, State)};
X when is_list(X) -> X when is_list(X) ->
{reply, ok, cache_put(Key, [Val|X], S)}; {reply, ok, cache_put(Key, [Val|X], undefined, State)};
X -> X ->
{reply, ok, cache_put(Key, [Val,X], S)} {reply, ok, cache_put(Key, [Val,X], undefined, State)}
end; end;
handle_call({append, Key, Val}, _, S) -> handle_call({append, Key, Val}, _, State) ->
% @todo: reduce one write % @todo: reduce one write
case cache_get(Key, S) of case cache_get(Key, State) of
undefined -> undefined ->
{reply, ok, cache_put(Key, [Val], S)}; {reply, ok, cache_put(Key, [Val], undefined, State)};
X when is_list(X) -> X when is_list(X) ->
{reply, ok, cache_put(Key, X++[Val], S)}; {reply, ok, cache_put(Key, X++[Val], undefined, State)};
X -> X ->
{reply, ok, cache_put(Key, [X, Val], S)} {reply, ok, cache_put(Key, [X, Val], undefined, State)}
end; end;
handle_call(i, _, State) -> handle_call(i, _, State) ->
@ -209,73 +190,6 @@ handle_call(_, _, S) ->
%% %%
%% %%
handle_cast({put, Key, Val}, S) ->
{noreply, cache_put(Key, Val, S)};
handle_cast({put, Key, Val, TTL}, S) ->
{noreply, cache_put(Key, Val, cache_util:now() + TTL, S)};
handle_cast({remove, Key}, S) ->
{noreply, cache_remove(Key, S)};
handle_cast({acc, Key, Val}, S) ->
{_, NS} = cache_acc(Key, Val, S),
{noreply, NS};
handle_cast({add, Key, Val}, S) ->
case cache_has(Key, S) of
true ->
{noreply, S};
false ->
{noreply, cache_put(Key, Val, S)}
end;
handle_cast({add, Key, Val, TTL}, S) ->
case cache_has(Key, S) of
true ->
{noreply, S};
false ->
{noreply, cache_put(Key, Val, cache_util:now() + TTL, S)}
end;
handle_cast({replace, Key, Val}, S) ->
case cache_has(Key, S) of
true ->
{noreply, cache_put(Key, Val, S)};
false ->
{noreply, S}
end;
handle_cast({replace, Key, Val, TTL}, S) ->
case cache_has(Key, S) of
true ->
{noreply, cache_put(Key, Val, cache_util:now() + TTL, S)};
false ->
{noreply, S}
end;
handle_cast({prepend, Key, Val}, S) ->
% @todo: reduce one write
case cache_get(Key, S) of
undefined ->
{noreply, cache_put(Key, [Val], S)};
X when is_list(X) ->
{noreply, cache_put(Key, [Val|X], S)};
X ->
{noreply, cache_put(Key, [Val,X], S)}
end;
handle_cast({append, Key, Val}, S) ->
% @todo: reduce one write
case cache_get(Key, S) of
undefined ->
{noreply, cache_put(Key, [Val], S)};
X when is_list(X) ->
{noreply, cache_put(Key, X++[Val], S)};
X ->
{noreply, cache_put(Key, [X, Val], S)}
end;
handle_cast(_, S) -> handle_cast(_, S) ->
{noreply, S}. {noreply, S}.
@ -309,7 +223,6 @@ handle_info(_, S) ->
code_change(_Vsn, S, _Extra) -> code_change(_Vsn, S, _Extra) ->
{ok, S}. {ok, S}.
%%%---------------------------------------------------------------------------- %%%----------------------------------------------------------------------------
%%% %%%
%%% private %%% private
@ -318,88 +231,83 @@ code_change(_Vsn, S, _Extra) ->
%% %%
%% insert value to cache %% insert value to cache
cache_put(Key, Val, #cache{heap=Heap}=State) -> cache_put(Key, Val, undefined, #cache{name=_Name, heap=Heap}=State) ->
{_, Head} = cache_heap:head(Heap), {_, Head} = cache_heap:head(Heap),
true = ets:insert(Head, {Key, Val}), true = ets:insert(Head, {Key, Val}),
lists:foreach( ok = heap_remove(Key, cache_heap:tail(Heap)),
fun({_, X}) -> ets:delete(X, Key) end, _ = stats(put, State),
cache_heap:tail(Heap) ?DEBUG("cache ~p: put ~p to heap ~p~n", [_Name, Key, Head]),
), State;
cache_util:stats(State#cache.stats, {cache, State#cache.name, put}),
?DEBUG("cache ~p: put ~p to heap ~p~n", [State#cache.name, Key, Head]),
State.
cache_put(Key, Val, Expire, #cache{}=State) -> cache_put(Key, Val, TTL, #cache{name=_Name, heap=Heap}=State) ->
Refs = cache_heap:refs(State#cache.heap), Expire = cache_util:now() + TTL,
Refs = cache_heap:refs(Heap),
case lists:splitwith(fun({X, _}) -> X > Expire end, Refs) of case lists:splitwith(fun({X, _}) -> X > Expire end, Refs) of
{[], _Tail} -> {[], _Tail} ->
cache_put(Key, Val, State); cache_put(Key, Val, undefined, State);
{Head, Tail} -> {Head, Tail} ->
[{_, Heap} | Rest] = lists:reverse(Head), [{_, Inst} | Rest] = lists:reverse(Head),
true = ets:insert(Heap, {Key, Val}), true = ets:insert(Inst, {Key, Val}),
lists:foreach( ok = heap_remove(Key, Rest ++ Tail),
fun({_, X}) -> ets:delete(X, Key) end, _ = stats(put, State),
Rest ++ Tail ?DEBUG("cache ~p: put ~p to heap ~p~n", [_Name, Key, Inst]),
),
cache_util:stats(State#cache.stats, {cache, State#cache.name, put}),
?DEBUG("cache ~p: put ~p to heap ~p~n", [State#cache.name, Key, Heap]),
State State
end. end.
%% %%
%% get cache value %% get cache value
cache_get(Key, #cache{policy=mru}=S) -> cache_get(Key, #cache{policy=mru}=State) ->
% cache MRU should not move key anywhere because % cache MRU should not move key anywhere because
% cache always evicts last generation % cache always evicts last generation
% fall-back to cache lookup % fall-back to cache lookup
cache_lookup(Key, S); cache_lookup(Key, State);
cache_get(Key, #cache{}=S) -> cache_get(Key, #cache{name=_Name, heap=Heap}=State) ->
{_, Head} = cache_heap:head(S#cache.heap), {_, Head} = cache_heap:head(Heap),
case heap_lookup(Key, cache_heap:refs(S#cache.heap)) of case heap_lookup(Key, cache_heap:refs(Heap)) of
undefined -> undefined ->
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}), stats(miss, State),
undefined; undefined;
{Head, Val} -> {Head, Val} ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Head]), ?DEBUG("cache ~p: get ~p at cell ~p~n", [_Name, Key, Head]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}), stats(hit, State),
Val; Val;
{Heap, Val} -> {Cell, Val} ->
true = ets:insert(Head, {Key, Val}), true = ets:insert(Head, {Key, Val}),
_ = ets:delete(Heap, Key), _ = ets:delete(Cell, Key),
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap]), ?DEBUG("cache ~p: get ~p at cell ~p~n", [_Name, Key, Cell]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}), stats(hit, State),
Val Val
end. end.
%% %%
%% lookup cache value %% lookup cache value
cache_lookup(Key, #cache{}=S) -> cache_lookup(Key, #cache{name=_Name, heap=Heap}=State) ->
case heap_lookup(Key, cache_heap:refs(S#cache.heap)) of case heap_lookup(Key, cache_heap:refs(Heap)) of
undefined -> undefined ->
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}), stats(miss, State),
undefined; undefined;
{_Heap, Val} -> {_Cell, Val} ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, _Heap]), ?DEBUG("cache ~p: get ~p at cell ~p~n", [_Name, Key, _Cell]),
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}), stats(hit, State),
Val Val
end. end.
%% %%
%% check if key exists %% check if key exists
cache_has(Key, #cache{}=S) -> cache_has(Key, #cache{name=_Name, heap=Heap}) ->
case heap_has(Key, cache_heap:refs(S#cache.heap)) of case heap_has(Key, cache_heap:refs(Heap)) of
false -> false ->
false; false;
_Heap -> _Heap ->
?DEBUG("cache ~p: has ~p at cell ~p~n", [S#cache.name, Key, _Heap]), ?DEBUG("cache ~p: has ~p at cell ~p~n", [_Name, Key, _Heap]),
true true
end. end.
%% %%
%% check key ttl %% check key ttl
cache_ttl(Key, #cache{}=S) -> cache_ttl(Key,#cache{heap=Heap}) ->
case heap_has(Key, cache_heap:refs(S#cache.heap)) of case heap_has(Key, cache_heap:refs(Heap)) of
false -> false ->
undefined; undefined;
{Expire, _} -> {Expire, _} ->
@ -408,35 +316,32 @@ cache_ttl(Key, #cache{}=S) ->
%% %%
%% %%
cache_remove(Key, #cache{}=S) -> cache_remove(Key, #cache{name=_Name, heap=Heap}=State) ->
lists:foreach( ok = heap_remove(Key, cache_heap:refs(Heap)),
fun({_, X}) -> ets:delete(X, Key) end, _ = stats(remove, State),
cache_heap:refs(S#cache.heap) ?DEBUG("cache ~p: remove ~p~n", [_Name, Key]),
), State.
cache_util:stats(S#cache.stats, {cache, S#cache.name, remove}),
?DEBUG("cache ~p: remove ~p~n", [S#cache.name, Key]),
S.
%% %%
%% @todo: reduce one write %% @todo: reduce one write
cache_acc(Key, Val, S) cache_acc(Key, Val, State)
when is_integer(Val) -> when is_integer(Val) ->
case cache_get(Key, S) of case cache_get(Key, State) of
undefined -> undefined ->
{undefined, cache_put(Key, Val, S)}; {undefined, cache_put(Key, Val, undefined, State)};
X when is_integer(X) -> X when is_integer(X) ->
{X, cache_put(Key, X + Val, S)}; {X, cache_put(Key, X + Val, undefined, State)};
X when is_tuple(X) -> X when is_tuple(X) ->
{erlang:element(1, X), cache_put(Key, tuple_acc({1, Val}, X), S)}; {erlang:element(1, X), cache_put(Key, tuple_acc({1, Val}, X), undefined, State)};
_ -> _ ->
{badarg, S} {badarg, State}
end; end;
cache_acc(Key, Val, S) -> cache_acc(Key, Val, State) ->
case cache_get(Key, S) of case cache_get(Key, State) of
X when is_tuple(X) -> X when is_tuple(X) ->
{X, cache_put(Key, tuple_acc(Val, X), S)}; {X, cache_put(Key, tuple_acc(Val, X), undefined, State)};
_ -> _ ->
{badarg, S} {badarg, State}
end. end.
tuple_acc({Pos, Val}, X) -> tuple_acc({Pos, Val}, X) ->
@ -450,6 +355,15 @@ tuple_acc(List, X) ->
List List
). ).
%%
%% remove key from heap segments
heap_remove(Key, Heap) ->
lists:foreach(
fun({_, Id}) -> ets:delete(Id, Key) end,
Heap
).
%% %%
%% %%
heap_lookup(Key, [{_, Heap} | Tail]) -> heap_lookup(Key, [{_, Heap} | Tail]) ->
@ -472,3 +386,10 @@ heap_has(Key, [{_, Heap}=X | Tail]) ->
heap_has(_Key, []) -> heap_has(_Key, []) ->
false. false.
%%
%% update statistic
stats(_, #cache{stats = undefined}) ->
ok;
stats(X, #cache{stats = Stats, name = Name}) ->
cache_util:stats(Stats, {cache, Name, X}).

View File

@ -44,22 +44,6 @@ cache_interface_test_() ->
] ]
}. }.
% lru_test_() ->
% {
% setup,
% fun cache_init/0,
% fun cache_free/1,
% [
% {"put", fun cache_put/0}
% ,{"has", fun cache_has/0}
% ,{"get", fun cache_get/0}
% ,{"del", fun cache_del/0}
% ,{"acc", fun cache_acc/0}
% ,{"lifecycle 1", {timeout, 10000, fun cache_lc1/0}}
% ]
% }.
%%%---------------------------------------------------------------------------- %%%----------------------------------------------------------------------------
%%% %%%
%%% factory %%% factory
@ -103,8 +87,8 @@ put(Pid) ->
[ [
?_assertMatch(ok, cache:put(Pid, <<"key-1">>, <<"val-1">>)) ?_assertMatch(ok, cache:put(Pid, <<"key-1">>, <<"val-1">>))
,?_assertMatch(ok, cache:put(Pid, <<"key-2">>, <<"val-2">>, 5)) ,?_assertMatch(ok, cache:put(Pid, <<"key-2">>, <<"val-2">>, 5))
,?_assertMatch(ok, cache:put_(Pid, <<"key-3">>, <<"val-3">>)) ,?_assertMatch(ok, async(cache:put_(Pid, <<"key-3">>, <<"val-3">>)))
,?_assertMatch(ok, cache:put_(Pid, <<"key-4">>, <<"val-4">>, 5)) ,?_assertMatch(ok, async(cache:put_(Pid, <<"key-4">>, <<"val-4">>, 5)))
]. ].
get(Pid) -> get(Pid) ->
@ -114,7 +98,7 @@ get(Pid) ->
,?_assertMatch(<<"val-1">>, cache:lookup(Pid, <<"key-1">>)) ,?_assertMatch(<<"val-1">>, cache:lookup(Pid, <<"key-1">>))
,?_assertMatch(true, cache:has(Pid, <<"key-1">>)) ,?_assertMatch(true, cache:has(Pid, <<"key-1">>))
,?_assertMatch(ok, cache:put_(Pid, <<"key-3">>, <<"val-3">>)) ,?_assertMatch(ok, async(cache:put_(Pid, <<"key-3">>, <<"val-3">>)))
,?_assertMatch(<<"val-3">>, cache:get(Pid, <<"key-3">>)) ,?_assertMatch(<<"val-3">>, cache:get(Pid, <<"key-3">>))
,?_assertMatch(<<"val-3">>, cache:lookup(Pid, <<"key-3">>)) ,?_assertMatch(<<"val-3">>, cache:lookup(Pid, <<"key-3">>))
,?_assertMatch(true, cache:has(Pid, <<"key-3">>)) ,?_assertMatch(true, cache:has(Pid, <<"key-3">>))
@ -124,166 +108,8 @@ get(Pid) ->
,?_assertMatch(false, cache:has(Pid, <<"key-5">>)) ,?_assertMatch(false, cache:has(Pid, <<"key-5">>))
]. ].
async(Ref) ->
%% @todo - fix ttl and segment expire time receive
{Ref, X} ->
X
end.
cache_init() ->
cache:start_link(test, ?CACHE).
cache_free({ok, Pid}) ->
erlang:unlink(Pid),
cache:drop(test).
cache_put() ->
ok = cache:put(test, <<"key">>, <<"val">>).
cache_has() ->
true = cache:has(test, <<"key">>),
false = cache:has(test, <<"yek">>).
cache_get() ->
<<"val">> = cache:get(test, <<"key">>),
undefined = cache:get(test, <<"yek">>).
cache_del() ->
ok = cache:remove(test, <<"key">>),
ok = cache:remove(test, <<"yek">>).
cache_acc() ->
undefined = cache:acc(test, <<"acc">>, 10),
10 = cache:acc(test, <<"acc">>, 10),
20 = cache:get(test, <<"acc">>),
badarg = cache:acc(test, <<"acc">>, [{1, 10}]),
badarg = cache:acc(test, <<"acc1">>,[{1, 10}]),
ok = cache:put(test, <<"acc1">>, {10,20,30,40}),
{10, 20, 30, 40} = cache:acc(test, <<"acc1">>,[{1, 10}, {2, 10}]),
{20, 30, 30, 40} = cache:get(test, <<"acc1">>).
cache_lc1() ->
ok = cache:put(test, key, val),
timer:sleep(1200),
val = cache:get(test, key),
timer:sleep(1200),
val = cache:get(test, key),
timer:sleep(1200),
val = cache:get(test, key),
timer:sleep(3200),
undefined = cache:get(test, key).
% lifecyle_2_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {ttl, 10},
% {evict, 100}
% ]),
% ok = cache:put(test, key, val),
% timer:sleep(6),
% {ok, val} = cache:get(test, key),
% timer:sleep(6),
% {ok, val} = cache:get(test, key),
% timer:sleep(20),
% none = cache:get(test, key),
% cache:stop(test).
% lifecyle_3_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {ttl, 10},
% {evict, 5}
% ]),
% ok = cache:put(test, key1, val1),
% timer:sleep(5),
% ok = cache:put(test, key2, val2),
% timer:sleep(5),
% ok = cache:put(test, key3, val3),
% timer:sleep(5),
% ok = cache:put(test, key4, val4),
% none = cache:get(test, key1),
% none = cache:get(test, key2),
% {ok, val3} = cache:get(test, key3),
% {ok, val4} = cache:get(test, key4),
% cache:stop(test).
% evict_lru_1_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, lru},
% {ttl, 100},
% {evict, 5},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% timer:sleep(10),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% timer:sleep(10),
% none = cache:get(test, 2),
% cache:stop(test).
% evict_lru_2_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, lru},
% {ttl, 100},
% {evict, 100},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% cache:evict(test),
% none = cache:get(test, 2),
% cache:stop(test).
% evict_mru_1_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, mru},
% {ttl, 100},
% {evict, 5},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% timer:sleep(10),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% timer:sleep(10),
% none = cache:get(test, key),
% cache:stop(test).
% evict_mru_2_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, mru},
% {ttl, 100},
% {evict, 100},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% cache:evict(test),
% none = cache:get(test, key),
% cache:stop(test).