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'. +%% +%% @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().