diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 350ec2e..5db114f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,15 +17,15 @@ jobs:
fail-fast: false
matrix:
include:
- - otp: '25.x'
+ - otp: '26.x'
os: 'ubuntu-latest'
coverage: true
xref: true
dialyzer: true
+ - otp: '25.x'
+ os: 'ubuntu-latest'
- otp: '24.x'
os: 'ubuntu-latest'
- - otp: '23.x'
- os: 'ubuntu-20.04'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/src/shards.erl b/src/shards.erl
index add6b04..faa83b3 100644
--- a/src/shards.erl
+++ b/src/shards.erl
@@ -144,7 +144,8 @@
get_meta/2,
get_meta/3,
put_meta/3,
- partition_owners/1
+ partition_owners/1,
+ with_meta_cache/2
]).
%% Inline-compiled functions
@@ -286,7 +287,7 @@ get_meta(Tab, Key, Def) ->
%% @doc Wrapper for `shards_meta:put/3'.
-spec put_meta(Tab, Key, Val) -> ok when
- Tab :: shards:tab(),
+ Tab :: tab(),
Key :: term(),
Val :: term().
put_meta(Tab, Key, Val) ->
@@ -297,9 +298,51 @@ put_meta(Tab, Key, Val) ->
TabOrPid :: pid() | tab(),
OwnerPid :: pid().
partition_owners(TabOrPid) when is_pid(TabOrPid) ->
- [Child || {_, Child, _, _} <- supervisor:which_children(TabOrPid)];
+ [Child || {_, Child, _, [shards_partition]} <- supervisor:which_children(TabOrPid)];
partition_owners(TabOrPid) when is_atom(TabOrPid); is_reference(TabOrPid) ->
- partition_owners(shards_meta:tab_pid(TabOrPid)).
+ partition_owners(shards_meta:get_owner(TabOrPid)).
+
+%% @doc
+%% Executes the given `Fun' by providing the table metadata retrieved from the
+%% cache or the meta table. In the last case, the metadata is stored in the
+%% cache and can be fetched from there on upcoming calls.
+%%
+%%
Examples:
+%%
+%% ```
+%% shards:with_meta_cache(Tab, fun(Meta) ->
+%% true = shards:insert(Tab, {foo, bar}, Meta)
+%% end)
+%% '''
+%%
+%% The metadata will be in the cache from this moment on.
+%%
+%% See the "Metadata Cache" section in the module docs for more information.
+%% @end
+-spec with_meta_cache(Tab, Fun) -> Res when
+ Tab :: tab(),
+ Fun :: fun((shards_meta:t()) -> term()),
+ Res :: term().
+with_meta_cache(Tab, Fun) ->
+ case shards_meta_cache:get_meta(Tab) of
+ undefined ->
+ % Get the metadata
+ Meta = shards_meta:get(Tab),
+
+ % Execute the given function with the retrieved metadata
+ Result = Fun(Meta),
+
+ % The function was executed successfully, hence, cache the metadata
+ ok = shards_meta_cache:put_meta(Tab, Meta),
+
+ % Finally, return the function's result
+ Result;
+
+ Meta ->
+ % The metadata is already in the cache, call the function with
+ % the returned value
+ Fun(Meta)
+ end.
%%%===================================================================
%%% ETS API
@@ -316,8 +359,7 @@ all() ->
%% @end
-spec delete(Tab :: tab()) -> true.
delete(Tab) ->
- Meta = shards_meta:get(Tab),
- TabPid = shards_meta:tab_pid(Meta),
+ TabPid = shards_meta:get_owner(Tab),
ok = shards_partition_sup:stop(TabPid),
true.
@@ -335,8 +377,8 @@ delete(Tab, Key) ->
Key :: term(),
Meta :: shards_meta:t().
delete(Tab, Key, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:delete(PartTid, Key).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:delete(PartTab, Key).
%% @equiv delete_all_objects(Tab, shards_meta:get(Tab))
delete_all_objects(Tab) ->
@@ -351,7 +393,7 @@ delete_all_objects(Tab) ->
Tab :: tab(),
Meta :: shards_meta:t().
delete_all_objects(Tab, Meta) ->
- _ = mapred(Tab, fun ets:delete_all_objects/1, Meta),
+ _ = map_reduce(Tab, fun ets:delete_all_objects/1, Meta),
true.
%% @equiv delete_object(Tab, Object, shards_meta:get(Tab))
@@ -369,8 +411,8 @@ delete_object(Tab, Object) ->
Meta :: shards_meta:t().
delete_object(Tab, Object, Meta) when is_tuple(Object) ->
Key = shards_lib:object_key(Object, Meta),
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:delete_object(PartTid, Object).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:delete_object(PartTab, Object).
%% @equiv file2tab(Filename, [])
file2tab(Filename) ->
@@ -397,15 +439,15 @@ file2tab(Filename, Options) when ?is_filename(Filename) ->
TabName = maps:get(name, Header),
Meta = maps:get(metadata, Header),
Partitions = maps:get(partitions, Header),
+ EtsOpts = maps:get(ets_opts, Header),
TabOpts = [
{partitions, shards_meta:partitions(Meta)},
{keyslot_fun, shards_meta:keyslot_fun(Meta)}
- | shards_meta:ets_opts(Meta)
+ | EtsOpts
],
- Return = new(TabName, [{restore, Partitions, Options} | TabOpts]),
- {ok, Return}
+ {ok, new(TabName, [{restore, Partitions, Options} | TabOpts])}
catch
throw:Error ->
Error;
@@ -436,12 +478,12 @@ first(Tab) ->
first(Tab, Meta) ->
N = shards_meta:partitions(Meta),
Partition = N - 1,
- first(Tab, ets:first(shards_partition:tid(Tab, Partition)), Partition).
+ first(Tab, ets:first(shards_partition:table(Tab, Partition)), Partition).
%% @private
first(Tab, '$end_of_table', Partition) when Partition > 0 ->
NextPartition = Partition - 1,
- first(Tab, ets:first(shards_partition:tid(Tab, NextPartition)), NextPartition);
+ first(Tab, ets:first(shards_partition:table(Tab, NextPartition)), NextPartition);
first(_, '$end_of_table', _) ->
'$end_of_table';
first(_, Key, _) ->
@@ -495,7 +537,7 @@ foldr(Fun, Acc, Tab, Meta) ->
%% @private
fold(Fold, Fun, Acc, Tab, Partition) when Partition >= 0 ->
- NewAcc = ets:Fold(Fun, Acc, shards_partition:tid(Tab, Partition)),
+ NewAcc = ets:Fold(Fun, Acc, shards_partition:table(Tab, Partition)),
fold(Fold, Fun, NewAcc, Tab, Partition - 1);
fold(_Fold, _Fun, Acc, _Tab, _Partition) ->
Acc.
@@ -551,7 +593,7 @@ info(Tab, Item) when is_atom(Item) ->
%% @private
do_info(Tab, Meta) ->
- InfoLists = mapred(Tab, fun ets:info/1, Meta),
+ InfoLists = map_reduce(Tab, fun ets:info/1, Meta),
[
{partitions, shards_meta:partitions(Meta)},
@@ -585,7 +627,7 @@ insert(Tab, ObjOrObjs, Meta) when is_list(ObjOrObjs) ->
Acc = ets:insert(Partition, Group)
end, true, group_keys_by_partition(Tab, ObjOrObjs, Meta));
insert(Tab, ObjOrObjs, Meta) when is_tuple(ObjOrObjs) ->
- ets:insert(get_part_tid(Tab, ObjOrObjs, Meta), ObjOrObjs).
+ ets:insert(get_part_table(Tab, ObjOrObjs, Meta), ObjOrObjs).
%% @equiv insert_new(Tab, ObjOrObjs, shards_meta:get(Tab))
insert_new(Tab, ObjOrObjs) ->
@@ -639,7 +681,7 @@ insert_new(Tab, ObjOrObjs, Meta) when is_list(ObjOrObjs) ->
_ -> true
end;
insert_new(Tab, ObjOrObjs, Meta) when is_tuple(ObjOrObjs) ->
- ets:insert_new(get_part_tid(Tab, ObjOrObjs, Meta), ObjOrObjs).
+ ets:insert_new(get_part_table(Tab, ObjOrObjs, Meta), ObjOrObjs).
%% @private
rollback_insert(Tab, Entries, Meta) ->
@@ -664,7 +706,7 @@ is_compiled_ms(Term) ->
Tab :: tab(),
Key :: term().
last(Tab) ->
- Partition0 = shards_partition:tid(Tab, 0),
+ Partition0 = shards_partition:table(Tab, 0),
case ets:info(Partition0, type) of
ordered_set -> ets:last(Partition0);
@@ -686,8 +728,8 @@ lookup(Tab, Key) ->
Meta :: shards_meta:t(),
Object :: tuple().
lookup(Tab, Key, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:lookup(PartTid, Key).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:lookup(PartTab, Key).
%% @equiv lookup_element(Tab, Key, Pos, shards_meta:get(Tab))
lookup_element(Tab, Key, Pos) ->
@@ -705,8 +747,8 @@ lookup_element(Tab, Key, Pos) ->
Meta :: shards_meta:t(),
Elem :: term() | [term()].
lookup_element(Tab, Key, Pos, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:lookup_element(PartTid, Key, Pos).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:lookup_element(PartTab, Key, Pos).
%% @equiv match(Tab, Pattern, shards_meta:get(Tab))
match(Tab, Pattern) ->
@@ -735,7 +777,7 @@ match(Tab, Pattern, Limit) when is_integer(Limit), Limit > 0 ->
match(Tab, Pattern, Meta) ->
Map = {fun ets:match/2, [Pattern]},
Reduce = fun erlang:'++'/2,
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @doc
%% Equivalent to `ets:match/3'.
@@ -786,7 +828,7 @@ match_delete(Tab, Pattern) ->
match_delete(Tab, Pattern, Meta) ->
Map = {fun ets:match_delete/2, [Pattern]},
Reduce = {fun erlang:'and'/2, true},
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @equiv match_object(Tab, Pattern, shards_meta:get(Tab))
match_object(Tab, Pattern) ->
@@ -814,7 +856,7 @@ match_object(Tab, Pattern, Limit) when is_integer(Limit), Limit > 0 ->
match_object(Tab, Pattern, Meta) ->
Map = {fun ets:match_object/2, [Pattern]},
Reduce = fun erlang:'++'/2,
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @doc
%% Equivalent to `ets:match_object/3'.
@@ -871,8 +913,8 @@ member(Tab, Key) ->
Key :: term(),
Meta :: shards_meta:t().
member(Tab, Key, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:member(PartTid, Key).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:member(PartTab, Key).
%% @doc
%% This operation is equivalent to `ets:new/2', but when is called,
@@ -904,13 +946,18 @@ member(Tab, Key, Meta) ->
%%
%%
%% `{parallel, P}' - Specifies whether `shards' should work in parallel mode
-%% or not, for the applicable functions, e.g.: `select', `match', etc. By
-%% default is set to `false'.
+%% or not, for the applicable functions, e.g.: `select', `match', etc.
+%% Defaults to `false'.
%%
%%
%% `{parallel_timeout, T}' - When `parallel' is set to `true', it specifies
%% the max timeout for a parallel execution. Defaults to `infinity'.
%%
+%%
+%% `{cache, Bool}' - Specifies whether `shards' should use the metadata cache
+%% (`persistent_term') for storing the table metadata. See the "Metadata Cache"
+%% section below. Defaults to `false'.
+%%
%%
%%
%% Access:
@@ -937,7 +984,7 @@ member(Tab, Key, Meta) ->
%%
%% @see ets:new/2.
%% @end
--spec new(Name, Options) -> Tab when
+-spec new(Name, Options) -> Tab | no_return() when
Name :: atom(),
Options :: [option()],
Tab :: tab().
@@ -945,13 +992,20 @@ new(Name, Options) ->
with_trap_exit(fun() ->
ParsedOpts = shards_opts:parse(Options),
StartResult = shards_partition_sup:start_link(Name, ParsedOpts),
+
do_new(StartResult, Name, ParsedOpts)
end).
%% @private
do_new({ok, Pid}, Name, Options) ->
+ % If it is a named table register it
ok = maybe_register(Name, Pid, maps:get(ets_opts, Options)),
- shards_partition:retrieve_tab(shards_lib:get_sup_child(Pid, 0));
+
+ % Wait for the message indicating the partitioned table was created
+ % successfully
+ receive
+ {reply, Pid, Tab} -> Tab
+ end;
do_new({error, {shutdown, {_, _, {restore_error, Error}}}}, _Name, _Options) ->
ok = wrap_exit(),
error(Error);
@@ -970,6 +1024,15 @@ maybe_register(Name, Pid, Options) ->
ok
end.
+-ifndef(OTP_RELEASE).
+%% OTP 20 or lower.
+-define(OTP_RELEASE, 20).
+-endif.
+
+-if(?OTP_RELEASE >= 26).
+%% @private
+wrap_exit() -> ok.
+-else.
%% @private
wrap_exit() ->
% We wait for the 'EXIT' signal from the partition supervisor knowing
@@ -978,6 +1041,7 @@ wrap_exit() ->
receive
{'EXIT', _Pid, _Reason} -> ok
end.
+-endif.
%% @equiv next(Tab, Key1, shards_meta:get(Tab))
next(Tab, Key1) ->
@@ -1000,13 +1064,13 @@ next(Tab, Key1, Meta) ->
KeyslotFun = shards_meta:keyslot_fun(Meta),
Partitions = shards_meta:partitions(Meta),
Idx = KeyslotFun(Key1, Partitions),
- PartTid = shards_partition:tid(Tab, Idx),
- next_(Tab, ets:next(PartTid, Key1), Idx).
+ PartTab = shards_partition:table(Tab, Idx),
+ next_(Tab, ets:next(PartTab, Key1), Idx).
%% @private
next_(Tab, '$end_of_table', Partition) when Partition > 0 ->
NextPartition = Partition - 1,
- next_(Tab, ets:first(shards_partition:tid(Tab, NextPartition)), NextPartition);
+ next_(Tab, ets:first(shards_partition:table(Tab, NextPartition)), NextPartition);
next_(_, '$end_of_table', _) ->
'$end_of_table';
next_(_, Key2, _) ->
@@ -1025,7 +1089,7 @@ next_(_, Key2, _) ->
Key1 :: term(),
Key2 :: term().
prev(Tab, Key1) ->
- Partition0 = shards_partition:tid(Tab, 0),
+ Partition0 = shards_partition:table(Tab, 0),
case ets:info(Partition0, type) of
ordered_set -> ets:prev(Partition0, Key1);
@@ -1034,7 +1098,7 @@ prev(Tab, Key1) ->
%% @equiv rename(Tab, Name, shards_meta:get(Tab))
rename(Tab, Name) ->
- rename(Tab, Name, shards_meta:get(Tab)).
+ rename(Tab, Name, shards_meta:get_owner(Tab)).
%% @doc
%% Equivalent to `ets:rename/2'.
@@ -1045,15 +1109,18 @@ rename(Tab, Name) ->
%%
%% @see ets:rename/2.
%% @end
--spec rename(Tab, Name, Meta) -> Name when
- Tab :: tab(),
- Name :: atom(),
- Meta :: shards_meta:t().
-rename(Tab, Name, Meta) ->
- Pid = shards_meta:tab_pid(Meta),
- true = unregister(Tab),
- true = register(Name, Pid),
- Name = shards_meta:rename(Tab, Name).
+-spec rename(Tab, Name, TabPid) -> Name when
+ Tab :: tab(),
+ Name :: atom(),
+ TabPid :: pid().
+rename(Tab, Name, TabPid) when is_pid(TabPid) ->
+ try
+ true = unregister(Tab),
+ true = register(Name, TabPid),
+ Name = shards_meta:rename(Tab, Name)
+ after
+ ok = shards_meta_cache:del_meta(Tab)
+ end.
%% @equiv safe_fixtable(Tab, Fix, shards_meta:get(Tab))
safe_fixtable(Tab, Fix) ->
@@ -1074,7 +1141,7 @@ safe_fixtable(Tab, Fix) ->
safe_fixtable(Tab, Fix, Meta) ->
Map = {fun ets:safe_fixtable/2, [Fix]},
Reduce = {fun erlang:'and'/2, true},
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @equiv select(Tab, MatchSpec, shards_meta:get(Tab))
select(Tab, MatchSpec) ->
@@ -1102,7 +1169,7 @@ select(Tab, MatchSpec, Limit) when is_integer(Limit) ->
select(Tab, MatchSpec, Meta) ->
Map = {fun ets:select/2, [MatchSpec]},
Reduce = fun erlang:'++'/2,
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @doc
%% Equivalent to `ets:select/3'.
@@ -1154,7 +1221,7 @@ select_count(Tab, MatchSpec) ->
select_count(Tab, MatchSpec, Meta) ->
Map = {fun ets:select_count/2, [MatchSpec]},
Reduce = {fun erlang:'+'/2, 0},
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @equiv select_delete(Tab, MatchSpec, shards_meta:get(Tab))
select_delete(Tab, MatchSpec) ->
@@ -1173,7 +1240,7 @@ select_delete(Tab, MatchSpec) ->
select_delete(Tab, MatchSpec, Meta) ->
Map = {fun ets:select_delete/2, [MatchSpec]},
Reduce = {fun erlang:'+'/2, 0},
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @equiv select_replace(Tab, MatchSpec, shards_meta:get(Tab))
select_replace(Tab, MatchSpec) ->
@@ -1192,7 +1259,7 @@ select_replace(Tab, MatchSpec) ->
select_replace(Tab, MatchSpec, Meta) ->
Map = {fun ets:select_replace/2, [MatchSpec]},
Reduce = {fun erlang:'+'/2, 0},
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @equiv select_reverse(Tab, MatchSpec, shards_meta:get(Tab))
select_reverse(Tab, MatchSpec) ->
@@ -1220,7 +1287,7 @@ select_reverse(Tab, MatchSpec, Limit) when is_integer(Limit) ->
select_reverse(Tab, MatchSpec, Meta) ->
Map = {fun ets:select_reverse/2, [MatchSpec]},
Reduce = fun erlang:'++'/2,
- mapred(Tab, Map, Reduce, Meta).
+ map_reduce(Tab, Map, Reduce, Meta).
%% @doc
%% Equivalent to `ets:select_reverse/3'.
@@ -1276,7 +1343,7 @@ setopts(Tab, Opts) ->
setopts(Tab, Opts, Meta) ->
Map = {fun shards_partition:apply_ets_fun/3, [setopts, [Opts]]},
Reduce = {fun erlang:'and'/2, true},
- mapred(Tab, Map, Reduce, {pid, Meta}).
+ map_reduce(Tab, Map, Reduce, {pid, Meta}).
%% @equiv tab2file(Tab, Filename, [])
tab2file(Tab, Filename) ->
@@ -1313,14 +1380,14 @@ tab2file(Tab, Filename, Options) when ?is_filename(Filename) ->
{error, _} = Error ->
{halt, Error}
end
- end, #{}, shards_meta:get_partition_tids(Tab)),
+ end, #{}, shards_meta:get_partition_tables(Tab)),
case PartitionFilenamePairs of
{error, _} = Error ->
Error;
_ ->
- TabInfo = maps:from_list(shards:info(Tab)),
+ TabInfo = maps:from_list(?MODULE:info(Tab)),
Header = #{
name => maps:get(name, TabInfo),
@@ -1330,6 +1397,7 @@ tab2file(Tab, Filename, Options) when ?is_filename(Filename) ->
size => maps:get(size, TabInfo),
named_table => maps:get(named_table, TabInfo),
extended_info => maps:get(extended_info, TabInfo, []),
+ ets_opts => shards_meta:get_ets_opts(Tab),
metadata => Metadata,
partitions => PartitionFilenamePairs
},
@@ -1351,7 +1419,7 @@ tab2list(Tab) ->
Meta :: shards_meta:t(),
Object :: tuple().
tab2list(Tab, Meta) ->
- mapred(Tab, fun ets:tab2list/1, fun erlang:'++'/2, Meta).
+ map_reduce(Tab, fun ets:tab2list/1, fun erlang:'++'/2, Meta).
%% @doc
%% Equivalent to `ets:tabfile_info/1'.
@@ -1370,8 +1438,8 @@ tabfile_info(Filename) when ?is_filename(Filename) ->
Header = shards_lib:read_tabfile(StrFilename),
TabName = maps:get(name, Header),
- Metadata = maps:get(metadata, Header),
- NamedTable = lists:member(named_table, shards_meta:ets_opts(Metadata)),
+ EtsOpts = maps:get(ets_opts, Header),
+ NamedTable = lists:member(named_table, EtsOpts),
Partitions = maps:get(partitions, Header),
ShardsTabInfo =
@@ -1422,7 +1490,7 @@ table(Tab, Options) ->
MatchSpec :: ets:match_spec(),
TraverseMethod :: first_next | last_prev | select | {select, MatchSpec}.
table(Tab, Options, Meta) ->
- mapred(Tab, {fun ets:table/2, [Options]}, Meta).
+ map_reduce(Tab, {fun ets:table/2, [Options]}, Meta).
%% @equiv ets:test_ms(Tuple, MatchSpec)
test_ms(Tuple, MatchSpec) ->
@@ -1443,8 +1511,8 @@ take(Tab, Key) ->
Meta :: shards_meta:t(),
Object :: tuple().
take(Tab, Key, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:take(PartTid, Key).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:take(PartTab, Key).
%% @equiv update_counter(Tab, Key, UpdateOp, shards_meta:get(Tab))
update_counter(Tab, Key, UpdateOp) ->
@@ -1467,8 +1535,8 @@ update_counter(Tab, Key, UpdateOp) ->
update_counter(Tab, Key, UpdateOp, DefaultOrMeta) ->
case shards_meta:is_metadata(DefaultOrMeta) of
true ->
- PartTid = shards_partition:tid(Tab, Key, DefaultOrMeta),
- ets:update_counter(PartTid, Key, UpdateOp);
+ PartTab = shards_partition:table(Tab, Key, DefaultOrMeta),
+ ets:update_counter(PartTab, Key, UpdateOp);
false ->
update_counter(Tab, Key, UpdateOp, DefaultOrMeta, shards_meta:get(Tab))
@@ -1487,8 +1555,8 @@ update_counter(Tab, Key, UpdateOp, DefaultOrMeta) ->
Meta :: shards_meta:t(),
Result :: integer().
update_counter(Tab, Key, UpdateOp, Default, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:update_counter(PartTid, Key, UpdateOp, Default).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:update_counter(PartTab, Key, UpdateOp, Default).
%% @equiv update_element(Tab, Key, ElementSpec, shards_meta:get(Tab))
update_element(Tab, Key, ElementSpec) ->
@@ -1502,13 +1570,13 @@ update_element(Tab, Key, ElementSpec) ->
-spec update_element(Tab, Key, ElementSpec, Meta) -> boolean() when
Tab :: tab(),
Key :: term(),
- ElementSpec :: {Pos, Value} | [{Pos, Value}],
Pos :: pos_integer(),
Value :: term(),
+ ElementSpec :: {Pos, Value} | [{Pos, Value}],
Meta :: shards_meta:t().
update_element(Tab, Key, ElementSpec, Meta) ->
- PartTid = shards_partition:tid(Tab, Key, Meta),
- ets:update_element(PartTid, Key, ElementSpec).
+ PartTab = shards_partition:table(Tab, Key, Meta),
+ ets:update_element(PartTab, Key, ElementSpec).
%%%===================================================================
%%% Internal functions
@@ -1533,15 +1601,15 @@ with_meta(Tab, Fun) ->
end.
%% @private
-get_part_tid(Tab, Object, Meta) ->
+get_part_table(Tab, Object, Meta) ->
Key = shards_lib:object_key(Object, Meta),
- shards_partition:tid(Tab, Key, Meta).
+ shards_partition:table(Tab, Key, Meta).
%% @private
group_keys_by_partition(Tab, Objects, Meta) ->
lists:foldr(fun(Object, Acc) ->
- PartTid = get_part_tid(Tab, Object, Meta),
- Acc#{PartTid => [Object | maps:get(PartTid, Acc, [])]}
+ PartTab = get_part_table(Tab, Object, Meta),
+ Acc#{PartTab => [Object | maps:get(PartTab, Acc, [])]}
end, #{}, Objects).
%% @private
@@ -1565,41 +1633,41 @@ parts_info(Tab, [FirstInfo | RestInfoLists], ExtraAttrs) ->
end, FirstInfo1, RestInfoLists).
%% @private
-mapred(Tab, Map, Meta) ->
- mapred(Tab, Map, nil, Meta).
+map_reduce(Tab, Map, Meta) ->
+ map_reduce(Tab, Map, nil, Meta).
%% @private
-mapred(Tab, Map, nil, Meta) ->
- mapred(Tab, Map, fun(E, Acc) -> [E | Acc] end, Meta);
-mapred(Tab, Map, Reduce, {PartitionFun, Meta}) ->
- do_mapred(Tab, Map, Reduce, PartitionFun, Meta);
-mapred(Tab, Map, Reduce, Meta) ->
- do_mapred(Tab, Map, Reduce, tid, Meta).
+map_reduce(Tab, Map, nil, Meta) ->
+ map_reduce(Tab, Map, fun(E, Acc) -> [E | Acc] end, Meta);
+map_reduce(Tab, Map, Reduce, {PartitionFun, Meta}) ->
+ do_map_reduce(Tab, Map, Reduce, PartitionFun, Meta);
+map_reduce(Tab, Map, Reduce, Meta) ->
+ do_map_reduce(Tab, Map, Reduce, table, Meta).
%% @private
-do_mapred(Tab, Map, Reduce, PartFun, Meta) ->
+do_map_reduce(Tab, Map, Reduce, PartFun, Meta) ->
case {shards_meta:partitions(Meta), shards_meta:parallel(Meta)} of
{Partitions, true} when Partitions > 1 ->
ParallelTimeout = shards_meta:parallel_timeout(Meta),
- p_mapred(Tab, Map, Reduce, PartFun, Partitions, ParallelTimeout);
+ p_map_reduce(Tab, Map, Reduce, PartFun, Partitions, ParallelTimeout);
{Partitions, false} ->
- s_mapred(Tab, Map, Reduce, PartFun, Partitions)
+ s_map_reduce(Tab, Map, Reduce, PartFun, Partitions)
end.
%% @private
-s_mapred(Tab, {MapFun, Args}, {ReduceFun, AccIn}, PartFun, Partitions) ->
+s_map_reduce(Tab, {MapFun, Args}, {ReduceFun, AccIn}, PartFun, Partitions) ->
shards_enum:reduce(fun(Part, Acc) ->
PartitionId = shards_partition:PartFun(Tab, Part),
MapRes = apply(MapFun, [PartitionId | Args]),
ReduceFun(MapRes, Acc)
end, AccIn, Partitions);
-s_mapred(Tab, MapFun, ReduceFun, PartFun, Partitions) ->
- {Map, Reduce} = mapred_funs(MapFun, ReduceFun),
- s_mapred(Tab, Map, Reduce, PartFun, Partitions).
+s_map_reduce(Tab, MapFun, ReduceFun, PartFun, Partitions) ->
+ {Map, Reduce} = map_reduce_funs(MapFun, ReduceFun),
+ s_map_reduce(Tab, Map, Reduce, PartFun, Partitions).
%% @private
-p_mapred(Tab, {MapFun, Args}, {ReduceFun, AccIn}, PartFun, Partitions, ParallelTimeout) ->
+p_map_reduce(Tab, {MapFun, Args}, {ReduceFun, AccIn}, PartFun, Partitions, ParallelTimeout) ->
MapResults =
shards_enum:pmap(fun(Idx) ->
PartitionId = shards_partition:PartFun(Tab, Idx),
@@ -1607,12 +1675,12 @@ p_mapred(Tab, {MapFun, Args}, {ReduceFun, AccIn}, PartFun, Partitions, ParallelT
end, ParallelTimeout, lists:seq(0, Partitions - 1)),
lists:foldl(ReduceFun, AccIn, MapResults);
-p_mapred(Tab, MapFun, ReduceFun, PartFun, Partitions, ParallelTimeout) ->
- {Map, Reduce} = mapred_funs(MapFun, ReduceFun),
- p_mapred(Tab, Map, Reduce, PartFun, Partitions, ParallelTimeout).
+p_map_reduce(Tab, MapFun, ReduceFun, PartFun, Partitions, ParallelTimeout) ->
+ {Map, Reduce} = map_reduce_funs(MapFun, ReduceFun),
+ p_map_reduce(Tab, Map, Reduce, PartFun, Partitions, ParallelTimeout).
%% @private
-mapred_funs(MapFun, ReduceFun) ->
+map_reduce_funs(MapFun, ReduceFun) ->
Map =
case is_function(MapFun) of
true -> {MapFun, []};
@@ -1631,7 +1699,7 @@ q(_, Tab, MatchSpec, Limit, _, _, Shard, {Acc, _}) when Shard < 0 ->
q(F, Tab, MatchSpec, Limit, QFun, I, Shard, {Acc, '$end_of_table'}) ->
q(F, Tab, MatchSpec, Limit, QFun, I, Shard - 1, {Acc, nil});
q(F, Tab, MatchSpec, Limit, QFun, I, Shard, {Acc, _}) ->
- case ets:F(shards_partition:tid(Tab, Shard), MatchSpec, I) of
+ case ets:F(shards_partition:table(Tab, Shard), MatchSpec, I) of
{L, Cont} ->
NewAcc = {QFun(L, Acc), Cont},
q(F, Tab, MatchSpec, Limit, QFun, I - length(L), Shard, NewAcc);
diff --git a/src/shards_group.erl b/src/shards_group.erl
index b973642..35ff71a 100644
--- a/src/shards_group.erl
+++ b/src/shards_group.erl
@@ -1,13 +1,13 @@
%%%-------------------------------------------------------------------
%%% @doc
-%%% This module provides a dynamic supervisor for creating and/or
-%%% deleting tables dynamically in runtime and as part of an existing
-%%% supervision tree; any application supervision tree using `shards'.
+%%% This module provides a feature for partitioned table groups.
+%%% A group is represented by a dynamic supervisor so that the
+%%% tables can be added or removed dynamically at runtime.
%%%
%%% Usage
-%%% To use `shards' and make the creates tables part of your
-%%% application supervision tree, you have to add to your main
-%%% supervisor:
+%%% To use `shards_group' and make the created tables part of
+%%% your application supervision tree, you have to add the
+%%% group supervisor to the children list:
%%%
%%% ```
%%% % Supervisor init callback
@@ -58,6 +58,7 @@
start_link() ->
start_link(?MODULE).
+%% @doc Starts a new group with the name `Name'.
-spec start_link(Name) -> StartRet when
Name :: atom() | undefined,
StartRet :: {ok, pid()} | {error, term()}.
@@ -70,12 +71,14 @@ start_link(Name) ->
stop(Pid) ->
stop(Pid, 5000).
+%% @doc Stops the group given by `SupRef'.
-spec stop(SupRef, Timeout) -> ok when
SupRef :: atom() | pid(),
Timeout :: timeout().
stop(SupRef, Timeout) ->
gen_server:stop(SupRef, normal, Timeout).
+%% @doc Builds a child specification with the given `Name'.
-spec child_spec(Name) -> ChildSpec when
Name :: atom() | undefined,
ChildSpec :: supervisor:child_spec().
@@ -86,6 +89,10 @@ child_spec(Name) ->
type => supervisor
}.
+%% @doc
+%% Dynamically creates a new partitioned table named `TabName' and adds that
+%% table to the supervisor `SupRef'.
+%% @end
-spec new_table(SupRef, TabName, Options) -> {ok, TabPid, Tab} | {error, Reason} when
SupRef :: atom() | pid(),
TabName :: atom(),
@@ -96,19 +103,23 @@ child_spec(Name) ->
new_table(SupRef, TabName, Options) when is_atom(SupRef); is_pid(SupRef) ->
supervisor:start_child(SupRef, [TabName, Options]).
+%% @doc
+%% Deletes the given table `Tab` and removes it from the supervisor `SupRef'.
+%% @end
-spec del_table(SupRef, Tab) -> ok | {error, Reason} when
SupRef :: atom(),
Tab :: shards:tab(),
Reason :: not_found | simple_one_for_one.
del_table(SupRef, Tab) when is_atom(SupRef); is_pid(SupRef) ->
- TabPid = shards_meta:tab_pid(Tab),
+ TabPid = shards_meta:get_owner(Tab),
supervisor:terminate_child(SupRef, TabPid).
%% Helper for starting the table
%% @hidden
start_table(Name, Opts) ->
Tab = shards:new(Name, Opts),
- Pid = shards_meta:tab_pid(Tab),
+ Pid = shards_meta:get_owner(Tab),
+
{ok, Pid, Tab}.
%%%===================================================================
@@ -118,6 +129,7 @@ start_table(Name, Opts) ->
%% @hidden
init({Name}) ->
ChildSpec = partitioned_table_spec(Name),
+
{ok, {{simple_one_for_one, 10, 10}, [ChildSpec]}}.
%%%===================================================================
@@ -127,7 +139,7 @@ init({Name}) ->
%% @private
partitioned_table_spec(Name) ->
#{
- id => Name,
+ id => {?MODULE, Name},
start => {?MODULE, start_table, []},
type => supervisor,
restart => permanent,
diff --git a/src/shards_lib.erl b/src/shards_lib.erl
index 5e4752f..2718485 100644
--- a/src/shards_lib.erl
+++ b/src/shards_lib.erl
@@ -7,8 +7,8 @@
%% API
-export([
+ partition_name/2,
object_key/2,
- get_sup_child/2,
keyfind/2,
keyfind/3,
keyupdate/3,
@@ -20,6 +20,7 @@
write_tabfile/2
]).
+%% Defines a key/value tuple list.
-type kv_list() :: [{term(), term()}].
-export_type([kv_list/0]).
@@ -28,6 +29,22 @@
%%% API
%%%===================================================================
+%% @doc
+%% Builds the partition name for the given table `Tab'
+%% and partition `Partition'.
+%%
+%% - `TabName': Table name from which the shard name is generated.
+%% - `Partition': Partition number – from `0' to `(NumShards - 1)'
+%%
+%% @end
+-spec partition_name(Tab, Partition) -> PartitionName when
+ Tab :: atom(),
+ Partition :: non_neg_integer(),
+ PartitionName :: atom().
+partition_name(Tab, Partition) ->
+ Bin = <<(atom_to_binary(Tab, utf8))/binary, ".ptn", (integer_to_binary(Partition))/binary>>,
+ binary_to_atom(Bin, utf8).
+
%% @doc
%% Returns the key for the given object or list of objects.
%% @end
@@ -39,19 +56,6 @@ object_key(ObjOrObjs, Meta) when is_tuple(ObjOrObjs) ->
object_key(ObjOrObjs, Meta) when is_list(ObjOrObjs) ->
element(shards_meta:keypos(Meta), hd(ObjOrObjs)).
-
-%% @doc
-%% Searches the childern of the supervisor `SupPid' for a child whose Nth
-%% element compares equal to `Id'.
-%% @end
--spec get_sup_child(SupPid, Id) -> Child when
- SupPid :: pid(),
- Id :: term(),
- Child :: pid() | undefined | restarting.
-get_sup_child(SupPid, Id) ->
- {Id, Child, _, _} = lists:keyfind(Id, 1, supervisor:which_children(SupPid)),
- Child.
-
%% @equiv keyfind(Key, KVList, undefined)
keyfind(Key, KVList) ->
keyfind(Key, KVList, undefined).
@@ -63,7 +67,7 @@ keyfind(Key, KVList) ->
keyfind(Key, KVList, Default) ->
case lists:keyfind(Key, 1, KVList) of
{Key, Value} -> Value;
- _ -> Default
+ false -> Default
end.
%% @equiv keyupdate(Fun, Keys, undefined, TupleList)
diff --git a/src/shards_meta.erl b/src/shards_meta.erl
index 41055d3..766d554 100644
--- a/src/shards_meta.erl
+++ b/src/shards_meta.erl
@@ -2,12 +2,12 @@
%%% @doc
%%% This module encapsulates the partitioned table metadata.
%%%
-%%% Different properties must be stored somewhere so `shards'
-%%% can work properly. Shards perform logic on top of ETS tables,
-%%% for example, compute the partition based on the `Key' where
-%%% the action will be applied. To do so, it needs the number of
-%%% partitions, the function to select the partition, and also the
-%%% partition identifier to perform the ETS action.
+%%% Different properties must be stored somewhere so `shards' can work
+%%% properly. Shards performs its logic on top of ETS tables, for
+%%% example, computing the partition based on the `Key' where the
+%%% action will be applied. To do so, it needs the number of
+%%% partitions, the function to select the partition, and also
+%%% the partition identifier to perform the ETS action.
%%% @end
%%%-------------------------------------------------------------------
-module(shards_meta).
@@ -21,31 +21,36 @@
init/2,
rename/2,
lookup/2,
+ put/2,
put/3,
get/1,
get/2,
get/3,
- get_partition_tids/1,
+ get_owner/1,
+ get_ets_opts/1,
+ fetch/2,
+ get_partition_tables/1,
get_partition_pids/1
]).
%% API – Getters
-export([
- tab_pid/1,
keypos/1,
partitions/1,
keyslot_fun/1,
parallel/1,
parallel_timeout/1,
- ets_opts/1
+ cache/1
]).
-%% Inline-compiled functions
+%% Inline common instructions
-compile({inline, [
lookup/2,
put/3,
get/1,
- get_partition_tids/1,
+ get_owner/1,
+ get_ets_opts/1,
+ get_partition_tables/1,
get_partition_pids/1
]}).
@@ -56,8 +61,8 @@
%% Default number of partitions
-define(PARTITIONS, erlang:system_info(schedulers_online)).
-%% Defines a tuple-list with the partition number and the ETS TID.
--type partition_tids() :: [{non_neg_integer(), ets:tid()}].
+%% Defines a tuple-list with the partition number and the table reference.
+-type partition_tables() :: [{non_neg_integer(), shards:tab()}].
%% Defines a tuple-list with the partition number and the partition owner PID.
-type partition_pids() :: [{non_neg_integer(), pid()}].
@@ -68,13 +73,12 @@
%% Metadata definition
-record(meta, {
- tab_pid = undefined :: pid() | undefined,
keypos = 1 :: pos_integer(),
partitions = ?PARTITIONS :: pos_integer(),
keyslot_fun = fun erlang:phash2/2 :: keyslot_fun(),
parallel = false :: boolean(),
parallel_timeout = infinity :: timeout(),
- ets_opts = [] :: [term()]
+ cache = false :: boolean()
}).
%% Defines `shards' metadata.
@@ -82,13 +86,12 @@
%% Defines the map representation for the metadata data type.
-type meta_map() :: #{
- tab_pid => pid(),
keypos => pos_integer(),
partitions => pos_integer(),
keyslot_fun => keyslot_fun(),
parallel => boolean(),
parallel_timeout => timeout(),
- ets_opts => [term()]
+ cache => boolean()
}.
%% Exported types
@@ -114,13 +117,12 @@ new() -> #meta{}.
-spec from_map(Map :: #{atom() => term()}) -> t().
from_map(Map) ->
#meta{
- tab_pid = maps:get(tab_pid, Map, self()),
keypos = maps:get(keypos, Map, 1),
partitions = maps:get(partitions, Map, ?PARTITIONS),
keyslot_fun = maps:get(keyslot_fun, Map, fun erlang:phash2/2),
parallel = maps:get(parallel, Map, false),
parallel_timeout = maps:get(parallel_timeout, Map, infinity),
- ets_opts = maps:get(ets_opts, Map, [])
+ cache = maps:get(cache, Map, false)
}.
%% @doc
@@ -129,13 +131,12 @@ from_map(Map) ->
-spec to_map(t()) -> meta_map().
to_map(Meta) ->
#{
- tab_pid => Meta#meta.tab_pid,
keypos => Meta#meta.keypos,
partitions => Meta#meta.partitions,
keyslot_fun => Meta#meta.keyslot_fun,
parallel => Meta#meta.parallel,
parallel_timeout => Meta#meta.parallel_timeout,
- ets_opts => Meta#meta.ets_opts
+ cache => Meta#meta.cache
}.
%% @doc
@@ -160,7 +161,7 @@ init(Tab, Opts) ->
false -> []
end,
- ets:new(Tab, [set, public, {read_concurrency, true}] ++ ExtraOpts).
+ ets:new(Tab, [set, public, {read_concurrency, true} | ExtraOpts]).
%% @doc
%% Renames the metadata ETS table.
@@ -175,9 +176,10 @@ rename(Tab, Name) ->
%% Returns the value associated to the key `Key' in the metadata table `Tab'.
%% If `Key' is not found, the error `{unknown_table, Tab}' is raised.
%% @end
--spec lookup(Tab, Key) -> term() when
+-spec lookup(Tab, Key) -> Val when
Tab :: shards:tab(),
- Key :: term().
+ Key :: term(),
+ Val :: term().
lookup(Tab, Key) ->
try
ets:lookup_element(Tab, Key, 2)
@@ -185,6 +187,16 @@ lookup(Tab, Key) ->
error:badarg -> error({unknown_table, Tab})
end.
+%% @doc
+%% Inserts the given entries `TupleList' into the metadata table `Tab'.
+%% @end
+-spec put(Tab, TupleList) -> ok when
+ Tab :: shards:tab(),
+ TupleList :: [tuple()].
+put(Tab, TupleList) ->
+ true = ets:insert(Tab, TupleList),
+ ok.
+
%% @doc
%% Stores the value `Val' under the given key `Key' into the metadata table
%% `Tab'.
@@ -198,10 +210,27 @@ put(Tab, Key, Val) ->
ok.
%% @doc
-%% Returns the `tab_info' within the metadata.
+%% Returns the table metadata.
%% @end
-spec get(Tab :: shards:tab()) -> t() | no_return().
-get(Tab) -> lookup(Tab, '$tab_info').
+get(Tab) ->
+ case shards_meta_cache:get_meta(Tab) of
+ undefined ->
+ case lookup(Tab, '$tab_meta') of
+ #meta{cache = true} = Meta ->
+ % Caching is enabled, hence, cache the metadata
+ ok = shards_meta_cache:put_meta(Tab, Meta),
+
+ % Return the metadata
+ Meta;
+
+ Meta ->
+ Meta
+ end;
+
+ Meta ->
+ Meta
+ end.
%% @equiv get(Tab, Key, undefined)
get(Tab, Key) ->
@@ -217,26 +246,64 @@ get(Tab, Key) ->
Def :: term(),
Val :: term().
get(Tab, Key, Def) ->
+ case fetch(Tab, Key) of
+ {ok, Val} ->
+ Val;
+
+ {error, not_found} ->
+ Def;
+
+ {error, unknown_table} ->
+ error({unknown_table, Tab})
+ end.
+
+%% @doc
+%% Fetches the value for a specific `Key' in the metadata.
+%%
+%% If the metadata contains the given `Key', its value is returned in
+%% the shape of `{ok, Value}'. If the metadata doesn't contain `Key',
+%% `{error, Reason}' is returned.
+%% @end
+-spec fetch(Tab, Key) -> {ok, Value} | {error, Reason} when
+ Tab :: shards:tab(),
+ Key :: term(),
+ Value :: term(),
+ Reason :: not_found | unknown_table.
+fetch(Tab, Key) ->
try
case ets:lookup(Tab, Key) of
- [{Key, Val}] -> Val;
- [] -> Def
+ [{Key, Val}] -> {ok, Val};
+ [] -> {error, not_found}
end
catch
- error:badarg -> error({unknown_table, Tab})
+ error:badarg -> {error, unknown_table}
end.
+%% @doc Returns the owner PID for the table `Tab'.
+%% @equiv lookup(Tab, '$tab_owner')
+-spec get_owner(Tab :: shards:tab()) -> pid().
+get_owner(Tab) ->
+ lookup(Tab, '$tab_owner').
+
+%% @doc Returns the ETS options for the table `Tab'.
+%% @equiv lookup(Tab, '$ets_opts')
+-spec get_ets_opts(Tab :: shards:tab()) -> [term()].
+get_ets_opts(Tab) ->
+ lookup(Tab, '$ets_opts').
+
%% @doc
%% Returns a list with the partition TIDs.
%% @end
--spec get_partition_tids(Tab :: shards:tab()) -> partition_tids().
-get_partition_tids(Tab) -> partitions_info(Tab, tid).
+-spec get_partition_tables(Tab :: shards:tab()) -> partition_tables().
+get_partition_tables(Tab) ->
+ partitions_info(Tab, table).
%% @doc
%% Returns a list with the partition PIDs.
%% @end
-spec get_partition_pids(Tab :: shards:tab()) -> partition_pids().
-get_partition_pids(Tab) -> partitions_info(Tab, pid).
+get_partition_pids(Tab) ->
+ partitions_info(Tab, pid).
%% @private
partitions_info(Tab, KeyPrefix) ->
@@ -250,12 +317,6 @@ partitions_info(Tab, KeyPrefix) ->
%%% Getters
%%%===================================================================
--spec tab_pid(t() | shards:tab()) -> pid().
-tab_pid(#meta{tab_pid = Value}) ->
- Value;
-tab_pid(Tab) when is_atom(Tab); is_reference(Tab) ->
- tab_pid(?MODULE:get(Tab)).
-
-spec keypos(t() | shards:tab()) -> pos_integer().
keypos(#meta{keypos = Value}) ->
Value;
@@ -286,8 +347,8 @@ parallel_timeout(#meta{parallel_timeout = Value}) ->
parallel_timeout(Tab) when is_atom(Tab); is_reference(Tab) ->
parallel_timeout(?MODULE:get(Tab)).
--spec ets_opts(t() | shards:tab()) -> [term()].
-ets_opts(#meta{ets_opts = Value}) ->
+-spec cache(t() | shards:tab()) -> boolean().
+cache(#meta{cache = Value}) ->
Value;
-ets_opts(Tab) when is_atom(Tab); is_reference(Tab) ->
- ets_opts(?MODULE:get(Tab)).
+cache(Tab) when is_atom(Tab); is_reference(Tab) ->
+ cache(?MODULE:get(Tab)).
diff --git a/src/shards_meta_cache.erl b/src/shards_meta_cache.erl
new file mode 100644
index 0000000..df78fc9
--- /dev/null
+++ b/src/shards_meta_cache.erl
@@ -0,0 +1,98 @@
+%%%-------------------------------------------------------------------
+%%% @doc
+%%% Metadata cache storage using the `persistent_term'.
+%%% @end
+%%%-------------------------------------------------------------------
+-module(shards_meta_cache).
+
+-behaviour(gen_server).
+
+%% API
+-export([
+ start_link/2,
+ child_spec/2,
+ get_meta/1,
+ put_meta/2,
+ del_meta/1
+]).
+
+%% gen_server callbacks
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+]).
+
+%% Inline common instructions
+-compile({inline, [
+ get_meta/1,
+ put_meta/2
+]}).
+
+%% State
+-record(state, {
+ tab :: shards:tab(),
+ tab_pid :: pid()
+}).
+
+%% Macro to build the metadata cache key
+-define(meta_key(Tab_), {?MODULE, Tab_}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec start_link(Tab :: shards:tab(), TabPid :: pid()) -> gen_server:start_ret().
+start_link(Tab, TabPid) ->
+ gen_server:start_link(?MODULE, {Tab, TabPid}, []).
+
+-spec child_spec(Tab :: shards:tab(), TabPid :: pid()) -> supervisor:child_spec().
+child_spec(Tab, TabPid) ->
+ #{
+ id => {?MODULE, TabPid},
+ start => {?MODULE, start_link, [Tab, TabPid]},
+ type => supervisor
+ }.
+
+-spec get_meta(Tab :: shards:tab()) -> shards_meta:t() | undefined.
+get_meta(Tab) ->
+ persistent_term:get(?meta_key(Tab), undefined).
+
+-spec put_meta(Tab :: shards:tab(), Meta :: shards_meta:t()) -> ok.
+put_meta(Tab, Meta) ->
+ persistent_term:put(?meta_key(Tab), Meta).
+
+-spec del_meta(Tab :: shards:tab()) -> ok.
+del_meta(Tab) ->
+ _ = persistent_term:erase(?meta_key(Tab)),
+ ok.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%% @hidden
+init({Tab, TabPid}) ->
+ _ = process_flag(trap_exit, true),
+
+ {ok, #state{tab = Tab, tab_pid = TabPid}}.
+
+%% @hidden
+handle_call(_Request, _From, State) ->
+ {reply, ok, State}.
+
+%% @hidden
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+%% @hidden
+handle_info({'EXIT', _Pid, _Reason}, #state{} = State) ->
+ {stop, normal, State};
+handle_info(_Reason, State) ->
+ {noreply, State}.
+
+%% @hidden
+terminate(_Reason, #state{tab = Tab}) ->
+ del_meta(Tab).
diff --git a/src/shards_opts.erl b/src/shards_opts.erl
index 9ee49e4..6243203 100644
--- a/src/shards_opts.erl
+++ b/src/shards_opts.erl
@@ -28,7 +28,7 @@
%%% API
%%%===================================================================
--spec parse([shards:option()]) -> shards_meta:meta_map().
+-spec parse([shards:option()]) -> map().
parse(Opts) ->
MetaMap = shards_meta:to_map(shards_meta:new()),
ParsedOpts = parse_opts(Opts, MetaMap#{ets_opts => []}),
@@ -59,6 +59,8 @@ parse_opts([{parallel, Val} | Opts], Acc) when is_boolean(Val) ->
parse_opts([{parallel_timeout, Val} | Opts], Acc)
when (is_integer(Val) andalso Val >= 0) orelse Val == infinity ->
parse_opts(Opts, Acc#{parallel_timeout := Val});
+parse_opts([{cache, Val} | Opts], Acc) when is_boolean(Val) ->
+ parse_opts(Opts, Acc#{cache := Val});
parse_opts([{restore, _, _} = Opt | Opts], #{ets_opts := NOpts} = Acc) ->
parse_opts(Opts, Acc#{ets_opts := [Opt | NOpts]});
parse_opts([{heir, _, _} = Opt | Opts], #{ets_opts := NOpts} = Acc) ->
diff --git a/src/shards_partition.erl b/src/shards_partition.erl
index 554b5c8..d2298d5 100644
--- a/src/shards_partition.erl
+++ b/src/shards_partition.erl
@@ -12,13 +12,13 @@
%% API
-export([
- start_link/4,
+ start_link/5,
+ child_spec/5,
apply_ets_fun/3,
- retrieve_tab/1,
stop/1,
stop/2,
- tid/2,
- tid/3,
+ table/2,
+ table/3,
pid/2,
compute/2
]).
@@ -28,45 +28,61 @@
init/1,
handle_call/3,
handle_cast/2,
- handle_info/2
+ handle_info/2,
+ terminate/2
]).
%% Inline-compiled functions
--compile({inline, [tid/2, tid/3, pid/2]}).
+-compile({inline, [table/2, table/3, pid/2]}).
%% State
-record(state, {
- tab :: atom() | ets:tid(),
+ tab :: shards:tab(),
tab_pid :: pid() | undefined,
+ tab_name :: atom() | undefined,
partition :: non_neg_integer() | undefined,
- partition_tid :: ets:tid() | undefined
+ partition_tab :: shards:tab() | undefined,
+ named_table :: boolean() | undefined
}).
+%% Key for the partition table reference
+-define(ptn_key(Ptn_), {table, Ptn_}).
+-define(ptn_key(Tab_, Ptn_), {table, Tab_, Ptn_}).
+
+%% Key for the partition pid
+-define(pid_key(Ptn_), {pid, Ptn_}).
+
%%%===================================================================
%%% API
%%%===================================================================
-spec start_link(
- Tab :: atom() | ets:tid(),
+ Name :: atom(),
+ Tab :: shards:tab(),
+ PartitionedTablePid :: pid(),
+ PartitionIndex :: non_neg_integer(),
+ Options :: [term()]
+ ) -> gen_server:start_ret().
+start_link(Name, Tab, PartitionedTablePid, PartitionIndex, Options) ->
+ gen_server:start_link(?MODULE, {Name, Tab, PartitionedTablePid, PartitionIndex, Options}, []).
+
+-spec child_spec(
+ Name :: atom(),
+ Tab :: shards:tab(),
PartitionedTablePid :: pid(),
PartitionIndex :: non_neg_integer(),
Options :: [term()]
- ) -> {ok, pid()} | ignore | {error, term()}.
-start_link(Tab, TabPid, Partition, Options) ->
- gen_server:start_link(?MODULE, {Tab, TabPid, Partition, Options}, []).
-
--spec apply_ets_fun(
- Pid :: pid(),
- EtsFun :: atom(),
- Args :: [term()]
- ) -> term().
+ ) -> supervisor:child_spec().
+child_spec(Name, Tab, PartitionedTablePid, PartitionIndex, Options) ->
+ #{
+ id => {?MODULE, PartitionIndex},
+ start => {?MODULE, start_link, [Name, Tab, PartitionedTablePid, PartitionIndex, Options]}
+ }.
+
+-spec apply_ets_fun(Pid :: pid(), EtsFun :: atom(), Args :: [term()]) -> term().
apply_ets_fun(Pid, Fun, Args) ->
gen_server:call(Pid, {ets, Fun, Args}).
--spec retrieve_tab(Pid :: pid()) -> atom() | ets:tid().
-retrieve_tab(Pid) ->
- gen_server:call(Pid, retrieve_tab).
-
%% @equiv stop(Server, 5000)
stop(Pid) ->
stop(Pid, 5000).
@@ -75,17 +91,45 @@ stop(Pid) ->
stop(Pid, Timeout) ->
gen_server:stop(Pid, normal, Timeout).
--spec tid(Tab :: shards:tab(), Partition :: non_neg_integer()) -> ets:tid().
-tid(Tab, Partition) ->
- shards_meta:lookup(Tab, {tid, Partition}).
+-spec table(Tab :: shards:tab(), Partition :: non_neg_integer()) -> shards:tab().
+table(Tab, Partition) ->
+ shards_meta:lookup(Tab, ?ptn_key(Partition)).
+ % MetaKey = ?ptn_key(Tab, Partition),
+
+ % case persistent_term:get(MetaKey, undefined) of
+ % undefined ->
+ % % Retrieve the partition table ref from the ETS meta table
+ % PtnTab = shards_meta:lookup(Tab, ?ptn_key(Partition)),
+
+ % % Caching is enabled, hence, cache the partition table ref
+ % ok = persistent_term:put(MetaKey, PtnTab),
+
+ % % Return the partition table ref
+ % PtnTab;
--spec tid(Tab :: shards:tab(), Key :: term(), Meta :: shards_meta:t()) -> ets:tid().
-tid(Tab, Key, Meta) ->
- shards_meta:lookup(Tab, {tid, compute(Key, Meta)}).
+ % % case shards_meta:cache(Meta) of
+ % % true ->
+ % % % Caching is enabled, hence, cache the partition table ref
+ % % ok = persistent_term:put(MetaKey, PtnTab),
+
+ % % % Return the partition table ref
+ % % PtnTab;
+
+ % % false ->
+ % % PtnTab
+ % % end;
+
+ % PtnTab ->
+ % PtnTab
+ % end.
+
+-spec table(Tab :: shards:tab(), Key :: term(), Meta :: shards_meta:t()) -> shards:tab().
+table(Tab, Key, Meta) ->
+ table(Tab, compute(Key, Meta)).
-spec pid(Tab :: shards:tab(), Partition :: non_neg_integer()) -> pid().
pid(Tab, Partition) ->
- shards_meta:lookup(Tab, {pid, Partition}).
+ shards_meta:lookup(Tab, ?pid_key(Partition)).
-spec compute(Key :: term(), Meta :: shards_meta:t()) -> non_neg_integer().
compute(Key, Meta) ->
@@ -98,7 +142,7 @@ compute(Key, Meta) ->
%%%===================================================================
%% @hidden
-init({Tab, TabPid, Partition, Opts}) ->
+init({Name, Tab, TabPid, Partition, Opts}) ->
_ = process_flag(trap_exit, true),
NewOpts =
@@ -107,32 +151,40 @@ init({Tab, TabPid, Partition, Opts}) ->
false -> Opts
end,
- init(NewOpts, #state{tab = Tab, tab_pid = TabPid, partition = Partition}).
+ InitState = #state{
+ tab = Tab,
+ tab_pid = TabPid,
+ tab_name = Name,
+ partition = Partition
+ },
+
+ init(NewOpts, InitState).
%% @private
init({restore, PartitionFilenames, Opts}, #state{partition = Partition} = State) ->
Filename = maps:get(Partition, PartitionFilenames),
case ets:file2tab(Filename, Opts) of
- {ok, Tid} ->
- NewState = register(State#state{partition_tid = Tid}),
- {ok, NewState};
+ {ok, PartitionTab} ->
+ {ok, register(State#state{partition_tab = PartitionTab})};
Error ->
{stop, {restore_error, Error}}
end;
-init(Opts, State) ->
- NewOpts = lists:delete(named_table, lists:usort([public | Opts])),
- Tid = ets:new(?MODULE, NewOpts),
- NewState = register(State#state{partition_tid = Tid}),
- {ok, NewState}.
+init(Opts, #state{tab_name = Name, partition = Partition} = State) ->
+ PartitionName = shards_lib:partition_name(Name, Partition),
+ PartitionTab = ets:new(PartitionName, lists:usort([public | Opts])),
+
+ NewState = State#state{
+ partition_tab = PartitionTab,
+ named_table = lists:member(named_table, Opts)
+ },
+
+ {ok, register(NewState)}.
%% @hidden
-handle_call(retrieve_tab, _From, #state{tab = Tab} = State) ->
- {reply, Tab, State};
-handle_call({ets, Fun, Args}, _From, #state{partition_tid = PartTid} = State) ->
- Response = apply(ets, Fun, [PartTid | Args]),
- {reply, Response, State};
+handle_call({ets, Fun, Args}, _From, #state{partition_tab = PartitionTab} = State) ->
+ {reply, apply(ets, Fun, [PartitionTab | Args]), State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@@ -146,12 +198,18 @@ handle_info({'EXIT', _Pid, _Reason}, #state{} = State) ->
handle_info(_Reason, State) ->
{noreply, State}.
+%% @hidden
+terminate(_Reason, #state{tab = Tab, partition = Partition, named_table = true}) ->
+ persistent_term:erase(?ptn_key(Tab, Partition));
+terminate(_Reason, State) ->
+ State.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
%% @private
-register(#state{tab = Tab, partition = Part, partition_tid = PartTid} = State) ->
- ok = shards_meta:put(Tab, {tid, Part}, PartTid),
- ok = shards_meta:put(Tab, {pid, Part}, self()),
+register(#state{tab = Tab, partition = Ptn, partition_tab = PartTab} = State) ->
+ ok = shards_meta:put(Tab, ?ptn_key(Ptn), PartTab),
+ ok = shards_meta:put(Tab, ?pid_key(Ptn), self()),
State.
diff --git a/src/shards_partition_sup.erl b/src/shards_partition_sup.erl
index 3bc4a10..4f5d21c 100644
--- a/src/shards_partition_sup.erl
+++ b/src/shards_partition_sup.erl
@@ -39,7 +39,7 @@
Opts :: opts(),
OnStart :: {ok, pid()} | ignore | {error, term()}.
start_link(Name, Opts) ->
- supervisor:start_link(?MODULE, {Name, Opts}).
+ supervisor:start_link(?MODULE, {self(), Name, Opts}).
%% @equiv stop(Pid, 5000)
stop(SupRef) ->
@@ -56,34 +56,48 @@ stop(SupRef, Timeout) ->
%%%===================================================================
%% @hidden
-init({Name, Opts}) ->
+init({ParentPid, Name, Opts}) ->
EtsOpts = maps:get(ets_opts, Opts, []),
Partitions = maps:get(partitions, Opts, ?PARTITIONS),
- % init metadata
+ % Init metadata
Tab = init_meta(Name, Opts, EtsOpts, Partitions),
- Children =
- shards_enum:map(fun(Partition) ->
- child(shards_partition, [Tab, self(), Partition, EtsOpts], #{id => Partition})
+ % Send a reply with the table reference to the client that called `new/2`
+ _ = ParentPid ! {reply, self(), Tab},
+
+ % Build the parrtition owner specs
+ PartitionOwners =
+ shards_enum:map(fun(PartitionIdx) ->
+ shards_partition:child_spec(Name, Tab, self(), PartitionIdx, EtsOpts)
end, Partitions),
+ % The children list contains the partition owners plus the metadata cache
+ Children = [
+ shards_meta_cache:child_spec(Tab, self())
+ | PartitionOwners
+ ],
+
{ok, {{one_for_all, 1, 5}, Children}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-%% @private
-child(Module, Args, Spec) ->
- Spec#{start => {Module, start_link, Args}}.
-
%% @private
init_meta(Name, Opts, EtsOpts, Partitions) ->
try
Tab = shards_meta:init(Name, EtsOpts),
- Meta = shards_meta:from_map(Opts#{tab_pid => self(), partitions => Partitions}),
- ok = shards_meta:put(Tab, '$tab_info', Meta),
+ Meta = shards_meta:from_map(Opts#{partitions => Partitions}),
+
+ % Store the metadata
+ ok =
+ shards_meta:put(Tab, [
+ {'$tab_meta', Meta},
+ {'$tab_owner', self()},
+ {'$ets_opts', EtsOpts}
+ ]),
+
Tab
catch
error:badarg -> error({conflict, Name})
diff --git a/test/prop_shards_statem.erl b/test/prop_shards_statem.erl
index dfa96f9..24949a3 100644
--- a/test/prop_shards_statem.erl
+++ b/test/prop_shards_statem.erl
@@ -11,7 +11,6 @@
next_state/3
]).
--include_lib("common_test/include/ct_property_test.hrl").
-include_lib("proper/include/proper.hrl").
%%%===================================================================
diff --git a/test/shards_SUITE.erl b/test/shards_SUITE.erl
index 0435ec4..54283e6 100644
--- a/test/shards_SUITE.erl
+++ b/test/shards_SUITE.erl
@@ -37,24 +37,10 @@
t_metadata_function_wrappers/1,
t_table_deleted_when_partition_goes_down/1,
t_shards_table_unhandled_callbacks/1,
- t_table_creation_errors/1
+ t_table_creation_errors/1,
+ t_with_meta_cache/1
]).
-% %% Test Cases Helpers
-% -export([
-% t_basic_ops_/1,
-% t_match_ops_/1,
-% t_select_ops_/1
-% ]).
-
-% %% Helpers
-% -export([
-% init_shards/0,
-% init_shards/1,
-% cleanup_shards/0,
-% delete_shards/0
-% ]).
-
-define(EXCLUDED_FUNS, [
module_info,
all,
@@ -545,7 +531,7 @@ t_info_ops_({Tab, EtsTab}) ->
({memory, V}) -> V = shards_lib:keyfind(memory, R1) * Partitions;
({name, V}) -> V = ets:info(Tab, name);
({id, V}) -> V = ets:info(Tab, id);
- ({owner, V}) -> V = shards_meta:tab_pid(Meta);
+ ({owner, V}) -> V = shards_meta:get_owner(Tab);
({K, V}) -> V = shards_lib:keyfind(K, R1)
end, R11).
@@ -667,7 +653,7 @@ t_equivalent_ops_({Tab, _EtsTab}) ->
R4 = ets:test_ms({k1, 1}, MS),
R4 = shards:test_ms({k1, 1}, MS),
- Ids = shards_meta:get_partition_tids(Tab),
+ Ids = shards_meta:get_partition_tables(Tab),
R5 = lists:sort([ets:table(Id) || {_, Id} <- Ids]),
R5 = lists:sort(shards:table(Tab)),
R5 = lists:sort(shards:table(Tab, [])),
@@ -759,6 +745,26 @@ t_table_creation_errors(_Config) ->
shards:new(another_table, [wrongarg])
end, {badoption, wrongarg}).
+-spec t_with_meta_cache(shards_ct:config()) -> any().
+t_with_meta_cache(_Config) ->
+ shards_ct:with_table(fun(Tab) ->
+ ExpectedMeta = shards_meta:get(Tab),
+
+ undefined = shards_meta_cache:get_meta(Tab),
+
+ shards:with_meta_cache(Tab, fun(Meta) ->
+ true = shards:insert(Tab, {foo, bar}, Meta)
+ end),
+
+ ExpectedMeta = shards_meta_cache:get_meta(Tab),
+
+ shards:with_meta_cache(Tab, fun(Meta) ->
+ bar = shards:lookup_element(Tab, foo, 2, Meta)
+ end),
+
+ ExpectedMeta = shards_meta_cache:get_meta(Tab)
+ end, meta_cache_test, [named_table]).
+
%%%===================================================================
%%% Helpers
%%%===================================================================
@@ -868,7 +874,7 @@ shards_created(TabL) when is_list(TabL) ->
shards_created(Tab) ->
lists:foreach(fun({_, Tid}) ->
true = lists:member(Tid, shards:all())
- end, shards_meta:get_partition_tids(Tab)).
+ end, shards_meta:get_partition_tables(Tab)).
%% @private
select_by({Mod, Fun} = ModFun, Continuation, Limit) ->
diff --git a/test/shards_enum_SUITE.erl b/test/shards_enum_SUITE.erl
index f6006f3..3eb0311 100644
--- a/test/shards_enum_SUITE.erl
+++ b/test/shards_enum_SUITE.erl
@@ -1,7 +1,5 @@
-module(shards_enum_SUITE).
--include_lib("common_test/include/ct.hrl").
-
%% Common Test
-export([
all/0
diff --git a/test/shards_group_SUITE.erl b/test/shards_group_SUITE.erl
index 71162d8..3893ec0 100644
--- a/test/shards_group_SUITE.erl
+++ b/test/shards_group_SUITE.erl
@@ -1,8 +1,5 @@
-module(shards_group_SUITE).
--include_lib("common_test/include/ct.hrl").
--include("shards_ct.hrl").
-
%% Common Test
-export([
all/0
@@ -50,13 +47,14 @@ t_start_group_without_name(_Config) ->
t_adding_removing_tables(_Config) ->
with_group(fun(SupPid) ->
{ok, Pid1, T1} = shards_group:new_table(SupPid, t1, []),
+ [Pid1] = children_pids(SupPid),
T1 = shards:info(T1, id),
{ok, Pid2, T2} = shards_group:new_table(SupPid, t2, []),
- [Pid1, Pid2] = shards:partition_owners(SupPid),
+ [Pid1, Pid2] = children_pids(SupPid),
ok = shards_group:del_table(SupPid, T2),
- [Pid1] = shards:partition_owners(SupPid)
+ [Pid1] = children_pids(SupPid)
end, dynamic_sup).
-spec t_child_spec(shards_ct:config()) -> any().
@@ -79,3 +77,6 @@ with_group(Fun, Name) ->
after
shards_group:stop(SupPid)
end.
+
+children_pids(SupPid) ->
+ [Child || {_, Child, _, _} <- supervisor:which_children(SupPid)].
diff --git a/test/shards_lib_SUITE.erl b/test/shards_lib_SUITE.erl
index 5e9e0d5..b26382f 100644
--- a/test/shards_lib_SUITE.erl
+++ b/test/shards_lib_SUITE.erl
@@ -1,7 +1,5 @@
-module(shards_lib_SUITE).
--include_lib("common_test/include/ct.hrl").
-
%% Common Test
-export([
all/0
@@ -9,8 +7,8 @@
%% Test Cases
-export([
+ t_partition_name/1,
t_object_key/1,
- t_get_sup_child/1,
t_keyfind/1,
t_keyupdate/1,
t_keypop/1,
@@ -36,6 +34,11 @@ all() ->
%%% Test Cases
%%%===================================================================
+-spec t_partition_name(shards_ct:config()) -> any().
+t_partition_name(_Config) ->
+ 't.ptn0' = shards_lib:partition_name(t, 0),
+ 't.ptn1' = shards_lib:partition_name(t, 1).
+
-spec t_object_key(shards_ct:config()) -> any().
t_object_key(_Config) ->
1 = shards_lib:object_key([{1, 1}, {2, 2}, {3, 3}], shards_meta:new()),
@@ -43,13 +46,6 @@ t_object_key(_Config) ->
State = shards_meta:from_map(#{tab_pid => self(), keypos => 2}),
"foo" = shards_lib:object_key({abc, "foo", "bar"}, State).
--spec t_get_sup_child(shards_ct:config()) -> any().
-t_get_sup_child(_Config) ->
- Tab = shards:new(shards_lib_test, []),
- Part0 = shards_lib:get_sup_child(shards_meta:tab_pid(Tab), 0),
- true = is_pid(Part0),
- true = shards:delete(Tab).
-
-spec t_keyfind(shards_ct:config()) -> any().
t_keyfind(_Config) ->
TupleList = new_tuple_list(10),
diff --git a/test/shards_meta_SUITE.erl b/test/shards_meta_SUITE.erl
index 0368cee..78410a6 100644
--- a/test/shards_meta_SUITE.erl
+++ b/test/shards_meta_SUITE.erl
@@ -1,6 +1,5 @@
-module(shards_meta_SUITE).
--include_lib("common_test/include/ct.hrl").
-include("shards_ct.hrl").
%% Common Test
@@ -13,8 +12,9 @@
t_getters/1,
t_getters_with_table/1,
t_to_map/1,
- t_retrieve_tids_pids/1,
+ t_retrieve_tables_and_pids/1,
t_store_and_retrieve_from_meta_table/1,
+ t_meta_cache/1,
t_errors/1
]).
@@ -39,31 +39,28 @@ all() ->
-spec t_getters(shards_ct:config()) -> any().
t_getters(_Config) ->
Meta0 = shards_meta:new(),
- undefined = shards_meta:tab_pid(Meta0),
1 = shards_meta:keypos(Meta0),
true = ?PARTITIONS == shards_meta:partitions(Meta0),
true = fun erlang:phash2/2 == shards_meta:keyslot_fun(Meta0),
false = shards_meta:parallel(Meta0),
infinity = shards_meta:parallel_timeout(Meta0),
- [] = shards_meta:ets_opts(Meta0),
+ false = shards_meta:cache(Meta0),
Meta1 =
shards_meta:from_map(#{
keypos => 2,
partitions => 4,
parallel => true,
- parallel_timeout => 5000
+ parallel_timeout => 5000,
+ cache => true
}),
- Self = self(),
- Self = shards_meta:tab_pid(Meta1),
2 = shards_meta:keypos(Meta1),
4 = shards_meta:partitions(Meta1),
true = fun erlang:phash2/2 == shards_meta:keyslot_fun(Meta1),
true = shards_meta:parallel(Meta1),
5000 = shards_meta:parallel_timeout(Meta1),
- [] = shards_meta:ets_opts(Meta1),
- ok.
+ true = shards_meta:cache(Meta1).
-spec t_getters_with_table(shards_ct:config()) -> any().
t_getters_with_table(_Config) ->
@@ -71,14 +68,15 @@ t_getters_with_table(_Config) ->
true = shards_meta:is_metadata(shards_meta:get(Tab)),
false = shards_meta:is_metadata(invalid),
- Pid = shards_meta:tab_pid(Tab),
+ Pid = shards_meta:get_owner(Tab),
true = is_pid(Pid),
1 = shards_meta:keypos(Tab),
true = ?PARTITIONS == shards_meta:partitions(Tab),
true = fun erlang:phash2/2 == shards_meta:keyslot_fun(Tab),
false = shards_meta:parallel(Tab),
infinity = shards_meta:parallel_timeout(Tab),
- [] = shards_meta:ets_opts(Tab),
+ false = shards_meta:cache(Tab),
+ [] = shards_meta:get_ets_opts(Tab),
true = shards:delete(Tab).
@@ -88,22 +86,21 @@ t_to_map(_Config) ->
Parts = ?PARTITIONS,
#{
- tab_pid := undefined,
keypos := 1,
partitions := Parts,
keyslot_fun := KeyslotFun,
parallel := false,
parallel_timeout := infinity,
- ets_opts := []
+ cache := false
} = shards_meta:to_map(Meta0),
true = fun erlang:phash2/2 == KeyslotFun.
--spec t_retrieve_tids_pids(shards_ct:config()) -> any().
-t_retrieve_tids_pids(_Config) ->
+-spec t_retrieve_tables_and_pids(shards_ct:config()) -> any().
+t_retrieve_tables_and_pids(_Config) ->
Tab = shards:new(shards_meta_test, [{partitions, 2}]),
- [{_, Tid}, _] = shards_meta:get_partition_tids(Tab),
+ [{_, Tid}, _] = shards_meta:get_partition_tables(Tab),
true = is_reference(Tid),
Pids = lists:usort([Pid || {_, Pid} <- shards_meta:get_partition_pids(Tab)]),
true = is_pid(hd(Pids)),
@@ -120,12 +117,29 @@ t_store_and_retrieve_from_meta_table(_Config) ->
bar = shards_meta:get(Tab, foo),
undefined = shards_meta:get(Tab, foo_foo),
bar_bar = shards_meta:get(Tab, foo_foo, bar_bar),
+ {ok, bar} = shards_meta:fetch(Tab, foo),
+ {error, not_found} = shards_meta:fetch(Tab, foo_foo),
+ {error, unknown_table} = shards_meta:fetch(unknown, foo),
shards_ct:assert_error(fun() ->
shards_meta:get(unknown, foo)
end, {unknown_table, unknown})
end, meta_table, []).
+-spec t_meta_cache(shards_ct:config()) -> any().
+t_meta_cache(_Config) ->
+ shards_ct:with_table(fun(Tab) ->
+ undefined = shards_meta_cache:get_meta(Tab),
+
+ ExpectedMeta = shards_meta:get(Tab),
+ ExpectedMeta = shards_meta_cache:get_meta(Tab),
+
+ true = shards:insert(Tab, {foo, bar}),
+ bar = shards:lookup_element(Tab, foo, 2),
+
+ ExpectedMeta = shards_meta_cache:get_meta(Tab)
+ end, meta_cache_tab, [{cache, true}]).
+
-spec t_errors(shards_ct:config()) -> any().
t_errors(_Config) ->
shards_ct:assert_error(fun() ->
@@ -137,5 +151,5 @@ t_errors(_Config) ->
end, {unknown_table, unknown}),
shards_ct:assert_error(fun() ->
- shards_meta:get_partition_tids(unknown)
+ shards_meta:get_partition_tables(unknown)
end, {unknown_table, unknown}).
diff --git a/test/shards_meta_cache_SUITE.erl b/test/shards_meta_cache_SUITE.erl
new file mode 100644
index 0000000..64faa87
--- /dev/null
+++ b/test/shards_meta_cache_SUITE.erl
@@ -0,0 +1,87 @@
+-module(shards_meta_cache_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% Common Test
+-export([
+ all/0,
+ init_per_testcase/2,
+end_per_testcase/2
+]).
+
+%% Test Cases
+-export([
+ t_exit_signal/1,
+ t_unhandled_message/1,
+ t_call_ignored/1,
+ t_cast_ignored/1
+]).
+
+-define(EXCLUDED_FUNS, [
+ module_info,
+ all,
+ init_per_testcase,
+ end_per_testcase
+]).
+
+%%%===================================================================
+%%% Common Test
+%%%===================================================================
+
+-spec all() -> [atom()].
+all() ->
+ Exports = ?MODULE:module_info(exports),
+ [F || {F, _} <- Exports, not lists:member(F, ?EXCLUDED_FUNS)].
+
+-spec init_per_testcase(atom(), shards_ct:config()) -> shards_ct:config().
+init_per_testcase(_, Config) ->
+ {ok, Pid} = shards_meta_cache:start_link(test, self()),
+ [{cache_pid, Pid} | Config].
+
+-spec end_per_testcase(atom(), shards_ct:config()) -> shards_ct:config().
+end_per_testcase(_, Config) ->
+ CachePid = ?config(cache_pid, Config),
+
+ case is_process_alive(CachePid) of
+ true ->
+ ok = gen_server:stop(CachePid),
+ Config;
+
+ _ ->
+ Config
+ end.
+
+%%%===================================================================
+%%% Tests Cases
+%%%===================================================================
+
+-spec t_exit_signal(shards_ct:config()) -> any().
+t_exit_signal(Config) ->
+ _ = process_flag(trap_exit, true),
+
+ Pid = ?config(cache_pid, Config),
+
+ _ = Pid ! {'EXIT', Pid, test},
+
+ {'EXIT', Pid, normal} = shards_ct:wait_for_msg(1000),
+ ok.
+
+-spec t_unhandled_message(shards_ct:config()) -> any().
+t_unhandled_message(Config) ->
+ Pid = ?config(cache_pid, Config),
+
+ _ = Pid ! hello,
+
+ ok.
+
+-spec t_call_ignored(shards_ct:config()) -> any().
+t_call_ignored(Config) ->
+ Pid = ?config(cache_pid, Config),
+
+ ok = gen_server:call(Pid, hello).
+
+-spec t_cast_ignored(shards_ct:config()) -> any().
+t_cast_ignored(Config) ->
+ Pid = ?config(cache_pid, Config),
+
+ ok = gen_server:cast(Pid, hello).
diff --git a/test/shards_opts_SUITE.erl b/test/shards_opts_SUITE.erl
index 456808a..c0f248a 100644
--- a/test/shards_opts_SUITE.erl
+++ b/test/shards_opts_SUITE.erl
@@ -1,7 +1,5 @@
-module(shards_opts_SUITE).
--include_lib("common_test/include/ct.hrl").
-
%% Common Test
-export([
all/0
@@ -43,7 +41,7 @@ t_parse(_Config) ->
keyslot_fun := KeyslotFun,
parallel := true,
partitions := 2,
- tab_pid := undefined,
+ cache := true,
ets_opts := [
set,
ordered_set,
@@ -79,7 +77,8 @@ t_parse(_Config) ->
{decentralized_counters, true},
{partitions, 2},
{keyslot_fun, KeyslotFun},
- {parallel, true}
+ {parallel, true},
+ {cache, true}
]).
-spec t_parse_with_defaults(shards_ct:config()) -> any().
@@ -92,8 +91,7 @@ t_parse_with_defaults(_Config) ->
keypos := 1,
keyslot_fun := KeyslotFun,
parallel := false,
- partitions := Partitions,
- tab_pid := undefined
+ partitions := Partitions
} = shards_opts:parse([{restore, nil, nil}]).
-spec t_parse_ordered_set(shards_ct:config()) -> any().
@@ -105,8 +103,7 @@ t_parse_ordered_set(_Config) ->
keypos := 1,
keyslot_fun := KeyslotFun,
parallel := false,
- partitions := 1,
- tab_pid := undefined
+ partitions := 1
} = shards_opts:parse([ordered_set]).
-spec t_parse_errors(shards_ct:config()) -> any().