diff --git a/src/khepri_machine.erl b/src/khepri_machine.erl index 4ed3f3c9..5e0209dd 100644 --- a/src/khepri_machine.erl +++ b/src/khepri_machine.erl @@ -42,6 +42,16 @@ %% %% %% +%% +%% 2 +%% +%% +%% +%% %% -module(khepri_machine). @@ -165,10 +175,13 @@ %% Added in machine version 1. dedups = #{} :: khepri_machine:dedups_map()}). --opaque state_v1() :: #khepri_machine{}. +-type state_v1() :: #khepri_machine{tree :: khepri_tree:tree_v0()}. %% State of this Ra state machine, version 1. --type state() :: state_v1() | khepri_machine_v0:state(). +-type state_v2() :: #khepri_machine{tree :: khepri_tree:tree_v1()}. +%% State of this Ra state machine, version 2. + +-type state() :: state_v2() | state_v1() | khepri_machine_v0:state(). %% State of this Ra state machine. -type triggers_map() :: #{khepri:trigger_id() => @@ -212,6 +225,7 @@ machine_init_args/0, state/0, state_v1/0, + state_v2/0, machine_config/0, triggers_map/0, metrics/0, @@ -1635,17 +1649,18 @@ overview(State) -> keep_while_conds => KeepWhileConds}. -spec version() -> MacVer when - MacVer :: 1. + MacVer :: 2. %% @doc Returns the state machine version. version() -> - 1. + 2. -spec which_module(MacVer) -> Module when - MacVer :: 1 | 0, + MacVer :: 0..2, Module :: ?MODULE. %% @doc Returns the state machine module corresponding to the given version. +which_module(2) -> ?MODULE; which_module(1) -> ?MODULE; which_module(0) -> ?MODULE. @@ -2313,7 +2328,7 @@ make_virgin_state(Params) -> -endif. -spec convert_state(OldState, OldMacVer, NewMacVer) -> NewState when - OldState :: khepri_machine_v0:state(), + OldState :: khepri_machine:state(), OldMacVer :: ra_machine:version(), NewMacVer :: ra_machine:version(), NewState :: khepri_machine:state(). @@ -2339,7 +2354,11 @@ convert_state1(State, 0, 1) -> Fields1 = Fields0 ++ [#{}], State1 = list_to_tuple(Fields1), ?assert(is_state(State1)), - State1. + State1; +convert_state1(State, 1, 2) -> + Tree = get_tree(State), + Tree1 = khepri_tree:convert_tree(Tree, 1, 2), + set_tree(State, Tree1). -spec update_projections(OldState, NewState) -> ok when OldState :: khepri_machine:state(), diff --git a/src/khepri_tree.erl b/src/khepri_tree.erl index af46374f..1dc05ae9 100644 --- a/src/khepri_tree.erl +++ b/src/khepri_tree.erl @@ -31,19 +31,32 @@ delete_matching_nodes/4, insert_or_update_node/5, does_path_match/3, - walk_down_the_tree/5]). + walk_down_the_tree/5, + + convert_tree/3]). -type tree_node() :: #node{}. %% A node in the tree structure. --type tree() :: #tree{}. +-type tree_v0() :: #tree{keep_while_conds_revidx :: + khepri_tree:keep_while_conds_revidx_v0()}. +-type tree_v1() :: #tree{keep_while_conds_revidx :: + khepri_tree:keep_while_conds_revidx_v1()}. + +-type tree() :: tree_v0() | tree_v1(). -type keep_while_conds_map() :: #{khepri_path:native_path() => khepri_condition:native_keep_while()}. %% Per-node `keep_while' conditions. --type keep_while_conds_revidx() :: #{khepri_path:native_path() => - #{khepri_path:native_path() => ok}}. +-type keep_while_conds_revidx_v0() :: #{khepri_path:native_path() => + #{khepri_path:native_path() => ok}}. + +-type keep_while_conds_revidx_v1() :: khepri_prefix_tree:tree( + #{khepri_path:native_path() => ok}). + +-opaque keep_while_conds_revidx() :: keep_while_conds_revidx_v0() | + keep_while_conds_revidx_v1(). %% Internal reverse index of the keep_while conditions. If node A depends on a %% condition on node B, then this reverse index will have a "node B => node A" %% entry. @@ -67,8 +80,12 @@ -type ok(Type1, Type2, Type3) :: {ok, Type1, Type2, Type3}. -export_type([tree_node/0, + tree_v0/0, + tree_v1/0, tree/0, keep_while_conds_map/0, + keep_while_conds_revidx_v0/0, + keep_while_conds_revidx_v1/0, keep_while_conds_revidx/0, applied_changes/0]). @@ -314,6 +331,19 @@ update_keep_while_conds(Tree, Watcher, KeepWhile) -> KeepWhile :: khepri_condition:native_keep_while(). update_keep_while_conds_revidx( + #tree{keep_while_conds_revidx = KeepWhileCondsRevIdx} = Tree, + Watcher, KeepWhile) -> + case is_v1_keep_while_conds_revidx(KeepWhileCondsRevIdx) of + true -> + update_keep_while_conds_revidx_v1(Tree, Watcher, KeepWhile); + false -> + update_keep_while_conds_revidx_v0(Tree, Watcher, KeepWhile) + end. + +is_v1_keep_while_conds_revidx(KeepWhileCondsRevIdx) -> + khepri_prefix_tree:is_prefix_tree(KeepWhileCondsRevIdx). + +update_keep_while_conds_revidx_v0( #tree{keep_while_conds = KeepWhileConds, keep_while_conds_revidx = KeepWhileCondsRevIdx} = Tree, Watcher, KeepWhile) -> @@ -338,6 +368,37 @@ update_keep_while_conds_revidx( end, KeepWhileCondsRevIdx1, KeepWhile), Tree#tree{keep_while_conds_revidx = KeepWhileCondsRevIdx2}. +update_keep_while_conds_revidx_v1( + #tree{keep_while_conds = KeepWhileConds, + keep_while_conds_revidx = KeepWhileCondsRevIdx} = Tree, + Watcher, KeepWhile) -> + %% First, clean up reversed index where a watched path isn't watched + %% anymore in the new keep_while. + OldWatcheds = maps:get(Watcher, KeepWhileConds, #{}), + KeepWhileCondsRevIdx1 = maps:fold( + fun(Watched, _, KWRevIdx) -> + khepri_prefix_tree:update( + fun(Watchers) -> + Watchers1 = maps:remove( + Watcher, Watchers), + case maps:size(Watchers1) of + 0 -> ?NO_PAYLOAD; + _ -> Watchers1 + end + end, Watched, KWRevIdx) + end, KeepWhileCondsRevIdx, OldWatcheds), + %% Then, record the watched paths. + KeepWhileCondsRevIdx2 = maps:fold( + fun(Watched, _, KWRevIdx) -> + khepri_prefix_tree:update( + fun (?NO_PAYLOAD) -> + #{Watcher => ok}; + (Watchers) -> + Watchers#{Watcher => ok} + end, Watched, KWRevIdx) + end, KeepWhileCondsRevIdx1, KeepWhile), + Tree#tree{keep_while_conds_revidx = KeepWhileCondsRevIdx2}. + %% ------------------------------------------------------------------- %% Find matching nodes. %% ------------------------------------------------------------------- @@ -1294,6 +1355,16 @@ eval_keep_while_conditions( %% %% Those modified in AppliedChanges must be evaluated again to decide %% if they should be removed. + case is_v1_keep_while_conds_revidx(KeepWhileCondsRevIdx) of + true -> + eval_keep_while_conditions_v1(Tree, AppliedChanges); + false -> + eval_keep_while_conditions_v0(Tree, AppliedChanges) + end. + +eval_keep_while_conditions_v0( + #tree{keep_while_conds_revidx = KeepWhileCondsRevIdx} = Tree, + AppliedChanges) -> maps:fold( fun (RemovedPath, delete, ToDelete) -> @@ -1317,6 +1388,29 @@ eval_keep_while_conditions( end end, #{}, AppliedChanges). +eval_keep_while_conditions_v1( + #tree{keep_while_conds_revidx = KeepWhileCondsRevIdx} = Tree, + AppliedChanges) -> + maps:fold( + fun + (RemovedPath, delete, ToDelete) -> + khepri_prefix_tree:fold_prefixes_of( + fun(Watchers, ToDelete1) -> + eval_keep_while_conditions_after_removal( + Tree, Watchers, ToDelete1) + end, ToDelete, RemovedPath, KeepWhileCondsRevIdx); + (UpdatedPath, NodeProps, ToDelete) -> + Result = khepri_prefix_tree:find_path( + UpdatedPath, KeepWhileCondsRevIdx), + case Result of + {ok, Watchers} -> + eval_keep_while_conditions_after_update( + Tree, UpdatedPath, NodeProps, Watchers, ToDelete); + error -> + ToDelete + end + end, #{}, AppliedChanges). + eval_keep_while_conditions_after_update( #tree{keep_while_conds = KeepWhileConds} = Tree, UpdatedPath, NodeProps, Watchers, ToDelete) -> @@ -1399,3 +1493,20 @@ remove_expired_nodes( applied_changes = AppliedChanges2}, remove_expired_nodes(Rest, Walk1) end. + +%% ------------------------------------------------------------------- +%% Conversion between tree versions. +%% ------------------------------------------------------------------- + +convert_tree(Tree, MacVer, MacVer) -> + Tree; +convert_tree(Tree, 0, 1) -> + Tree; +convert_tree(Tree, 1, 2) -> + %% In version 2 the reverse index for keep while conditions was converted + %% into a prefix tree. See the `keep_while_conds_revidx_v0()' and + %% `keep_while_conds_revidx_v1()` types. + #tree{keep_while_conds_revidx = KeepWhileCondsRevIdxV0} = Tree, + KeepWhileCondsRevIdxV1 = khepri_prefix_tree:from_map( + KeepWhileCondsRevIdxV0), + Tree#tree{keep_while_conds_revidx = KeepWhileCondsRevIdxV1}. diff --git a/src/khepri_tree.hrl b/src/khepri_tree.hrl index deb4a728..35aaccb8 100644 --- a/src/khepri_tree.hrl +++ b/src/khepri_tree.hrl @@ -24,5 +24,4 @@ -record(tree, {root = #node{} :: khepri_tree:tree_node(), keep_while_conds = #{} :: khepri_tree:keep_while_conds_map(), - keep_while_conds_revidx = #{} :: - khepri_tree:keep_while_conds_revidx()}). + keep_while_conds_revidx = #{}}).