2015-04-06 05:16:20 +00:00
%% -------------------------------------------------------------------
%%
%% Machi: a small village of replicated files
%%
%% Copyright (c) 2014-2015 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
2015-04-08 05:24:07 +00:00
%% @doc The Machi chain manager, Guardian of all things related to
%% Chain Replication state, status, and data replica safety.
%%
%% The Chain Manager is responsible for managing the state of Machi's
%% "Chain Replication" state. This role is roughly analogous to the
%% "Riak Core" application inside of Riak, which takes care of
%% coordinating replica placement and replica repair.
%%
%% For each primitive data server in the cluster, a Machi FLU, there
%% is a Chain Manager process that manages its FLU's role within the
%% Machi cluster's Chain Replication scheme. Each Chain Manager
%% process executes locally and independently to manage the
%% distributed state of a single Machi Chain Replication chain.
%%
%% Machi's Chain Manager process performs similar tasks as Riak Core's
%% claimant. However, Machi has several active Chain Manager
%% processes, one per FLU server, instead of a single active process
%% like Core's claimant. Each Chain Manager process acts
%% independently; each is constrained so that it will reach consensus
%% via independent computation & action.
2015-04-06 05:16:20 +00:00
- module ( machi_chain_manager1 ) .
%% TODO: I am going to sever the connection between the flowchart and the
%% code. That diagram is really valuable, but it also takes a long time
%% to make any kind of edit; the process is too slow. This is a todo
%% item a reminder that the flowchart is important documentation and
%% must be brought back into sync with the code soon.
- behaviour ( gen_server ) .
- include ( " machi_projection.hrl " ) .
- include ( " machi_chain_manager.hrl " ) .
2015-07-16 07:01:53 +00:00
- include ( " machi_verbose.hrl " ) .
2015-04-06 05:16:20 +00:00
2015-05-11 10:50:13 +00:00
- record ( ch_mgr , {
name : : pv1_server ( ) ,
proj : : projection ( ) ,
2015-08-27 08:58:43 +00:00
proj_unanimous : : 'false' | erlang : timestamp ( ) ,
2015-05-11 10:50:13 +00:00
%%
timer : : 'undefined' | timer : tref ( ) ,
ignore_timer : : boolean ( ) ,
proj_history : : queue : queue ( ) ,
2015-07-18 15:43:10 +00:00
not_sanes : : orddict : orddict ( ) ,
2015-07-20 05:04:25 +00:00
sane_transitions = 0 : : non_neg_integer ( ) ,
2015-08-05 07:05:03 +00:00
consistency_mode : : 'ap_mode' | 'cp_mode' ,
2015-05-11 10:50:13 +00:00
repair_worker : : 'undefined' | pid ( ) ,
repair_start : : 'undefined' | erlang : timestamp ( ) ,
repair_final_status : : 'undefined' | term ( ) ,
runenv : : list ( ) , %proplist()
opts : : list ( ) , %proplist()
members_dict : : p_srvr_dict ( ) ,
2015-09-04 09:51:01 +00:00
proxies_dict : : orddict : orddict ( ) ,
2015-09-08 07:11:54 +00:00
last_down : : list ( ) ,
fitness_svr : : atom ( )
2015-05-11 10:50:13 +00:00
} ) .
2015-04-06 11:07:39 +00:00
- define ( FLU_PC , machi_proxy_flu1_client ) .
2015-04-09 05:44:58 +00:00
- define ( TO , ( 2 * 1000 ) ) . % default timeout
2015-04-06 11:07:39 +00:00
2015-04-06 05:16:20 +00:00
%% Keep a history of our flowchart execution in the process dictionary.
- define ( REACT ( T ) , put ( react , [ T | get ( react ) ] ) ) .
2015-05-11 10:50:13 +00:00
%% Define the period of private projection stability before we'll
%% start repair.
- ifdef ( TEST ) .
2015-05-16 08:39:58 +00:00
- define ( REPAIR_START_STABILITY_TIME , 3 ) .
2015-05-11 10:50:13 +00:00
- else . % TEST
2015-05-16 08:39:58 +00:00
- define ( REPAIR_START_STABILITY_TIME , 10 ) .
2015-05-11 10:50:13 +00:00
- endif . % TEST
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
%% Magic constant for looping "too frequently" breaker. TODO revisit & revise.
- define ( TOO_FREQUENT_BREAKER , 10 ) .
2015-07-05 05:52:50 +00:00
- define ( RETURN2 ( X ) , begin ( catch put ( why2 , [ ? LINE | get ( why2 ) ] ) ) , X end ) .
2015-07-03 14:17:34 +00:00
2015-08-13 05:21:31 +00:00
%% This rank is used if a majority quorum is not available.
- define ( RANK_CP_MINORITY_QUORUM , - 99 ) .
2015-08-14 08:05:16 +00:00
%% Amount of epoch number skip-ahead for set_chain_members call
- define ( SET_CHAIN_MEMBERS_EPOCH_SKIP , 1111 ) .
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
%% Maximum length of the history of adopted projections (via C120).
- define ( MAX_HISTORY_LENGTH , 30 ) .
2015-04-06 05:16:20 +00:00
%% API
2015-05-01 15:33:49 +00:00
- export ( [ start_link / 2 , start_link / 3 , stop / 1 , ping / 1 ,
2015-07-21 09:43:59 +00:00
set_chain_members / 2 , set_chain_members / 3 , set_active / 2 ,
2015-09-09 10:09:39 +00:00
trigger_react_to_env / 1 ] ) .
2015-04-06 05:16:20 +00:00
- export ( [ init / 1 , handle_call / 3 , handle_cast / 2 , handle_info / 2 ,
terminate / 2 , code_change / 3 ] ) .
2015-05-08 09:17:41 +00:00
- export ( [ make_chmgr_regname / 1 , projection_transitions_are_sane / 2 ,
2015-07-03 10:21:41 +00:00
simple_chain_state_transition_is_sane / 3 ,
2015-07-03 13:05:35 +00:00
simple_chain_state_transition_is_sane / 5 ,
2015-09-11 08:32:52 +00:00
chain_state_transition_is_sane / 6 ] ) .
2015-07-03 10:21:41 +00:00
%% Exports so that EDoc docs generated for these internal funcs.
- export ( [ mk / 3 ] ) .
2015-04-06 05:16:20 +00:00
2015-08-25 09:43:55 +00:00
%% Exports for developer/debugging
- export ( [ scan_dir / 4 , strip_dbg2 / 1 ,
2015-09-04 08:17:49 +00:00
get_ps / 2 , has_not_sane / 2 ] ) .
2015-08-25 09:43:55 +00:00
2015-04-06 05:16:20 +00:00
- ifdef ( TEST ) .
- export ( [ test_calc_projection / 2 ,
2015-04-09 05:44:58 +00:00
test_write_public_projection / 2 ,
2015-09-04 08:17:49 +00:00
test_read_latest_public_projection / 2 ] ) .
2015-09-11 07:45:29 +00:00
- export ( [ perhaps_call / 5 , % for partition simulator use w/machi_fitness
init_remember_down_list / 0 , update_remember_down_list / 1 ,
get_remember_down_list / 0 ] ) .
2015-04-06 05:16:20 +00:00
- ifdef ( EQC ) .
- include_lib ( " eqc/include/eqc.hrl " ) .
- endif .
- ifdef ( PULSE ) .
- compile ( { parse_transform , pulse_instrument } ) .
2015-07-16 07:25:38 +00:00
- include_lib ( " pulse_otp/include/pulse_otp.hrl " ) .
2015-04-06 05:16:20 +00:00
- endif .
- include_lib ( " eunit/include/eunit.hrl " ) .
- compile ( export_all ) .
- endif . %TEST
2015-04-09 08:13:38 +00:00
start_link ( MyName , MembersDict ) - >
start_link ( MyName , MembersDict , [ ] ) .
2015-04-06 05:16:20 +00:00
2015-04-09 08:13:38 +00:00
start_link ( MyName , MembersDict , MgrOpts ) - >
2015-05-02 07:59:28 +00:00
gen_server : start_link ( { local , make_chmgr_regname ( MyName ) } , ? MODULE ,
2015-04-30 08:28:43 +00:00
{ MyName , MembersDict , MgrOpts } , [ ] ) .
2015-04-06 05:16:20 +00:00
stop ( Pid ) - >
gen_server : call ( Pid , { stop } , infinity ) .
ping ( Pid ) - >
gen_server : call ( Pid , { ping } , infinity ) .
2015-05-07 09:39:39 +00:00
%% @doc Set chain members list.
%%
%% NOTE: This implementation is a bit brittle, in that an author with
%% higher rank may try to re-suggest the old membership list if it
%% races with an author of lower rank. For now, we suggest calling
%% set_chain_members() first on the author of highest rank and finish
%% with lowest rank, i.e. name z* first, name a* last.
2015-05-01 15:33:49 +00:00
set_chain_members ( Pid , MembersDict ) - >
2015-07-21 09:43:59 +00:00
set_chain_members ( Pid , MembersDict , [ ] ) .
set_chain_members ( Pid , MembersDict , Witness_list ) - >
case lists : all ( fun ( Witness ) - > orddict : is_key ( Witness , MembersDict ) end ,
Witness_list ) of
true - >
Cmd = { set_chain_members , MembersDict , Witness_list } ,
gen_server : call ( Pid , Cmd , infinity ) ;
false - >
{ error , bad_arg }
end .
2015-05-01 15:33:49 +00:00
2015-05-02 07:59:28 +00:00
set_active ( Pid , Boolean ) when Boolean == true ; Boolean == false - >
gen_server : call ( Pid , { set_active , Boolean } , infinity ) .
2015-07-03 07:18:40 +00:00
trigger_react_to_env ( Pid ) - >
gen_server : call ( Pid , { trigger_react_to_env } , infinity ) .
2015-04-06 05:16:20 +00:00
- ifdef ( TEST ) .
%% Test/debugging code only.
2015-04-09 05:44:58 +00:00
test_write_public_projection ( Pid , Proj ) - >
gen_server : call ( Pid , { test_write_public_projection , Proj } , infinity ) .
2015-04-06 05:16:20 +00:00
%% Calculate a projection and return it to us.
%% If KeepRunenvP is true, the server will retain its change in its
%% runtime environment, e.g., changes in simulated network partitions.
test_calc_projection ( Pid , KeepRunenvP ) - >
gen_server : call ( Pid , { test_calc_projection , KeepRunenvP } , infinity ) .
test_read_latest_public_projection ( Pid , ReadRepairP ) - >
gen_server : call ( Pid , { test_read_latest_public_projection , ReadRepairP } ,
infinity ) .
- endif . % TEST
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015-05-01 05:51:42 +00:00
%% Bootstrapping is a hassle ... when when isn't it?
%%
%% If InitMembersDict == [], then we don't know anything about the chain
%% that we'll be participating in. We'll have to wait for directions from
%% our sysadmin later.
%%
%% If InitMembersDict /= [], then we do know what chain we're
%% participating in. It's probably test code, since that's about the
%% only time that we know so much at init() time.
%%
%% In either case, we'll try to create & store an epoch 0 projection
%% and store it to both projections stores. This is tricky if
%% InitMembersDict == [] because InitMembersDict usually contains the
%% #p_svrv records that we need to *write* to the projection store,
%% even our own private store! For test code, we get the store
%% manager's pid in MgrOpts and use direct gen_server calls to the
%% local projection store.
init ( { MyName , InitMembersDict , MgrOpts } ) - >
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
random : seed ( now ( ) ) ,
2015-09-08 07:11:54 +00:00
init_remember_down_list ( ) ,
2015-08-11 06:24:26 +00:00
Opt = fun ( Key , Default ) - > proplists : get_value ( Key , MgrOpts , Default ) end ,
InitWitness_list = Opt ( witnesses , [ ] ) ,
2015-05-01 05:51:42 +00:00
ZeroAll_list = [ P #p_srvr.name | | { _ , P } < - orddict : to_list ( InitMembersDict ) ] ,
2015-08-30 10:53:47 +00:00
ZeroProj = make_none_projection ( 0 , MyName , ZeroAll_list ,
2015-08-11 06:24:26 +00:00
InitWitness_list , InitMembersDict ) ,
2015-05-01 05:51:42 +00:00
ok = store_zeroth_projection_maybe ( ZeroProj , MgrOpts ) ,
2015-08-26 05:54:01 +00:00
CMode = Opt ( consistency_mode , ap_mode ) ,
case get_projection_store_regname ( MgrOpts ) of
undefined - >
ok ;
PS - >
ok = set_consistency_mode ( PS , CMode )
end ,
2015-05-01 05:51:42 +00:00
2015-08-06 10:24:14 +00:00
%% Using whatever is the largest epoch number in our local private
%% store, this manager starts out using the "none" projection. If
%% other members of the chain are running, then we'll simply adopt
%% whatever they're using as a basis for our next suggested
%% projection.
%%
%% If we're in CP mode, we have to be very careful about who we
%% choose to be UPI members when we (or anyone else) restarts.
%% However, that choice is *not* made here: it is made later
%% during our first humming consensus iteration. When we start
%% with the none projection, we're make a safe choice before
%% wading into the deep waters.
2015-08-06 08:48:22 +00:00
{ MembersDict , Proj0 } =
2015-05-06 02:41:04 +00:00
get_my_private_proj_boot_info ( MgrOpts , InitMembersDict , ZeroProj ) ,
2015-08-06 10:24:14 +00:00
#projection_v1 { epoch_number = CurrentEpoch ,
all_members = All_list , witnesses = Witness_list } = Proj0 ,
2015-08-30 10:53:47 +00:00
Proj = make_none_projection ( CurrentEpoch ,
MyName , All_list , Witness_list , MembersDict ) ,
2015-08-06 08:48:22 +00:00
2015-04-09 08:13:38 +00:00
RunEnv = [ { seed , Opt ( seed , now ( ) ) } ,
2015-05-08 04:40:44 +00:00
{ use_partition_simulator , Opt ( use_partition_simulator , false ) } ,
2015-07-20 07:25:42 +00:00
{ simulate_repair , Opt ( simulate_repair , true ) } ,
2015-04-09 08:13:38 +00:00
{ network_partitions , Opt ( network_partitions , [ ] ) } ,
{ network_islands , Opt ( network_islands , [ ] ) } ,
2015-09-04 09:51:01 +00:00
{ last_up_nodes , [ ] } ,
{ last_up_nodes_time , now ( ) } ,
{ up_nodes , Opt ( up_nodes , [ ] ) } ] ,
2015-05-06 02:41:04 +00:00
ActiveP = Opt ( active_mode , true ) ,
2015-08-26 09:47:39 +00:00
S = set_proj ( #ch_mgr { name = MyName ,
2015-09-04 09:51:01 +00:00
timer = 'undefined' ,
proj_history = queue : new ( ) ,
not_sanes = orddict : new ( ) ,
consistency_mode = CMode ,
runenv = RunEnv ,
opts = MgrOpts ,
2015-09-09 10:09:39 +00:00
last_down = [ no_such_server_initial_value_only ] ,
2015-09-08 07:11:54 +00:00
fitness_svr = machi_flu_psup : make_fitness_regname ( MyName )
} , Proj ) ,
2015-05-07 08:52:16 +00:00
{ _ , S2 } = do_set_chain_members_dict ( MembersDict , S ) ,
2015-05-01 15:33:49 +00:00
S3 = if ActiveP == false - >
S2 ;
2015-04-09 08:13:38 +00:00
ActiveP == true - >
2015-05-01 15:33:49 +00:00
set_active_timer ( S2 )
2015-04-09 08:13:38 +00:00
end ,
2015-05-01 15:33:49 +00:00
{ ok , S3 } .
2015-04-06 05:16:20 +00:00
2015-04-09 03:16:58 +00:00
handle_call ( { ping } , _ From , S ) - >
{ reply , pong , S } ;
2015-07-21 09:43:59 +00:00
handle_call ( { set_chain_members , MembersDict , Witness_list } , _ From ,
2015-05-07 08:52:16 +00:00
#ch_mgr { name = MyName ,
proj = #projection_v1 { all_members = OldAll_list ,
epoch_number = OldEpoch ,
upi = OldUPI } = OldProj } = S ) - >
{ Reply , S2 } = do_set_chain_members_dict ( MembersDict , S ) ,
%% TODO: should there be any additional sanity checks? Right now,
%% if someone does something bad, then do_react_to_env() will
%% crash, which will crash us, and we'll restart in a sane & old
%% config.
All_list = [ P #p_srvr.name | | { _ , P } < - orddict : to_list ( MembersDict ) ] ,
MissingInNew = OldAll_list -- All_list ,
2015-08-30 11:39:58 +00:00
{ NewUPI , NewDown } = if OldEpoch == 0 - >
{ All_list , [ ] } ;
true - >
NUPI = OldUPI -- MissingInNew ,
{ NUPI , All_list -- NUPI }
end ,
2015-08-14 08:05:16 +00:00
NewEpoch = OldEpoch + ? SET_CHAIN_MEMBERS_EPOCH_SKIP ,
2015-08-26 09:47:39 +00:00
CMode = calc_consistency_mode ( Witness_list ) ,
2015-08-26 05:54:01 +00:00
ok = set_consistency_mode ( machi_flu_psup : make_proj_supname ( MyName ) , CMode ) ,
2015-05-07 08:52:16 +00:00
NewProj = machi_projection : update_checksum (
OldProj #projection_v1 { author_server = MyName ,
creation_time = now ( ) ,
2015-08-13 15:12:13 +00:00
mode = CMode ,
2015-05-07 08:52:16 +00:00
epoch_number = NewEpoch ,
all_members = All_list ,
2015-07-21 09:43:59 +00:00
witnesses = Witness_list ,
2015-05-07 08:52:16 +00:00
upi = NewUPI ,
repairing = [ ] ,
down = NewDown ,
members_dict = MembersDict } ) ,
2015-09-13 05:16:02 +00:00
S3 = set_proj ( S2 #ch_mgr { proj_history = queue : new ( ) ,
consistency_mode = CMode } , NewProj ) ,
2015-05-07 08:52:16 +00:00
{ _ QQ , S4 } = do_react_to_env ( S3 ) ,
{ reply , Reply , S4 } ;
2015-05-02 07:59:28 +00:00
handle_call ( { set_active , Boolean } , _ From , #ch_mgr { timer = TRef } = S ) - >
case { Boolean , TRef } of
{ true , undefined } - >
S2 = set_active_timer ( S ) ,
{ reply , ok , S2 } ;
{ false , _ } - >
( catch timer : cancel ( TRef ) ) ,
{ reply , ok , S #ch_mgr { timer = undefined } } ;
_ - >
{ reply , error , S }
end ;
2015-04-09 03:16:58 +00:00
handle_call ( { stop } , _ From , S ) - >
{ stop , normal , ok , S } ;
2015-04-06 05:16:20 +00:00
handle_call ( { test_calc_projection , KeepRunenvP } , _ From ,
#ch_mgr { name = MyName } = S ) - >
RelativeToServer = MyName ,
2015-08-11 06:24:26 +00:00
{ P , S2 , _ Up } = calc_projection ( S , RelativeToServer ) ,
2015-04-06 05:16:20 +00:00
{ reply , { ok , P } , if KeepRunenvP - > S2 ;
true - > S
end } ;
2015-04-09 05:44:58 +00:00
handle_call ( { test_write_public_projection , Proj } , _ From , S ) - >
{ Res , S2 } = do_cl_write_public_proj ( Proj , S ) ,
{ reply , Res , S2 } ;
2015-04-06 05:16:20 +00:00
handle_call ( { test_read_latest_public_projection , ReadRepairP } , _ From , S ) - >
{ Perhaps , Val , ExtraInfo , S2 } =
do_cl_read_latest_public_projection ( ReadRepairP , S ) ,
Res = { Perhaps , Val , ExtraInfo } ,
{ reply , Res , S2 } ;
2015-07-03 07:18:40 +00:00
handle_call ( { trigger_react_to_env } = Call , _ From , S ) - >
2015-07-02 19:30:05 +00:00
gobble_calls ( Call ) ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
{ TODOtodo , S2 } = do_react_to_env ( S ) ,
2015-04-06 05:16:20 +00:00
{ reply , TODOtodo , S2 } ;
handle_call ( _ Call , _ From , S ) - >
2015-08-06 08:48:22 +00:00
io : format ( user , " \n Bad call to ~p : ~p \n " , [ S #ch_mgr.name , _ Call ] ) ,
2015-04-06 05:16:20 +00:00
{ reply , whaaaaaaaaaa , S } .
handle_cast ( _ Cast , S ) - >
? D ( { cast_whaaaaaaaaaaa , _ Cast } ) ,
{ noreply , S } .
2015-05-07 08:52:16 +00:00
handle_info ( tick_check_environment , #ch_mgr { ignore_timer = true } = S ) - >
{ noreply , S } ;
handle_info ( tick_check_environment , S ) - >
2015-05-11 10:50:13 +00:00
{ { _ Delta , Props , _ Epoch } , S1 } = do_react_to_env ( S ) ,
S2 = sanitize_repair_state ( S1 ) ,
S3 = perhaps_start_repair ( S2 ) ,
2015-05-06 02:41:04 +00:00
case proplists : get_value ( throttle_seconds , Props ) of
N when is_integer ( N ) , N > 0 - >
2015-05-07 08:52:16 +00:00
%% We are flapping. Set ignore_timer=true and schedule a
%% reminder to stop ignoring. This slows down the rate of
2015-07-02 19:30:05 +00:00
%% flapping.
2015-05-07 08:52:16 +00:00
erlang : send_after ( N * 1000 , self ( ) , stop_ignoring_timer ) ,
2015-05-11 10:50:13 +00:00
{ noreply , S3 #ch_mgr { ignore_timer = true } } ;
2015-05-06 02:41:04 +00:00
_ - >
2015-05-11 10:50:13 +00:00
{ noreply , S3 }
2015-05-07 08:52:16 +00:00
end ;
handle_info ( stop_ignoring_timer , S ) - >
{ noreply , S #ch_mgr { ignore_timer = false } } ;
2015-05-11 10:50:13 +00:00
handle_info ( { 'DOWN' , _ Ref , process , Worker , Res } ,
#ch_mgr { repair_worker = Worker } = S ) - >
{ noreply , S #ch_mgr { ignore_timer = false ,
repair_worker = undefined ,
repair_final_status = Res } } ;
Tweak IsRelevantToMe_p in B10 (more)
Last night we hit a rare case of failed convergence.
f was out of sync with the rest of the world.
f: upi=[b,g,f] repairing=[a,c]
The "rest of the world" used a larger chain at:
*: upi=[c,b,g,a], repairing=[f]
And f refused to join the larger chain because of the way that
IsRelevantToMe_p was being calculated before this commit.
Hrrrm, though, I'm not convinced that this particular problem
is fixed 100% by this patch. What if the chain lengths were
the same but also UPI incompatible? e.g. if I remove 'a' from
the "real world (in the partition simulator)" example above:
f: upi=[b,g,f] repairing=[c]
*: upi=[c,b,g], repairing=[f]
Hrmmmmm, I may need to reintroduce the my-recent-adopted-projection-
flapping-like-counter thingie to try to break this kind of
incompatible deadlock.
2015-09-14 04:40:34 +00:00
handle_info ( Msg , #ch_mgr { name = MyName } = S ) - >
case get ( todo_bummer ) of undefined - > io : format ( " TODO: ~w got ~p \n " ,
[ MyName , Msg ] ) ;
2015-04-30 08:28:43 +00:00
_ - > ok
end ,
put ( todo_bummer , true ) ,
2015-04-06 05:16:20 +00:00
{ noreply , S } .
terminate ( _ Reason , _ S ) - >
ok .
code_change ( _ OldVsn , S , _ Extra ) - >
{ ok , S } .
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015-08-30 10:53:47 +00:00
make_none_projection ( Epoch , MyName , All_list , Witness_list , MembersDict ) - >
2015-04-14 06:30:24 +00:00
Down_list = All_list ,
UPI_list = [ ] ,
2015-08-05 03:53:20 +00:00
P = machi_projection : new ( MyName , MembersDict , Down_list , UPI_list , [ ] , [ ] ) ,
2015-08-13 15:12:13 +00:00
CMode = if Witness_list == [ ] - >
ap_mode ;
Witness_list / = [ ] - >
cp_mode
end ,
2015-08-30 10:53:47 +00:00
machi_projection : update_checksum ( P #projection_v1 { epoch_number = Epoch ,
mode = CMode ,
2015-08-13 15:12:13 +00:00
witnesses = Witness_list } ) .
2015-04-14 06:30:24 +00:00
2015-08-12 08:53:39 +00:00
make_all_projection ( MyName , All_list , Witness_list , MembersDict ) - >
Down_list = [ ] ,
UPI_list = All_list ,
P = machi_projection : new ( MyName , MembersDict , Down_list , UPI_list , [ ] , [ ] ) ,
machi_projection : update_checksum ( P #projection_v1 { witnesses = Witness_list } ) .
2015-05-06 02:41:04 +00:00
get_my_private_proj_boot_info ( MgrOpts , DefaultDict , DefaultProj ) - >
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
get_my_proj_boot_info ( MgrOpts , DefaultDict , DefaultProj , private ) .
get_my_public_proj_boot_info ( MgrOpts , DefaultDict , DefaultProj ) - >
get_my_proj_boot_info ( MgrOpts , DefaultDict , DefaultProj , public ) .
get_my_proj_boot_info ( MgrOpts , DefaultDict , DefaultProj , ProjType ) - >
2015-05-01 05:51:42 +00:00
case proplists : get_value ( projection_store_registered_name , MgrOpts ) of
undefined - >
2015-05-06 02:41:04 +00:00
{ DefaultDict , DefaultProj } ;
2015-05-01 05:51:42 +00:00
Store - >
{ ok , P } = machi_projection_store : read_latest_projection ( Store ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
ProjType ) ,
2015-05-06 02:41:04 +00:00
{ P #projection_v1.members_dict , P }
2015-05-01 05:51:42 +00:00
end .
%% Write the epoch 0 projection store, to assist bootstrapping. If the
%% 0th epoch is already written, there's no problem.
store_zeroth_projection_maybe ( ZeroProj , MgrOpts ) - >
2015-08-26 05:54:01 +00:00
case get_projection_store_regname ( MgrOpts ) of
2015-05-01 05:51:42 +00:00
undefined - >
ok ;
Store - >
_ = machi_projection_store : write ( Store , public , ZeroProj ) ,
_ = machi_projection_store : write ( Store , private , ZeroProj ) ,
ok
end .
2015-08-26 05:54:01 +00:00
get_projection_store_regname ( MgrOpts ) - >
proplists : get_value ( projection_store_registered_name , MgrOpts ) .
2015-08-26 09:47:39 +00:00
set_consistency_mode ( undefined , _ CMode ) - >
2015-08-26 05:54:01 +00:00
ok ;
set_consistency_mode ( ProjStore , CMode ) - >
machi_projection_store : set_consistency_mode ( ProjStore , CMode ) .
2015-04-09 08:13:38 +00:00
set_active_timer ( #ch_mgr { name = MyName , members_dict = MembersDict } = S ) - >
2015-04-09 12:08:15 +00:00
FLU_list = [ P #p_srvr.name | | { _ , P } < - orddict : to_list ( MembersDict ) ] ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
%% Perturb the order a little bit, to avoid near-lock-step
%% operations every few ticks.
MSec = calc_sleep_ranked_order ( 400 , 1500 , MyName , FLU_list ) +
random : uniform ( 100 ) ,
{ ok , TRef } = timer : send_interval ( MSec , tick_check_environment ) ,
2015-04-09 08:13:38 +00:00
S #ch_mgr { timer = TRef } .
2015-04-06 05:16:20 +00:00
2015-04-09 05:44:58 +00:00
do_cl_write_public_proj ( Proj , S ) - >
2015-04-06 05:16:20 +00:00
#projection_v1 { epoch_number = Epoch } = Proj ,
2015-04-09 05:44:58 +00:00
cl_write_public_proj ( Epoch , Proj , S ) .
2015-04-06 05:16:20 +00:00
cl_write_public_proj ( Epoch , Proj , S ) - >
cl_write_public_proj ( Epoch , Proj , false , S ) .
cl_write_public_proj_skip_local_error ( Epoch , Proj , S ) - >
cl_write_public_proj ( Epoch , Proj , true , S ) .
cl_write_public_proj ( Epoch , Proj , SkipLocalWriteErrorP , S ) - >
%% Write to local public projection store first, and if it succeeds,
%% then write to all remote public projection stores.
cl_write_public_proj_local ( Epoch , Proj , SkipLocalWriteErrorP , S ) .
cl_write_public_proj_local ( Epoch , Proj , SkipLocalWriteErrorP ,
2015-04-09 08:13:38 +00:00
#ch_mgr { name = MyName } = S ) - >
2015-04-06 05:16:20 +00:00
{ _ UpNodes , Partitions , S2 } = calc_up_nodes ( S ) ,
Res0 = perhaps_call_t (
2015-04-09 08:13:38 +00:00
S , Partitions , MyName ,
2015-04-09 05:44:58 +00:00
fun ( Pid ) - > ? FLU_PC : write_projection ( Pid , public , Proj , ? TO ) end ) ,
2015-04-06 05:16:20 +00:00
Continue = fun ( ) - >
2015-04-09 08:13:38 +00:00
FLUs = Proj #projection_v1.all_members -- [ MyName ] ,
2015-04-06 05:16:20 +00:00
cl_write_public_proj_remote ( FLUs , Partitions , Epoch , Proj , S )
end ,
case Res0 of
ok - >
{ XX , SS } = Continue ( ) ,
{ { local_write_result , ok , XX } , SS } ;
Else when SkipLocalWriteErrorP - >
{ XX , SS } = Continue ( ) ,
{ { local_write_result , Else , XX } , SS } ;
2015-05-01 15:33:49 +00:00
Else - >
2015-04-06 05:16:20 +00:00
{ Else , S2 }
end .
2015-04-09 05:44:58 +00:00
cl_write_public_proj_remote ( FLUs , Partitions , _ Epoch , Proj , S ) - >
2015-04-06 05:16:20 +00:00
%% We're going to be very care-free about this write because we'll rely
%% on the read side to do any read repair.
2015-04-09 05:44:58 +00:00
DoIt = fun ( Pid ) - > ? FLU_PC : write_projection ( Pid , public , Proj , ? TO ) end ,
Rs = [ { FLU , perhaps_call_t ( S , Partitions , FLU , fun ( Pid ) - > DoIt ( Pid ) end ) } | |
2015-04-06 05:16:20 +00:00
FLU < - FLUs ] ,
{ { remote_write_results , Rs } , S } .
do_cl_read_latest_public_projection ( ReadRepairP ,
2015-05-02 07:59:28 +00:00
#ch_mgr { proj = Proj1 } = S ) - >
2015-04-06 05:16:20 +00:00
_ Epoch1 = Proj1 #projection_v1.epoch_number ,
case cl_read_latest_projection ( public , S ) of
{ needs_repair , FLUsRs , Extra , S3 } - >
if not ReadRepairP - >
2015-04-30 14:16:08 +00:00
{ not_unanimous , todoxyz , [ { unanimous_flus , [ ] } ,
{ results , FLUsRs } | Extra ] , S3 } ;
2015-04-06 05:16:20 +00:00
true - >
{ _ Status , S4 } = do_read_repair ( FLUsRs , Extra , S3 ) ,
do_cl_read_latest_public_projection ( ReadRepairP , S4 )
end ;
2015-05-02 07:59:28 +00:00
{ _ UnanimousTag , _ Proj2 , _ Extra , _ S3 } = Else - >
Else
2015-04-06 05:16:20 +00:00
end .
read_latest_projection_call_only ( ProjectionType , AllHosed ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
#ch_mgr { proj = CurrentProj } = S ) - >
2015-04-06 05:16:20 +00:00
#projection_v1 { all_members = All_list } = CurrentProj ,
All_queried_list = All_list -- AllHosed ,
2015-08-26 09:47:39 +00:00
{ Rs , S2 } = read_latest_projection_call_only2 ( ProjectionType ,
All_queried_list , S ) ,
FLUsRs = lists : zip ( All_queried_list , Rs ) ,
{ All_queried_list , FLUsRs , S2 } .
read_latest_projection_call_only2 ( ProjectionType , All_queried_list , S ) - >
2015-04-06 05:16:20 +00:00
{ _ UpNodes , Partitions , S2 } = calc_up_nodes ( S ) ,
2015-04-09 05:44:58 +00:00
DoIt = fun ( Pid ) - >
2015-05-01 15:33:49 +00:00
case ( ? FLU_PC : read_latest_projection ( Pid , ProjectionType , ? TO ) ) of
2015-04-06 05:16:20 +00:00
{ ok , P } - > P ;
Else - > Else
end
end ,
2015-08-26 09:47:39 +00:00
Rs = [ ( catch perhaps_call_t ( S , Partitions , FLU , fun ( Pid ) - > DoIt ( Pid ) end ) ) | |
2015-04-06 05:16:20 +00:00
FLU < - All_queried_list ] ,
2015-08-26 09:47:39 +00:00
{ Rs , S2 } .
2015-04-06 05:16:20 +00:00
cl_read_latest_projection ( ProjectionType , S ) - >
AllHosed = [ ] ,
cl_read_latest_projection ( ProjectionType , AllHosed , S ) .
cl_read_latest_projection ( ProjectionType , AllHosed , S ) - >
{ All_queried_list , FLUsRs , S2 } =
read_latest_projection_call_only ( ProjectionType , AllHosed , S ) ,
2015-05-18 08:32:22 +00:00
rank_and_sort_projections_with_extra ( All_queried_list , FLUsRs ,
ProjectionType , S2 ) .
2015-04-06 05:16:20 +00:00
2015-05-18 08:32:22 +00:00
rank_and_sort_projections_with_extra ( All_queried_list , FLUsRs , ProjectionType ,
2015-04-30 14:16:08 +00:00
#ch_mgr { name = MyName , proj = CurrentProj } = S ) - >
2015-05-18 08:32:22 +00:00
UnwrittenRs = [ x | | { _ , { error , not_written } } < - FLUsRs ] ,
2015-04-06 05:16:20 +00:00
Ps = [ Proj | | { _ FLU , Proj } < - FLUsRs , is_record ( Proj , projection_v1 ) ] ,
BadAnswerFLUs = [ FLU | | { FLU , Answer } < - FLUsRs ,
not is_record ( Answer , projection_v1 ) ] ,
if All_queried_list == [ ]
orelse
length ( UnwrittenRs ) == length ( FLUsRs ) - >
2015-08-05 06:50:32 +00:00
Witness_list = CurrentProj #projection_v1.witnesses ,
2015-08-30 10:53:47 +00:00
NoneProj = make_none_projection ( 0 , MyName , [ ] , Witness_list ,
2015-08-05 06:50:32 +00:00
orddict : new ( ) ) ,
2015-04-30 14:16:08 +00:00
Extra2 = [ { all_members_replied , true } ,
{ all_queried_list , All_queried_list } ,
{ flus_rs , FLUsRs } ,
{ unanimous_flus , [ ] } ,
{ not_unanimous_flus , [ ] } ,
{ bad_answer_flus , BadAnswerFLUs } ,
2015-09-04 08:17:49 +00:00
{ not_unanimous_answers , [ ] } ] ,
2015-04-30 14:16:08 +00:00
{ not_unanimous , NoneProj , Extra2 , S } ;
2015-05-18 08:32:22 +00:00
ProjectionType == public , UnwrittenRs / = [ ] - >
2015-04-06 05:16:20 +00:00
{ needs_repair , FLUsRs , [ flarfus ] , S } ;
true - >
[ { _ Rank , BestProj } | _ ] = rank_and_sort_projections ( Ps , CurrentProj ) ,
2015-09-09 16:33:55 +00:00
BestEpoch = BestProj #projection_v1.epoch_number ,
2015-04-06 05:16:20 +00:00
NotBestPs = [ Proj | | Proj < - Ps , Proj / = BestProj ] ,
2015-09-09 16:33:55 +00:00
NotBestPsEpochFilt =
[ Proj | | Proj < - Ps , Proj / = BestProj ,
Proj #projection_v1.epoch_number == BestEpoch ] ,
%% Wow, I'm not sure how long this bug has been here, but it's
%% likely 5 months old (April 2015). I just now noticed a problem
%% where BestProj was epoch 1194, but NotBestPs contained a
%% projection at smaller epoch 1192. The test for nonempty
%% NotBestPs list caused the 1194 BestProj to be marked
%% not_unanimous incorrectly. (This can happen in asymmetric
%% partition cases, hooray for crazy corner cases.)
%%
%% We correct the bug by filtering NotBestPs further to include
%% only not-equal projections that also share BestProj's epoch.
%% We'll get the correct answer we seek using this list == []
%% method, as long as rank_and_sort_projections() will always pick
%% a proj with the highest visible epoch.
UnanimousTag = if NotBestPsEpochFilt == [ ] - > unanimous ;
true - > not_unanimous
2015-04-06 05:16:20 +00:00
end ,
Extra = [ { all_members_replied , length ( FLUsRs ) == length ( All_queried_list ) } ] ,
Best_FLUs = [ FLU | | { FLU , Projx } < - FLUsRs , Projx == BestProj ] ,
Extra2 = [ { all_queried_list , All_queried_list } ,
{ flus_rs , FLUsRs } ,
{ unanimous_flus , Best_FLUs } ,
{ not_unanimous_flus , All_queried_list --
( Best_FLUs ++ BadAnswerFLUs ) } ,
{ bad_answer_flus , BadAnswerFLUs } ,
2015-09-09 16:33:55 +00:00
{ not_best_ps , NotBestPs } ,
{ not_best_ps_epoch_filt , NotBestPsEpochFilt } | Extra ] ,
2015-04-06 05:16:20 +00:00
{ UnanimousTag , BestProj , Extra2 , S }
end .
do_read_repair ( FLUsRs , _ Extra , #ch_mgr { proj = CurrentProj } = S ) - >
2015-05-18 08:32:22 +00:00
Unwrittens = [ x | | { _ FLU , { error , not_written } } < - FLUsRs ] ,
2015-04-06 05:16:20 +00:00
Ps = [ Proj | | { _ FLU , Proj } < - FLUsRs , is_record ( Proj , projection_v1 ) ] ,
if Unwrittens == [ ] orelse Ps == [ ] - >
{ nothing_to_do , S } ;
true - >
%% We have at least one unwritten and also at least one proj.
%% Pick the best one, then spam it everywhere.
[ { _ Rank , BestProj } | _ ] = rank_and_sort_projections ( Ps , CurrentProj ) ,
Epoch = BestProj #projection_v1.epoch_number ,
%% We're doing repair, so use the flavor that will
%% continue to all others even if there is an
%% error_written on the local FLU.
{ _ DontCare , _ S2 } = Res = cl_write_public_proj_skip_local_error (
Epoch , BestProj , S ) ,
Res
end .
calc_projection ( S , RelativeToServer ) - >
calc_projection ( S , RelativeToServer , [ ] ) .
2015-09-01 13:10:45 +00:00
calc_projection ( #ch_mgr { proj = P_current } = S , RelativeToServer , AllHosed ) - >
calc_projection ( S , RelativeToServer , AllHosed , P_current ) .
calc_projection ( #ch_mgr { name = MyName , consistency_mode = CMode ,
2015-08-30 11:39:58 +00:00
runenv = RunEnv } = S ,
2015-09-01 13:10:45 +00:00
RelativeToServer , AllHosed , P_current ) - >
2015-04-06 05:16:20 +00:00
Dbg = [ ] ,
2015-08-25 09:43:55 +00:00
%% OldThreshold = proplists:get_value(old_threshold, RunEnv),
%% NoPartitionThreshold = proplists:get_value(no_partition_threshold, RunEnv),
2015-08-12 08:53:39 +00:00
if CMode == ap_mode - >
2015-08-30 11:39:58 +00:00
calc_projection2 ( P_current , RelativeToServer , AllHosed , Dbg , S ) ;
2015-08-12 08:53:39 +00:00
CMode == cp_mode - >
#projection_v1 { epoch_number = OldEpochNum ,
all_members = AllMembers ,
2015-08-25 09:43:55 +00:00
upi = OldUPI_list
2015-08-30 11:39:58 +00:00
} = P_current ,
2015-08-12 08:53:39 +00:00
UPI_length_ok_p =
length ( OldUPI_list ) > = full_majority_size ( AllMembers ) ,
case { OldEpochNum , UPI_length_ok_p } of
{ 0 , _ } - >
2015-08-30 11:39:58 +00:00
calc_projection2 ( P_current , RelativeToServer , AllHosed ,
2015-08-12 08:53:39 +00:00
Dbg , S ) ;
{ _ , true } - >
2015-08-30 11:39:58 +00:00
calc_projection2 ( P_current , RelativeToServer , AllHosed ,
2015-08-12 08:53:39 +00:00
Dbg , S ) ;
{ _ , false } - >
2015-09-04 08:17:49 +00:00
{ Up , _ Partitions , RunEnv2 } = calc_up_nodes (
2015-08-30 11:39:58 +00:00
MyName , AllMembers , RunEnv ) ,
%% We can't improve on the current projection.
{ P_current , S #ch_mgr { runenv = RunEnv2 } , Up }
2015-08-12 08:53:39 +00:00
end
end .
2015-04-06 05:16:20 +00:00
2015-04-10 12:59:56 +00:00
%% AllHosed: FLUs that we must treat as if they are down, e.g., we are
%% in a flapping situation and wish to ignore FLUs that we
%% believe are bad-behaving causes of our flapping.
2015-04-06 05:16:20 +00:00
2015-08-12 08:53:39 +00:00
calc_projection2 ( LastProj , RelativeToServer , AllHosed , Dbg ,
#ch_mgr { name = MyName ,
proj = CurrentProj ,
consistency_mode = CMode ,
runenv = RunEnv1 ,
repair_final_status = RepairFS } = S ) - >
2015-04-06 05:16:20 +00:00
#projection_v1 { epoch_number = OldEpochNum ,
2015-04-09 08:13:38 +00:00
members_dict = MembersDict ,
2015-07-30 20:12:08 +00:00
witnesses = OldWitness_list ,
2015-04-09 08:13:38 +00:00
upi = OldUPI_list ,
2015-08-25 09:43:55 +00:00
repairing = OldRepairing_list
2015-04-09 08:13:38 +00:00
} = LastProj ,
2015-04-06 05:16:20 +00:00
LastUp = lists : usort ( OldUPI_list ++ OldRepairing_list ) ,
AllMembers = ( S #ch_mgr.proj ) #projection_v1.all_members ,
{ Up0 , Partitions , RunEnv2 } = calc_up_nodes ( MyName ,
AllMembers , RunEnv1 ) ,
Up = Up0 -- AllHosed ,
NewUp = Up -- LastUp ,
Down = AllMembers -- Up ,
2015-08-30 10:53:47 +00:00
? REACT ( { calc , ? LINE , [ { old_epoch , OldEpochNum } ,
{ old_upi , OldUPI_list } ,
2015-08-14 13:28:50 +00:00
{ old_repairing , OldRepairing_list } ,
{ last_up , LastUp } , { up0 , Up0 } , { all_hosed , AllHosed } ,
2015-08-14 10:30:05 +00:00
{ up , Up } , { new_up , NewUp } , { down , Down } ] } ) ,
2015-04-06 05:16:20 +00:00
2015-08-05 06:50:32 +00:00
NewUPI_list =
[ X | | X < - OldUPI_list , lists : member ( X , Up ) andalso
not lists : member ( X , OldWitness_list ) ] ,
2015-08-22 12:27:01 +00:00
RepChk_Proj = if AllHosed == [ ] - >
CurrentProj ;
true - >
2015-09-04 08:17:49 +00:00
CurrentProj
2015-08-22 12:27:01 +00:00
end ,
RepChk_LastInUPI = case RepChk_Proj #projection_v1.upi of
2015-07-19 04:32:55 +00:00
[ ] - > does_not_exist_because_upi_is_empty ;
2015-08-22 12:27:01 +00:00
[ _ | _ ] - > lists : last ( RepChk_Proj #projection_v1.upi )
2015-07-19 04:32:55 +00:00
end ,
2015-08-05 06:50:32 +00:00
Repairing_list2 = [ X | | X < - OldRepairing_list ,
lists : member ( X , Up ) ,
not lists : member ( X , OldWitness_list ) ] ,
2015-05-08 06:36:53 +00:00
Simulator_p = proplists : get_value ( use_partition_simulator , RunEnv2 , false ) ,
2015-07-20 07:25:42 +00:00
SimRepair_p = proplists : get_value ( simulate_repair , RunEnv2 , true ) ,
2015-04-06 05:16:20 +00:00
{ NewUPI_list3 , Repairing_list3 , RunEnv3 } =
2015-08-14 10:30:36 +00:00
case { NewUp -- OldWitness_list , Repairing_list2 } of
2015-04-06 05:16:20 +00:00
{ [ ] , [ ] } - >
2015-08-14 08:05:16 +00:00
D_foo = [ d_foo1 ] ,
2015-04-06 05:16:20 +00:00
{ NewUPI_list , [ ] , RunEnv2 } ;
2015-08-22 12:27:01 +00:00
{ [ ] , [ H | T ] } when RelativeToServer == RepChk_LastInUPI - >
2015-05-08 06:36:53 +00:00
%% The author is tail of the UPI list. Let's see if
2015-04-06 05:16:20 +00:00
%% *everyone* in the UPI+repairing lists are using our
%% projection. This is to simulate a requirement that repair
%% a real repair process cannot take place until the chain is
%% stable, i.e. everyone is in the same epoch.
2015-04-10 12:59:56 +00:00
%% TODO create a real API call for fetching this info?
SameEpoch_p = check_latest_private_projections_same_epoch (
2015-06-04 05:31:58 +00:00
NewUPI_list ++ Repairing_list2 ,
2015-08-22 12:27:01 +00:00
RepChk_Proj , Partitions , S ) ,
2015-07-20 07:25:42 +00:00
if Simulator_p andalso SimRepair_p andalso
2015-08-22 12:27:01 +00:00
SameEpoch_p andalso RelativeToServer == RepChk_LastInUPI - >
2015-05-07 08:52:16 +00:00
D_foo = [ { repair_airquote_done , { we_agree , ( S #ch_mgr.proj ) #projection_v1.epoch_number } } ] ,
2015-08-25 08:01:14 +00:00
if CMode == cp_mode - > timer : sleep ( 567 ) ; true - > ok end ,
2015-05-08 06:36:53 +00:00
{ NewUPI_list ++ [ H ] , T , RunEnv2 } ;
2015-07-20 07:25:42 +00:00
not ( Simulator_p andalso SimRepair_p )
2015-05-16 08:39:58 +00:00
andalso
RepairFS == { repair_final_status , ok } - >
D_foo = [ { repair_done , { repair_final_status , ok , ( S #ch_mgr.proj ) #projection_v1.epoch_number } } ] ,
{ NewUPI_list ++ Repairing_list2 , [ ] , RunEnv2 } ;
2015-05-08 06:36:53 +00:00
true - >
2015-08-14 08:05:16 +00:00
D_foo = [ d_foo2 ] ,
2015-05-08 06:36:53 +00:00
{ NewUPI_list , OldRepairing_list , RunEnv2 }
2015-04-06 05:16:20 +00:00
end ;
2015-08-14 10:30:36 +00:00
{ _ ABC , _ XYZ } - >
2015-08-14 08:05:16 +00:00
D_foo = [ d_foo3 , { new_upi_list , NewUPI_list } , { new_up , NewUp } , { repairing_list3 , OldRepairing_list } ] ,
2015-04-06 05:16:20 +00:00
{ NewUPI_list , OldRepairing_list , RunEnv2 }
end ,
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE ,
[ { newupi_list3 , NewUPI_list3 } , { repairing_list3 , Repairing_list3 } ] } ) ,
2015-04-06 05:16:20 +00:00
Repairing_list4 = case NewUp of
[ ] - > Repairing_list3 ;
NewUp - > Repairing_list3 ++ NewUp
end ,
2015-08-05 06:50:32 +00:00
Repairing_list5 = ( Repairing_list4 -- Down ) -- OldWitness_list ,
2015-04-06 05:16:20 +00:00
TentativeUPI = NewUPI_list3 ,
TentativeRepairing = Repairing_list5 ,
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE , [ { tent , TentativeUPI } , { tent_rep , TentativeRepairing } ] } ) ,
2015-04-06 05:16:20 +00:00
{ NewUPI , NewRepairing } =
2015-08-14 08:05:16 +00:00
if ( CMode == ap_mode
orelse
( CMode == cp_mode andalso OldEpochNum == 0 ) )
andalso
TentativeUPI == [ ] andalso TentativeRepairing / = [ ] - >
2015-08-06 06:21:44 +00:00
%% UPI is empty (not including witnesses), so grab
2015-08-05 06:50:32 +00:00
%% the first from the repairing list and make it the
%% only non-witness in the UPI.
2015-04-06 05:16:20 +00:00
[ FirstRepairing | TailRepairing ] = TentativeRepairing ,
2015-08-06 06:21:44 +00:00
{ [ FirstRepairing ] , TailRepairing } ;
2015-04-06 05:16:20 +00:00
true - >
{ TentativeUPI , TentativeRepairing }
end ,
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE , [ { new_upi , NewUPI } , { new_rep , NewRepairing } ] } ) ,
2015-04-06 05:16:20 +00:00
2015-08-05 06:50:32 +00:00
P = machi_projection : new ( OldEpochNum + 1 ,
MyName , MembersDict , Down , NewUPI , NewRepairing ,
D_foo ++
Dbg ++ [ { ps , Partitions } , { nodes_up , Up } ] ) ,
2015-08-06 06:22:04 +00:00
P2 = if CMode == cp_mode - >
UpWitnesses = [ W | | W < - Up , lists : member ( W , OldWitness_list ) ] ,
2015-08-12 08:53:39 +00:00
Majority = full_majority_size ( AllMembers ) ,
2015-08-06 08:48:22 +00:00
SoFar = length ( NewUPI ) ,
if SoFar > = Majority - >
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE , [ ] } ) ,
2015-08-06 08:48:22 +00:00
P ;
true - >
Need = Majority - SoFar ,
UpWitnesses = [ W | | W < - Up ,
lists : member ( W , OldWitness_list ) ] ,
if length ( UpWitnesses ) > = Need - >
Ws = lists : sublist ( UpWitnesses , Need ) ,
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE , [ { ws , Ws } ] } ) ,
2015-08-06 08:48:22 +00:00
machi_projection : update_checksum (
P #projection_v1 { upi = Ws ++ NewUPI } ) ;
true - >
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE , [ ] } ) ,
2015-08-06 08:48:22 +00:00
P_none0 = make_none_projection (
2015-08-30 10:53:47 +00:00
OldEpochNum + 1 ,
2015-08-06 08:48:22 +00:00
MyName , AllMembers , OldWitness_list ,
MembersDict ) ,
2015-08-27 08:58:43 +00:00
Why = if NewUPI == [ ] - >
2015-08-28 12:13:54 +00:00
" No real servers in old upi are available now " ;
2015-08-27 08:58:43 +00:00
true - >
2015-08-28 12:13:54 +00:00
" Not enough witnesses are available now "
2015-08-27 08:58:43 +00:00
end ,
2015-08-06 08:48:22 +00:00
P_none1 = P_none0 #projection_v1 {
2015-08-29 06:06:57 +00:00
%% Stable creation time!
creation_time = { 1 , 2 , 3 } ,
2015-08-06 08:48:22 +00:00
dbg = [ { none_projection , true } ,
2015-08-14 08:05:16 +00:00
{ up0 , Up0 } ,
2015-08-13 12:24:56 +00:00
{ up , Up } ,
{ all_hosed , AllHosed } ,
2015-08-14 13:28:50 +00:00
{ oldupi , OldUPI_list } ,
{ newupi , NewUPI_list } ,
{ newupi3 , NewUPI_list3 } ,
{ tent_upi , TentativeUPI } ,
{ new_upi , NewUPI } ,
{ up_witnesses , UpWitnesses } ,
2015-08-29 10:59:46 +00:00
{ why_none , Why } ] ,
dbg2 = [
{ creation_time , os : timestamp ( ) } ] } ,
2015-08-06 08:48:22 +00:00
machi_projection : update_checksum ( P_none1 )
end
end ;
2015-08-06 06:22:04 +00:00
CMode == ap_mode - >
2015-08-14 08:05:16 +00:00
? REACT ( { calc , ? LINE , [ ] } ) ,
2015-08-06 06:22:04 +00:00
P
end ,
P3 = machi_projection : update_checksum (
2015-08-13 15:12:13 +00:00
P2 #projection_v1 { mode = CMode , witnesses = OldWitness_list } ) ,
2015-08-14 13:28:50 +00:00
? REACT ( { calc , ? LINE , [ machi_projection : make_summary ( P3 ) ] } ) ,
2015-08-11 06:24:26 +00:00
{ P3 , S #ch_mgr { runenv = RunEnv3 } , Up } .
2015-04-06 05:16:20 +00:00
2015-04-10 12:59:56 +00:00
check_latest_private_projections_same_epoch ( FLUs , MyProj , Partitions , S ) - >
2015-08-22 12:27:01 +00:00
#projection_v1 { epoch_number = MyEpoch , epoch_csum = MyCSum } = MyProj ,
2015-06-04 05:31:58 +00:00
%% NOTE: The caller must provide us with the FLUs list for all
%% FLUs that must be up & available right now. So any
%% failure of perhaps_call_t() means that we must return
%% false.
2015-04-06 05:16:20 +00:00
FoldFun = fun ( _ FLU , false ) - >
false ;
( FLU , true ) - >
2015-04-09 05:44:58 +00:00
F = fun ( Pid ) - >
? FLU_PC : read_latest_projection ( Pid , private , ? TO )
2015-04-06 05:16:20 +00:00
end ,
case perhaps_call_t ( S , Partitions , FLU , F ) of
2015-08-22 12:27:01 +00:00
{ ok , RPJ } - >
#projection_v1 { epoch_number = RemoteEpoch ,
2015-09-04 08:17:49 +00:00
epoch_csum = RemoteCSum } = RPJ ,
2015-08-22 12:27:01 +00:00
if MyEpoch == RemoteEpoch ,
MyCSum == RemoteCSum - >
2015-04-06 05:16:20 +00:00
true ;
true - >
false
end ;
2015-08-22 12:27:01 +00:00
_ Else - >
2015-04-06 05:16:20 +00:00
false
end
end ,
lists : foldl ( FoldFun , true , FLUs ) .
calc_up_nodes ( #ch_mgr { name = MyName , proj = Proj , runenv = RunEnv1 } = S ) - >
AllMembers = Proj #projection_v1.all_members ,
{ UpNodes , Partitions , RunEnv2 } =
calc_up_nodes ( MyName , AllMembers , RunEnv1 ) ,
{ UpNodes , Partitions , S #ch_mgr { runenv = RunEnv2 } } .
calc_up_nodes ( MyName , AllMembers , RunEnv1 ) - >
2015-09-04 09:51:01 +00:00
put ( myname_hack , MyName ) ,
2015-05-01 15:33:49 +00:00
case proplists : get_value ( use_partition_simulator , RunEnv1 ) of
true - >
calc_up_nodes_sim ( MyName , AllMembers , RunEnv1 ) ;
false - >
2015-09-08 07:11:54 +00:00
UpNodesNew = ( AllMembers -- get_remember_down_list ( ) ) ,
2015-09-04 09:51:01 +00:00
RunEnv2 = update_runenv_with_up_nodes ( UpNodesNew , RunEnv1 ) ,
{ UpNodesNew , [ ] , RunEnv2 }
end .
update_runenv_with_up_nodes ( UpNodesNew , RunEnv1 ) - >
LastUpNodes0 = proplists : get_value ( last_up_nodes , RunEnv1 ) ,
if UpNodesNew / = LastUpNodes0 - >
replace ( RunEnv1 ,
[ { last_up_nodes , UpNodesNew } ,
{ last_up_nodes_time , now ( ) } ] ) ;
true - >
RunEnv1
2015-05-01 15:33:49 +00:00
end .
calc_up_nodes_sim ( MyName , AllMembers , RunEnv1 ) - >
{ Partitions2 , Islands2 } = machi_partition_simulator : get ( AllMembers ) ,
catch ? REACT ( { calc_up_nodes , ? LINE , [ { partitions , Partitions2 } ,
{ islands , Islands2 } ] } ) ,
2015-04-06 05:16:20 +00:00
UpNodes = lists : sort (
[ Node | | Node < - AllMembers ,
not lists : member ( { MyName , Node } , Partitions2 ) ,
not lists : member ( { Node , MyName } , Partitions2 ) ] ) ,
RunEnv2 = replace ( RunEnv1 ,
[ { network_partitions , Partitions2 } ,
{ network_islands , Islands2 } ,
{ up_nodes , UpNodes } ] ) ,
2015-07-15 02:25:06 +00:00
catch ? REACT ( { calc_up_nodes , ? LINE , [ { partitions , Partitions2 } ,
{ islands , Islands2 } ,
{ up_nodes , UpNodes } ] } ) ,
2015-09-04 09:51:01 +00:00
RunEnv3 = update_runenv_with_up_nodes ( UpNodes , RunEnv2 ) ,
{ UpNodes , Partitions2 , RunEnv3 } .
2015-04-06 05:16:20 +00:00
replace ( PropList , Items ) - >
2015-05-07 08:52:16 +00:00
Tmp = Items ++ PropList ,
[ { K , proplists : get_value ( K , Tmp ) } | | K < - proplists : get_keys ( Tmp ) ] .
2015-04-06 05:16:20 +00:00
2015-04-09 08:13:38 +00:00
rank_and_sort_projections ( [ ] , CurrentProj ) - >
rank_projections ( [ CurrentProj ] , CurrentProj ) ;
2015-04-06 05:16:20 +00:00
rank_and_sort_projections ( Ps , CurrentProj ) - >
Epoch = lists : max ( [ Proj #projection_v1.epoch_number | | Proj < - Ps ] ) ,
MaxPs = [ Proj | | Proj < - Ps ,
Proj #projection_v1.epoch_number == Epoch ] ,
%% Sort with highest rank first (custom sort)
lists : sort ( fun ( { RankA , _ } , { RankB , _ } ) - > RankA > RankB end ,
rank_projections ( MaxPs , CurrentProj ) ) .
%% Caller must ensure all Projs are of the same epoch number.
%% If the caller gives us projections with different epochs, we assume
%% that the caller is doing an OK thing.
2015-04-14 06:30:24 +00:00
%%
%% TODO: This implementation currently gives higher rank to the last
%% member of All_list, which is typically/always/TODO-CLARIFY
%% sorted. That's fine, but there's a source of unnecessary
%% churn: during repair, we assume that the head of the chain is
%% the coordinator of the repair. So any time that the head
%% makes a repair-related transition, that projection may get
%% quickly replaced by an identical projection that merely has
%% higher rank because it's authored by a higher-ranked member.
%% Worst case, for chain len=4:
%% E+0: author=a, upi=[a], repairing=[b,c,d]
%% E+1: author=b, upi=[a], repairing=[b,c,d] (**)
%% E+2: author=c, upi=[a], repairing=[b,c,d] (**)
%% E+3: author=d, upi=[a], repairing=[b,c,d] (**)
%% E+4: author=a, upi=[a,b], repairing=[c,d]
%% E+5: author=b, upi=[a,b], repairing=[c,d] (**)
%% E+6: author=c, upi=[a,b], repairing=[c,d] (**)
%% E+7: author=d, upi=[a,b], repairing=[c,d] (**)
2015-05-20 02:11:54 +00:00
%% E+... 6 more (**) epochs when c & d finish their repairs.
2015-04-14 06:30:24 +00:00
%% Ideally, the "(**)" epochs are avoidable churn.
%% Perhaps this means that we should change the responsibility
%% for repair management to the highest ranking member of the
%% UPI_list?
2015-04-14 07:17:49 +00:00
%% TODO Hrrrmmmmm ... what about the TODO comment in A40's A40a clause?
%% That could perhaps resolve this same problem in a better way?
2015-04-06 05:16:20 +00:00
rank_projections ( Projs , CurrentProj ) - >
#projection_v1 { all_members = All_list } = CurrentProj ,
MemberRank = orddict : from_list (
lists : zip ( All_list , lists : seq ( 1 , length ( All_list ) ) ) ) ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
N = ? MAX_CHAIN_LENGTH + 1 ,
2015-04-06 05:16:20 +00:00
[ { rank_projection ( Proj , MemberRank , N ) , Proj } | | Proj < - Projs ] .
rank_projection ( #projection_v1 { upi = [ ] } , _ MemberRank , _ N ) - >
2015-08-13 05:21:31 +00:00
? RANK_CP_MINORITY_QUORUM ;
2015-07-06 14:56:29 +00:00
rank_projection ( #projection_v1 { author_server = _ Author ,
2015-08-05 06:50:32 +00:00
witnesses = Witness_list ,
2015-04-09 08:13:38 +00:00
upi = UPI_list ,
2015-07-06 14:56:29 +00:00
repairing = Repairing_list } , _ MemberRank , N ) - >
2015-07-06 07:12:15 +00:00
AuthorRank = 0 ,
2015-08-06 08:48:22 +00:00
UPI_witn = [ X | | X < - UPI_list , lists : member ( X , Witness_list ) ] ,
UPI_full = [ X | | X < - UPI_list , not lists : member ( X , Witness_list ) ] ,
2015-08-05 06:50:32 +00:00
case UPI_list -- Witness_list of
[ ] - >
2015-08-13 05:21:31 +00:00
? RANK_CP_MINORITY_QUORUM ;
2015-08-05 06:50:32 +00:00
_ - >
AuthorRank +
2015-08-06 08:48:22 +00:00
( N * length ( Repairing_list ) ) +
( N * N * length ( UPI_witn ) ) +
( N * N * N * length ( UPI_full ) )
2015-08-05 06:50:32 +00:00
end .
2015-04-06 05:16:20 +00:00
2015-05-07 08:52:16 +00:00
do_set_chain_members_dict ( MembersDict , #ch_mgr { proxies_dict = OldProxiesDict } = S ) - >
2015-05-17 14:48:05 +00:00
_ = ? FLU_PC : stop_proxies ( OldProxiesDict ) ,
ProxiesDict = ? FLU_PC : start_proxies ( MembersDict ) ,
2015-05-06 02:41:04 +00:00
{ ok , S #ch_mgr { members_dict = MembersDict ,
2015-05-17 14:48:05 +00:00
proxies_dict = ProxiesDict } } .
2015-05-01 15:33:49 +00:00
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
do_react_to_env ( #ch_mgr { name = MyName ,
proj = #projection_v1 { epoch_number = Epoch ,
members_dict = [ ] = OldDict } = OldProj ,
opts = Opts } = S ) - >
%% Read from our local *public* projection store. If some other
%% chain member has written something there, and if we are a
%% member of that chain, then we'll adopt that projection and then
%% start actively humming in that chain.
{ NewMembersDict , NewProj } =
get_my_public_proj_boot_info ( Opts , OldDict , OldProj ) ,
case orddict : is_key ( MyName , NewMembersDict ) of
false - >
{ { empty_members_dict , [ ] , Epoch } , S } ;
true - >
{ _ , S2 } = do_set_chain_members_dict ( NewMembersDict , S ) ,
2015-08-26 09:47:39 +00:00
CMode = calc_consistency_mode ( NewProj #projection_v1.witnesses ) ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
{ { empty_members_dict , [ ] , Epoch } ,
2015-08-26 09:47:39 +00:00
set_proj ( S2 #ch_mgr { members_dict = NewMembersDict ,
consistency_mode = CMode } , NewProj ) }
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
end ;
2015-04-06 05:16:20 +00:00
do_react_to_env ( S ) - >
2015-07-18 15:43:10 +00:00
%% The not_sanes manager counting dictionary is not strictly
%% limited to flapping scenarios. (Though the mechanism first
%% started as a way to deal with rare flapping scenarios.)
%%
%% I believe that the problem cannot happen in real life, but it can
%% happen in simulated environments, especially if the simulation for
%% repair can be approximately infinitely fast.
%%
%% For example:
%% P_current: epoch=1135, UPI=[b,e,a], Repairing=[c,d], author=e
%%
%% Now a partition happens, a & b are on an island, c & d & e on
%% the other island.
%%
%% P_newprop: epoch=1136, UPI=[e,c], Repairing=[d], author=e
%%
%% Why does e think that this is feasible? Well, the old UPI was
%% [b,e,a], and we know that a & b are partitioned away from e.
%% Therefore e chooses the best UPI, [e]. However, the simulator
%% now also says, hey, there are nodes in the repairing list, so
%% let's simulate a repair ... and the repair goes infinitely
%% quickly ...and the epoch is stable during the repair period
%% (i.e., both e/repairer and c/repairee remained in the same
%% epoch 1135) ... so e decides that the simulated repair is
%% "finished" and it's time to add the repairee to the tail of the
%% UPI ... so that's why 1136's UPI=[e,c].
%%
%% I'll try to add a condition to the simulated repair to try to
%% make slightly fewer assumptions in a row. However, I believe
%% it's a good idea to keep this too-many-not_sane-transition-
%% attempts counter very generic (i.e., not specific for flapping
%% as it once was).
%%
2015-07-20 05:04:25 +00:00
%% The not_sanes counter dict should be reset when we have had at
%% least 3 state transitions that did not have a not_sane
%% suggested projection transition or whenever we fall back to the
%% none_projection.
%%
%% We'll probably implement a very simple counter that may/will be
%% *inaccurate* by at most one -- so any reset test should ignore
%% counter values of 0 & 1.
%%
2015-04-06 05:16:20 +00:00
put ( react , [ ] ) ,
2015-08-12 08:53:39 +00:00
try
2015-08-26 09:47:39 +00:00
S2 = if S #ch_mgr.sane_transitions > 3 - > % TODO review this constant
S #ch_mgr { not_sanes = orddict : new ( ) } ;
true - >
S
end ,
2015-09-14 07:01:26 +00:00
%% Perhaps tell the fitness server to spam everyone.
case random : uniform ( 100 ) of
42 - > machi_fitness : send_spam_to_everyone ( S #ch_mgr.fitness_svr ) ;
_ - > ok
end ,
2015-09-08 07:11:54 +00:00
%% NOTE: If we use the fitness server's unfit list at the start, then
%% we would need to add some kind of poll/check for down members to
%% check if they are now up. Instead, our lazy attempt to read from
%% all servers in A20 will give us the info we need to remove a down
%% server from our last_down list (and also inform our fitness server
%% of the down->up change).
%%
%% TODO? We may need to change this behavior to make our latency
%% jitter smoother by only talking to servers that we believe are fit.
%% But we will defer such work because it may never be necessary.
2015-08-26 09:47:39 +00:00
{ Res , S3 } = react_to_env_A10 ( S2 ) ,
2015-09-08 07:11:54 +00:00
S4 = manage_last_down_list ( S3 ) ,
%% When in CP mode, we call the poll function twice: once at the start
%% of reacting (in state A10) & once after. This call is the 2nd.
{ Res , poll_private_proj_is_upi_unanimous ( S4 ) }
2015-08-12 08:53:39 +00:00
catch
2015-08-13 05:21:31 +00:00
throw : { zerf , _ } = _ Throw - >
2015-08-12 08:53:39 +00:00
Proj = S #ch_mgr.proj ,
2015-08-18 11:49:36 +00:00
io : format ( user , " zerf ~p caught ~p \n " , [ S #ch_mgr.name , _ Throw ] ) ,
2015-08-12 08:53:39 +00:00
{ { no_change , [ ] , Proj #projection_v1.epoch_number } , S }
2015-07-20 05:04:25 +00:00
end .
2015-04-06 05:16:20 +00:00
2015-09-09 10:09:39 +00:00
manage_last_down_list ( #ch_mgr { last_down = LastDown , fitness_svr = FitnessSvr ,
members_dict = MembersDict } = S ) - >
2015-09-08 07:11:54 +00:00
case get_remember_down_list ( ) of
Down when Down == LastDown - >
S ;
Down - >
2015-09-09 10:09:39 +00:00
machi_fitness : update_local_down_list ( FitnessSvr , Down , MembersDict ) ,
2015-09-08 07:11:54 +00:00
S #ch_mgr { last_down = Down }
end .
2015-04-06 05:16:20 +00:00
react_to_env_A10 ( S ) - >
? REACT ( a10 ) ,
2015-08-26 09:47:39 +00:00
react_to_env_A20 ( 0 , poll_private_proj_is_upi_unanimous ( S ) ) .
2015-04-06 05:16:20 +00:00
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
react_to_env_A20 ( Retries , #ch_mgr { name = MyName } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( a20 ) ,
2015-09-08 07:11:54 +00:00
init_remember_down_list ( ) ,
2015-04-06 05:16:20 +00:00
{ UnanimousTag , P_latest , ReadExtra , S2 } =
do_cl_read_latest_public_projection ( true , S ) ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
LastComplaint = get ( rogue_server_epoch ) ,
case orddict : is_key ( P_latest #projection_v1.author_server ,
S #ch_mgr.members_dict ) of
false when P_latest #projection_v1.epoch_number / = LastComplaint - >
put ( rogue_server_epoch , P_latest #projection_v1.epoch_number ) ,
Rogue = P_latest #projection_v1.author_server ,
2015-05-17 10:00:51 +00:00
error_logger : info_msg ( " Chain manager ~w found latest public "
" projection ~w has author ~w not a member "
" of our members list ~w . Please check "
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
" chain membership on this "
2015-05-17 10:00:51 +00:00
" rogue chain manager ~w . \n " ,
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
[ S #ch_mgr.name ,
P_latest #projection_v1.epoch_number ,
Rogue ,
[ K | | { K , _ } < - orddict : to_list ( S #ch_mgr.members_dict ) ] ,
Rogue ] ) ;
_ - >
ok
end ,
case lists : member ( MyName , P_latest #projection_v1.all_members ) of
2015-05-11 10:00:21 +00:00
false when P_latest #projection_v1.epoch_number / = LastComplaint ,
P_latest #projection_v1.all_members / = [ ] - >
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
put ( rogue_server_epoch , P_latest #projection_v1.epoch_number ) ,
error_logger : info_msg ( " Chain manager ~p found latest public "
" projection ~p has author ~p has a "
" members list ~p that does not include me. \n " ,
[ S #ch_mgr.name ,
P_latest #projection_v1.epoch_number ,
P_latest #projection_v1.author_server ,
P_latest #projection_v1.all_members ] ) ;
_ - >
ok
end ,
2015-04-06 05:16:20 +00:00
%% The UnanimousTag isn't quite sufficient for our needs. We need
%% to determine if *all* of the UPI+Repairing FLUs are members of
2015-05-01 15:33:49 +00:00
%% the unanimous server replies. All Repairing FLUs should be up
%% now (because if they aren't then they cannot be repairing), so
%% all Repairing FLUs have no non-race excuse not to be in UnanimousFLUs.
2015-04-06 05:16:20 +00:00
UnanimousFLUs = lists : sort ( proplists : get_value ( unanimous_flus , ReadExtra ) ) ,
UPI_Repairing_FLUs = lists : sort ( P_latest #projection_v1.upi ++
P_latest #projection_v1.repairing ) ,
2015-07-10 06:27:11 +00:00
All_UPI_Repairing_were_unanimous =
ordsets : is_subset ( ordsets : from_list ( UPI_Repairing_FLUs ) ,
ordsets : from_list ( UnanimousFLUs ) ) ,
2015-07-10 06:04:50 +00:00
NotUnanimousFLUs = lists : sort ( proplists : get_value ( not_unanimous_flus ,
ReadExtra , [ xxx ] ) ) ,
NotUnanimousPs = lists : sort ( proplists : get_value ( not_unanimous_answers ,
ReadExtra , [ xxx ] ) ) ,
NotUnanimousSumms = [ machi_projection : make_summary (
P #projection_v1 { dbg2 = [ omitted ] } ) | |
P < - NotUnanimousPs ,
is_record ( P , projection_v1 ) ] ,
BadAnswerFLUs = lists : sort ( proplists : get_value ( bad_answer_flus , ReadExtra ) ) ,
2015-07-10 06:27:11 +00:00
? REACT ( { a20 , ? LINE , [ { upi_repairing , UPI_Repairing_FLUs } ,
{ unanimous_flus , UnanimousFLUs } ,
2015-07-10 06:04:50 +00:00
{ all_upi_repairing_were_unanimous , All_UPI_Repairing_were_unanimous } ,
{ not_unanimous_flus , NotUnanimousFLUs } ,
{ not_unanimous_answers , NotUnanimousSumms } ,
{ bad_answer_flus , BadAnswerFLUs }
] } ) ,
2015-04-06 05:16:20 +00:00
LatestUnanimousP =
if UnanimousTag == unanimous
andalso
All_UPI_Repairing_were_unanimous - >
? REACT ( { a20 , ? LINE } ) ,
true ;
UnanimousTag == unanimous - >
2015-07-10 06:04:50 +00:00
? REACT ( { a20 , ? LINE } ) ,
2015-04-06 05:16:20 +00:00
false ;
UnanimousTag == not_unanimous - >
? REACT ( { a20 , ? LINE } ) ,
false ;
true - >
exit ( { badbad , UnanimousTag } )
end ,
2015-08-20 14:04:27 +00:00
react_to_env_A29 ( Retries , P_latest , LatestUnanimousP , ReadExtra , S2 ) .
2015-09-04 08:17:49 +00:00
react_to_env_A29 ( Retries , P_latest , LatestUnanimousP , _ ReadExtra ,
#ch_mgr { consistency_mode = CMode ,
2015-08-24 12:54:30 +00:00
proj = P_current } = S ) - >
2015-08-31 06:21:17 +00:00
{ Epoch_current , _ } = EpochID_current =
machi_projection : get_epoch_id ( P_current ) ,
{ Epoch_latest , _ } = EpochID_latest = machi_projection : get_epoch_id ( P_latest ) ,
Trigger = if CMode == cp_mode , EpochID_latest / = EpochID_current - >
2015-08-30 11:39:58 +00:00
true ;
true - >
false
end ,
if Trigger - >
2015-08-31 08:57:37 +00:00
? REACT ( { a29 , ? LINE ,
2015-08-31 13:14:28 +00:00
[ { epoch_id_latest , EpochID_latest } ,
{ epoch_id_current , EpochID_current } ,
{ old_current , machi_projection : make_summary ( P_current ) } ] } ) ,
2015-08-31 08:57:37 +00:00
if Epoch_latest > = Epoch_current orelse Epoch_latest == 0 orelse
P_current #projection_v1.upi == [ ] - >
ok ; % sanity check
true - >
exit ( { ? MODULE , ? LINE ,
{ epoch_latest , Epoch_latest } ,
{ epoch_current , Epoch_current } ,
{ latest , machi_projection : make_summary ( P_latest ) } ,
{ current , machi_projection : make_summary ( P_current ) } } )
end ,
2015-08-30 11:39:58 +00:00
put ( yyy_hack , [ ] ) ,
case make_zerf ( P_current , S ) of
Zerf when is_record ( Zerf , projection_v1 ) - >
? REACT ( { a29 , ? LINE ,
[ { zerf_backstop , true } ,
{ zerf_in , machi_projection : make_summary ( Zerf ) } ] } ) ,
%% io:format(user, "zerf_in: A29: ~p: ~w\n\t~p\n", [MyName, machi_projection:make_summary(Zerf), get(yyy_hack)]),
#projection_v1 { dbg = ZerfDbg } = Zerf ,
2015-09-13 06:58:45 +00:00
Backstop = if Zerf #projection_v1.upi == [ ] - >
[ ] ;
true - >
[ { zerf_backstop , true } ]
end ,
2015-09-01 13:10:45 +00:00
P_current_calc = Zerf #projection_v1 {
2015-09-13 06:58:45 +00:00
dbg = Backstop ++ ZerfDbg } ,
2015-08-30 11:39:58 +00:00
react_to_env_A30 ( Retries , P_latest , LatestUnanimousP ,
2015-09-01 13:10:45 +00:00
P_current_calc , S ) ;
2015-08-30 11:39:58 +00:00
Zerf - >
{ { { yo_todo_incomplete_fix_me_cp_mode , line , ? LINE , Zerf } } }
end ;
true - >
2015-08-31 08:57:37 +00:00
? REACT ( { a29 , ? LINE , [ ] } ) ,
2015-09-01 13:10:45 +00:00
react_to_env_A30 ( Retries , P_latest , LatestUnanimousP , P_current , S )
2015-08-30 11:39:58 +00:00
end .
2015-04-06 05:16:20 +00:00
2015-09-01 13:10:45 +00:00
react_to_env_A30 ( Retries , P_latest , LatestUnanimousP , P_current_calc ,
2015-09-09 14:10:39 +00:00
#ch_mgr { name = MyName , consistency_mode = CMode } = S ) - >
2015-09-10 07:39:15 +00:00
V = case file : read_file ( " /tmp/moomoo. " ++ atom_to_list ( S #ch_mgr.name ) ) of { ok , _ } - > true ; _ - > false end ,
if V - > io : format ( user , " A30: ~w : ~p \n " , [ S #ch_mgr.name , get ( react ) ] ) ; true - > ok end ,
2015-04-06 05:16:20 +00:00
? REACT ( a30 ) ,
2015-09-08 07:11:54 +00:00
AllHosed = get_unfit_list ( S #ch_mgr.fitness_svr ) ,
2015-09-09 14:10:39 +00:00
? REACT ( { a30 , ? LINE ,
[ { current , machi_projection : make_summary ( S #ch_mgr.proj ) } ,
2015-09-13 06:58:45 +00:00
{ current_calc , machi_projection : make_summary ( P_current_calc ) } ,
2015-09-09 14:10:39 +00:00
{ latest , machi_projection : make_summary ( P_latest ) } ,
{ all_hosed , AllHosed }
] } ) ,
? REACT ( { a30 , ? LINE , [ ] } ) ,
2015-09-09 12:06:40 +00:00
case lists : member ( MyName , AllHosed ) of
true - >
2015-09-09 14:10:39 +00:00
#projection_v1 { epoch_number = Epoch , all_members = All_list ,
witnesses = Witness_list ,
members_dict = MembersDict } = P_latest ,
P = #projection_v1 { down = Down } =
make_none_projection ( Epoch + 1 , MyName , All_list ,
Witness_list , MembersDict ) ,
P_newprop = if CMode == ap_mode - >
%% Not really none proj: just myself, AP style
machi_projection : update_checksum (
P #projection_v1 { upi = [ MyName ] ,
down = Down -- [ MyName ] ,
dbg = [ { hosed_list , AllHosed } ] } ) ;
CMode == cp_mode - >
machi_projection : update_checksum (
P #projection_v1 { dbg = [ { hosed_list , AllHosed } ] } )
end ,
react_to_env_A40 ( Retries , P_newprop , P_latest , LatestUnanimousP ,
2015-09-13 06:58:45 +00:00
P_current_calc , true , S ) ;
2015-09-09 12:06:40 +00:00
false - >
react_to_env_A31 ( Retries , P_latest , LatestUnanimousP ,
P_current_calc , AllHosed , S )
end .
react_to_env_A31 ( Retries , P_latest , LatestUnanimousP , P_current_calc ,
AllHosed , #ch_mgr { name = MyName } = S ) - >
2015-09-04 08:17:49 +00:00
{ P_newprop1 , S2 , _ Up } = calc_projection ( S , MyName , AllHosed , P_current_calc ) ,
2015-09-09 14:10:39 +00:00
? REACT ( { a31 , ? LINE ,
[ { newprop1 , machi_projection : make_summary ( P_newprop1 ) } ] } ) ,
2015-04-06 05:16:20 +00:00
2015-09-04 08:17:49 +00:00
{ P_newprop2 , S3 } = { P_newprop1 , S2 } ,
2015-04-06 05:16:20 +00:00
%% Move the epoch number up ... originally done in C300.
#projection_v1 { epoch_number = Epoch_newprop2 } = P_newprop2 ,
2015-09-04 08:17:49 +00:00
#projection_v1 { epoch_number = Epoch_latest } = P_latest ,
2015-04-06 05:16:20 +00:00
NewEpoch = erlang : max ( Epoch_newprop2 , Epoch_latest ) + 1 ,
P_newprop3 = P_newprop2 #projection_v1 { epoch_number = NewEpoch } ,
2015-09-09 14:10:39 +00:00
? REACT ( { a31 , ? LINE , [ { newprop3 , machi_projection : make_summary ( P_newprop3 ) } ] } ) ,
2015-04-06 05:16:20 +00:00
2015-09-04 08:17:49 +00:00
{ P_newprop10 , S10 } = { P_newprop3 , S3 } ,
2015-08-29 03:32:30 +00:00
P_newprop11 = machi_projection : update_checksum ( P_newprop10 ) ,
2015-09-09 14:10:39 +00:00
? REACT ( { a31 , ? LINE , [ { newprop11 , machi_projection : make_summary ( P_newprop11 ) } ] } ) ,
2015-04-14 07:17:49 +00:00
2015-09-09 14:10:39 +00:00
react_to_env_A40 ( Retries , P_newprop11 , P_latest , LatestUnanimousP ,
2015-09-13 06:58:45 +00:00
P_current_calc , false , S10 ) .
2015-08-18 11:49:36 +00:00
2015-08-11 06:24:26 +00:00
a40_latest_author_down ( #projection_v1 { author_server = LatestAuthor } = _ P_latest ,
#projection_v1 { upi = [ ] , repairing = [ ] ,
all_members = AllMembers } = _ P_newprop ,
#ch_mgr { name = MyName , runenv = RunEnv } ) - >
%% P_newprop is the none projection. P_newprop's down list is
%% bogus, we cannot use it here.
{ Up , _ Partitions , _ RunEnv2 } = calc_up_nodes ( MyName , AllMembers , RunEnv ) ,
? REACT ( { a40 , ? LINE , [ { latest_author , LatestAuthor } , { up , Up } ] } ) ,
2015-08-12 08:36:13 +00:00
not lists : member ( LatestAuthor , Up ) ;
2015-08-11 06:24:26 +00:00
a40_latest_author_down ( #projection_v1 { author_server = LatestAuthor } = _ P_latest ,
#projection_v1 { down = NewPropDown } = _ P_newprop , _ S ) - >
lists : member ( LatestAuthor , NewPropDown ) .
2015-09-13 06:58:45 +00:00
react_to_env_A40 ( Retries , P_newprop , P_latest , LatestUnanimousP ,
P_current_calc , AmHosedP , #ch_mgr { name = MyName , proj = P_current ,
consistency_mode = CMode } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( a40 ) ,
[ { Rank_newprop , _ } ] = rank_projections ( [ P_newprop ] , P_current ) ,
[ { Rank_latest , _ } ] = rank_projections ( [ P_latest ] , P_current ) ,
2015-08-11 06:24:26 +00:00
LatestAuthorDownP = a40_latest_author_down ( P_latest , P_newprop , S )
andalso
P_latest #projection_v1.author_server / = MyName ,
2015-08-31 08:57:37 +00:00
P_latestStable = make_basic_comparison_stable ( P_latest ) ,
P_currentStable = make_basic_comparison_stable ( P_current ) ,
2015-08-12 08:53:39 +00:00
? REACT ( { a40 , ? LINE ,
[ { latest_author , P_latest #projection_v1.author_server } ,
2015-09-04 08:17:49 +00:00
{ author_is_down_p , LatestAuthorDownP } ] } ) ,
2015-04-06 05:16:20 +00:00
if
2015-09-09 14:10:39 +00:00
AmHosedP - >
2015-09-09 15:43:37 +00:00
ExpectedUPI = if CMode == cp_mode - > [ ] ;
CMode == ap_mode - > [ MyName ]
end ,
2015-09-09 17:15:49 +00:00
if ( P_current #projection_v1.upi / = ExpectedUPI orelse
P_current #projection_v1.repairing / = [ ] )
2015-09-09 14:10:39 +00:00
andalso
2015-09-09 17:15:49 +00:00
( P_newprop #projection_v1.upi == ExpectedUPI andalso
P_newprop #projection_v1.repairing == [ ] ) - >
2015-09-09 14:10:39 +00:00
%% I am hosed. I need to shut up and quit disturbing my
%% peers. If P_latest is the none projection that I wrote
%% on a previous iteration and it's also unanimous, then
%% go to B10 so that I can adopt it. Otherwise, tell the
%% world my intention via C300.
if P_latest #projection_v1.author_server == MyName andalso
2015-09-09 15:43:37 +00:00
P_latest #projection_v1.upi == ExpectedUPI andalso
2015-09-09 14:10:39 +00:00
LatestUnanimousP - >
? REACT ( { a40 , ? LINE , [ ] } ) ,
react_to_env_B10 ( Retries , P_newprop , P_latest ,
2015-09-13 06:58:45 +00:00
LatestUnanimousP , P_current_calc ,
2015-09-09 14:10:39 +00:00
AmHosedP , Rank_newprop , Rank_latest , S ) ;
true - >
2015-09-11 06:56:02 +00:00
? REACT ( { a40 , ? LINE , [ { q1 , P_current #projection_v1.upi } ,
{ q2 , P_current #projection_v1.repairing } ,
{ q3 , P_newprop #projection_v1.upi } ,
{ q4 , P_newprop #projection_v1.repairing } ,
{ q5 , ExpectedUPI } ] } ) ,
%% Ha, there's a "fun" sync problem with the
%% machi_chain_manager1_converge_demo simulator:
%% two servers could get caught in a mutual lock-
%% step that we would end up in this branch 100%
%% of the time because each would only ever see
%% P_newprop authored by the other server.
timer : sleep ( random : uniform ( 100 ) ) ,
2015-09-09 14:10:39 +00:00
react_to_env_C300 ( P_newprop , P_latest , S )
end ;
true - >
? REACT ( { a40 , ? LINE , [ ] } ) ,
react_to_env_A50 ( P_latest , [ ] , S )
end ;
2015-04-10 12:59:56 +00:00
%% Epoch == 0 is reserved for first-time, just booting conditions.
2015-08-13 05:21:31 +00:00
( Rank_newprop > 0 orelse ( Rank_newprop == ? RANK_CP_MINORITY_QUORUM ) )
2015-08-05 06:50:32 +00:00
andalso
( ( P_current #projection_v1.epoch_number > 0
andalso
P_latest #projection_v1.epoch_number > P_current #projection_v1.epoch_number )
orelse
not LatestUnanimousP ) - >
2015-04-06 05:16:20 +00:00
? REACT ( { a40 , ? LINE ,
[ { latest_epoch , P_latest #projection_v1.epoch_number } ,
{ current_epoch , P_current #projection_v1.epoch_number } ,
{ latest_unanimous_p , LatestUnanimousP } ] } ) ,
Tweak IsRelevantToMe_p in B10 (more)
Last night we hit a rare case of failed convergence.
f was out of sync with the rest of the world.
f: upi=[b,g,f] repairing=[a,c]
The "rest of the world" used a larger chain at:
*: upi=[c,b,g,a], repairing=[f]
And f refused to join the larger chain because of the way that
IsRelevantToMe_p was being calculated before this commit.
Hrrrm, though, I'm not convinced that this particular problem
is fixed 100% by this patch. What if the chain lengths were
the same but also UPI incompatible? e.g. if I remove 'a' from
the "real world (in the partition simulator)" example above:
f: upi=[b,g,f] repairing=[c]
*: upi=[c,b,g], repairing=[f]
Hrmmmmm, I may need to reintroduce the my-recent-adopted-projection-
flapping-like-counter thingie to try to break this kind of
incompatible deadlock.
2015-09-14 04:40:34 +00:00
%% TODO 2015-09-14: Should rank be factored in here? If P_latest
%% rank is definitely lower than current rank (or perhaps lower
%% than P_newprop rank?), then don't accept it. Hrm, I'm not sure
%% how that would ripple through the rest of this state machine &
%% interactions with the other state machines, hrmmmm. For a real
%% example, if current upi/rep = [c,b,g,a],[f], going to P_latest
%% of [b,g,f],[a,c] doesn't make sense (even if we ignore the UPI
%% sanity problem in this example).
2015-04-06 05:16:20 +00:00
react_to_env_B10 ( Retries , P_newprop , P_latest , LatestUnanimousP ,
2015-09-13 06:58:45 +00:00
P_current_calc , AmHosedP ,
Rank_newprop , Rank_latest , S ) ;
2015-04-06 05:16:20 +00:00
2015-08-05 06:50:32 +00:00
Rank_newprop > 0
andalso
( P_latest #projection_v1.epoch_number < P_current #projection_v1.epoch_number
orelse
2015-08-28 11:06:09 +00:00
P_latestStable / = P_currentStable ) - >
2015-04-06 05:16:20 +00:00
? REACT ( { a40 , ? LINE ,
2015-08-31 06:40:19 +00:00
[ { latest , P_latestStable } ,
{ current , P_currentStable } ,
{ neq , P_latestStable / = P_currentStable } ] } ) ,
2015-04-06 05:16:20 +00:00
%% Both of these cases are rare. Elsewhere, the code
%% assumes that the local FLU's projection store is always
%% available, so reads & writes to it aren't going to fail
%% willy-nilly. If that assumption is true, then we can
%% reason as follows:
%%
%% a. If we can always read from the local FLU projection
%% store, then the 1st clause isn't possible because
%% P_latest's epoch # must be at least as large as
%% P_current's epoch #
%%
%% b. If P_latest /= P_current, then there can't be a
%% unanimous reply for P_latest, so the earlier 'if'
%% clause would be triggered and so we could never reach
%% this clause.
%%
%% I'm keeping this 'if' clause just in case the local FLU
%% projection store assumption changes.
react_to_env_B10 ( Retries , P_newprop , P_latest , LatestUnanimousP ,
2015-09-13 06:58:45 +00:00
P_current_calc , AmHosedP ,
Rank_newprop , Rank_latest , S ) ;
2015-04-06 05:16:20 +00:00
%% A40a (see flowchart)
Rank_newprop > Rank_latest - >
2015-04-14 07:17:49 +00:00
? REACT ( { a40 , ? LINE ,
2015-04-06 05:16:20 +00:00
[ { rank_latest , Rank_latest } ,
{ rank_newprop , Rank_newprop } ,
{ latest_author , P_latest #projection_v1.author_server } ] } ) ,
%% TODO: There may be an "improvement" here. If we're the
%% highest-ranking FLU in the all_members list, then if we make a
%% projection where our UPI list is the same as P_latest's, and
%% our repairing list is the same as P_latest's, then it may not
%% be necessary to write our projection: it doesn't "improve"
%% anything UPI-wise or repairing-wise. But it isn't clear to me
%% if it's 100% correct to "improve" here and skip writing
%% P_newprop, yet.
react_to_env_C300 ( P_newprop , P_latest , S ) ;
%% A40b (see flowchart)
2015-08-05 06:50:32 +00:00
Rank_newprop > 0
andalso
2015-04-06 05:16:20 +00:00
P_latest #projection_v1.author_server == MyName
andalso
( P_newprop #projection_v1.upi / = P_latest #projection_v1.upi
orelse
P_newprop #projection_v1.repairing / = P_latest #projection_v1.repairing ) - >
? REACT ( { a40 , ? LINE ,
[ { latest_author , P_latest #projection_v1.author_server } ,
{ newprop_upi , P_newprop #projection_v1.upi } ,
{ latest_upi , P_latest #projection_v1.upi } ,
{ newprop_repairing , P_newprop #projection_v1.repairing } ,
{ latest_repairing , P_latest #projection_v1.repairing } ] } ) ,
react_to_env_C300 ( P_newprop , P_latest , S ) ;
%% A40c (see flowchart)
LatestAuthorDownP - >
? REACT ( { a40 , ? LINE ,
[ { latest_author , P_latest #projection_v1.author_server } ,
{ author_is_down_p , LatestAuthorDownP } ] } ) ,
react_to_env_C300 ( P_newprop , P_latest , S ) ;
true - >
? REACT ( { a40 , ? LINE , [ true ] } ) ,
2015-08-29 12:36:53 +00:00
CurrentZerfInStatus_p = has_make_zerf_annotation ( P_current ) ,
2015-09-04 08:17:49 +00:00
? REACT ( { a40 , ? LINE , [ { currentzerfinstatus_p , CurrentZerfInStatus_p } ] } ) ,
GoTo50_p = if CurrentZerfInStatus_p andalso
P_newprop #projection_v1.upi / = [ ] - >
%% One scenario here: we are waking up after
%% a slumber with the none proj and need to
%% send P_newprop (which has non/empty UPI)
%% through the process to continue chain
%% recovery.
? REACT ( { a40 , ? LINE , [ ] } ) ,
false ;
true - >
? REACT ( { a40 , ? LINE , [ ] } ) ,
true
end ,
2015-08-22 12:27:01 +00:00
if GoTo50_p - >
2015-08-23 06:46:57 +00:00
? REACT ( { a40 , ? LINE , [ ] } ) ,
2015-09-04 08:17:49 +00:00
%% io:format(user, "CONFIRM debug question line ~w\n", [?LINE]),
2015-08-22 12:27:01 +00:00
FinalProps = [ { throttle_seconds , 0 } ] ,
react_to_env_A50 ( P_latest , FinalProps , S ) ;
true - >
2015-08-23 06:46:57 +00:00
? REACT ( { a40 , ? LINE , [ ] } ) ,
2015-09-04 08:17:49 +00:00
io : format ( user , " CONFIRM debug question line ~w \n " , [ ? LINE ] ) ,
2015-08-22 12:27:01 +00:00
react_to_env_C300 ( P_newprop , P_latest , S )
end
2015-04-06 05:16:20 +00:00
end .
2015-08-13 09:43:41 +00:00
react_to_env_A50 ( P_latest , FinalProps , #ch_mgr { proj = P_current } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( a50 ) ,
2015-08-13 09:43:41 +00:00
? REACT ( { a50 , ? LINE , [ { current_epoch , P_current #projection_v1.epoch_number } ,
{ latest_epoch , P_latest #projection_v1.epoch_number } ,
2015-04-10 12:59:56 +00:00
{ final_props , FinalProps } ] } ) ,
2015-08-30 10:53:47 +00:00
%% if S#ch_mgr.name == c -> io:format(user, "A50: ~w: ~p\n", [S#ch_mgr.name, get(react)]); true -> ok end,
2015-09-01 13:10:45 +00:00
V = case file : read_file ( " /tmp/moomoo. " ++ atom_to_list ( S #ch_mgr.name ) ) of { ok , _ } - > true ; _ - > false end ,
if V - > io : format ( user , " A50: ~w : ~p \n " , [ S #ch_mgr.name , get ( react ) ] ) ; true - > ok end ,
2015-08-13 09:43:41 +00:00
{ { no_change , FinalProps , P_current #projection_v1.epoch_number } , S } .
2015-04-06 05:16:20 +00:00
2015-09-13 06:58:45 +00:00
react_to_env_B10 ( Retries , P_newprop , P_latest , LatestUnanimousP , P_current_calc ,
2015-09-11 10:14:41 +00:00
_ AmHosedP , Rank_newprop , Rank_latest ,
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
#ch_mgr { name = MyName , proj = P_current ,
proj_history = History } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( b10 ) ,
2015-09-11 10:14:41 +00:00
P_current_upi = if is_record ( P_current , projection_v1 ) - >
P_current #projection_v1.upi ;
true - >
[ ]
end ,
#projection_v1 { author_server = P_latest_author , upi = P_latest_upi ,
repairing = P_latest_repairing } = P_latest ,
I_am_in_P_latest_upi = lists : member ( MyName , P_latest_upi ) ,
I_am_in_P_latest_repairing = lists : member ( MyName , P_latest_repairing ) ,
2015-09-13 05:56:28 +00:00
IsRelevantToMe_p = if P_latest_author == MyName - >
2015-09-11 10:14:41 +00:00
true ;
not ( I_am_in_P_latest_upi
orelse I_am_in_P_latest_repairing ) - >
%% There is no sense for me to leave whatever
%% chain I'm in and go join some other chain
%% that doesn't include me at all in either
%% UPI or repairing. E.g., someone else
%% fell back to none proj ... that proj is
%% now P_latest and it's unanimous. But that
%% doesn't make it a good idea. ^_^
false ;
I_am_in_P_latest_repairing - >
%% If I'm already in the current UPI, and the
Tweak IsRelevantToMe_p in B10 (more)
Last night we hit a rare case of failed convergence.
f was out of sync with the rest of the world.
f: upi=[b,g,f] repairing=[a,c]
The "rest of the world" used a larger chain at:
*: upi=[c,b,g,a], repairing=[f]
And f refused to join the larger chain because of the way that
IsRelevantToMe_p was being calculated before this commit.
Hrrrm, though, I'm not convinced that this particular problem
is fixed 100% by this patch. What if the chain lengths were
the same but also UPI incompatible? e.g. if I remove 'a' from
the "real world (in the partition simulator)" example above:
f: upi=[b,g,f] repairing=[c]
*: upi=[c,b,g], repairing=[f]
Hrmmmmm, I may need to reintroduce the my-recent-adopted-projection-
flapping-like-counter thingie to try to break this kind of
incompatible deadlock.
2015-09-14 04:40:34 +00:00
%% current UPI is longer than P_latest's UPI,
%% then it makes no sense to leave the UPI to
%% go to someone else's suggestion of
2015-09-11 10:14:41 +00:00
%% repairing. If I'm the only member of
%% P_current UPI, then sure, then having me
%% join a repairing list is relevant.
not ( lists : member ( MyName , P_current_upi ) andalso
Tweak IsRelevantToMe_p in B10 (more)
Last night we hit a rare case of failed convergence.
f was out of sync with the rest of the world.
f: upi=[b,g,f] repairing=[a,c]
The "rest of the world" used a larger chain at:
*: upi=[c,b,g,a], repairing=[f]
And f refused to join the larger chain because of the way that
IsRelevantToMe_p was being calculated before this commit.
Hrrrm, though, I'm not convinced that this particular problem
is fixed 100% by this patch. What if the chain lengths were
the same but also UPI incompatible? e.g. if I remove 'a' from
the "real world (in the partition simulator)" example above:
f: upi=[b,g,f] repairing=[c]
*: upi=[c,b,g], repairing=[f]
Hrmmmmm, I may need to reintroduce the my-recent-adopted-projection-
flapping-like-counter thingie to try to break this kind of
incompatible deadlock.
2015-09-14 04:40:34 +00:00
length ( P_current_upi ) > length ( P_latest_upi ) ) ;
2015-09-13 05:56:28 +00:00
true - >
2015-09-11 10:14:41 +00:00
true
end ,
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
HistoryList = queue : to_list ( History ) ,
UniqueHistories = lists : usort ( HistoryList ) ,
UniqueHistoryTrigger_p = length ( HistoryList ) > ( ? MAX_HISTORY_LENGTH - 1 )
andalso case UniqueHistories of
[ [ ] ] - > false ;
[ [ MyName ] ] - > false ;
[ _ ] - > true ;
_ - > false
end ,
2015-09-11 10:14:41 +00:00
? REACT ( { b10 , ? LINE , [ { newprop_epoch , P_newprop #projection_v1.epoch_number } ,
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
{ is_relevant_to_me_p , IsRelevantToMe_p } ,
{ unique_histories , UniqueHistories } ,
{ unique_history_trigger_p , UniqueHistoryTrigger_p } ] } ) ,
2015-04-06 05:16:20 +00:00
if
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
UniqueHistoryTrigger_p - >
? REACT ( { b10 , ? LINE , [ ] } ) ,
%% We need to do something drastic: we're repeating ourselves. In
%% a former version of this code, we called this "flapping" and
%% would enter an alternative projection calculation mode in order
%% to dampen the flapping. In today's version of the code, we use
%% the machi_fitness service to help us figure out who is causing
%% the flapping so that we can exclude them from our projection
%% calculations.
%%
%% In this hypothetical example (i.e., I haven't witnessed this
%% actually happening with this code, but it's a valid scenario, I
%% believe), with a chain length of 7, during an asymmetric
%% partition scenario, we could have:
%% f suggests : upi=[b,g,f] repairing=[c]
%% c,b,g all suggest: upi=[c,b,g], repairing=[f]
%%
%% The projection ranking and UPI lengths are the same, but the
%% UPIs are incompatible. In the Git history, very recently, we
%% hit a case very similar to this one. The fix, after more
%% thought, wasn't sufficient. A more comprehensive fix, I
%% believe, is using the older "flapping" detection mechanism for
%% this worst-case condition: go to C103, fall back to shortest &
%% safe projection, tell fitness server we're administratively
%% down for a short while (to signal to other state machines that
%% they need to adapt to our bad situation), and then resume.
io : format ( user , " \n CONFIRM dbg *************************** ~w UniqueHistoryTrigger_p \n " , [ MyName ] ) ,
react_to_env_C103 ( P_newprop , P_latest , P_current_calc , S ) ;
2015-09-11 10:14:41 +00:00
LatestUnanimousP andalso IsRelevantToMe_p - >
2015-04-14 07:17:49 +00:00
? REACT ( { b10 , ? LINE ,
[ { latest_unanimous_p , LatestUnanimousP } ,
{ latest_epoch , P_latest #projection_v1.epoch_number } ,
{ latest_author , P_latest #projection_v1.author_server } ,
{ newprop_epoch , P_newprop #projection_v1.epoch_number } ,
{ newprop_author , P_newprop #projection_v1.author_server }
] } ) ,
2015-04-06 05:16:20 +00:00
2015-09-13 06:58:45 +00:00
react_to_env_C100 ( P_newprop , P_latest , P_current_calc , S ) ;
2015-04-06 05:16:20 +00:00
Retries > 2 - >
? REACT ( { b10 , ? LINE , [ { retries , Retries } ] } ) ,
%% The author of P_latest is too slow or crashed.
%% Let's try to write P_newprop and see what happens!
react_to_env_C300 ( P_newprop , P_latest , S ) ;
2015-09-13 08:44:23 +00:00
Rank_latest < 0 andalso Rank_newprop < 0 - >
? REACT ( { b10 , ? LINE ,
[ { rank_latest , Rank_latest } ,
{ rank_newprop , Rank_newprop } ] } ) ,
2015-09-13 12:51:20 +00:00
%% The latest projection is none proj, so is my newprop. In CP
%% mode and asymmetric partitions, we might write a lot of new
%% none projs, but this action also helps trigger others to change
%% their projections in a healthy way. TODO: perhaps use a
%% counter here to silence ourselves for good after a certain time
%% and/or number of retries?
case random : uniform ( 100 ) of
N when N < 4 - >
? REACT ( { b10 , ? LINE } ) ,
react_to_env_C300 ( P_newprop , P_latest , S ) ;
_ - >
? REACT ( { b10 , ? LINE } ) ,
react_to_env_A50 ( P_latest , [ ] , S )
end ;
2015-09-13 08:44:23 +00:00
2015-04-06 05:16:20 +00:00
Rank_latest > = Rank_newprop
andalso
P_latest #projection_v1.author_server / = MyName - >
? REACT ( { b10 , ? LINE ,
[ { rank_latest , Rank_latest } ,
{ rank_newprop , Rank_newprop } ,
{ latest_author , P_latest #projection_v1.author_server } ] } ) ,
2015-04-14 09:19:08 +00:00
%% Give the author of P_latest an opportunity to write a
2015-04-06 05:16:20 +00:00
%% new projection in a new epoch to resolve this mixed
%% opinion.
react_to_env_C200 ( Retries , P_latest , S ) ;
true - >
? REACT ( { b10 , ? LINE } ) ,
2015-04-13 15:54:38 +00:00
? REACT ( { b10 , ? LINE , [ { retries , Retries } , { rank_latest , Rank_latest } , { rank_newprop , Rank_newprop } , { latest_author , P_latest #projection_v1.author_server } ] } ) , % TODO debug delete me!
2015-04-06 05:16:20 +00:00
%% P_newprop is best, so let's write it.
react_to_env_C300 ( P_newprop , P_latest , S )
end .
2015-09-04 08:17:49 +00:00
react_to_env_C100 ( P_newprop ,
#projection_v1 { author_server = Author_latest } = P_latest ,
2015-09-13 06:58:45 +00:00
P_current_calc ,
#ch_mgr { name = MyName , proj = P_current , consistency_mode = CMode ,
2015-07-18 15:43:10 +00:00
not_sanes = NotSanesDict0 } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( c100 ) ,
2015-04-10 13:41:22 +00:00
2015-09-13 06:58:45 +00:00
P_cur_for_sanity = if CMode == cp_mode - >
P_current_calc ;
CMode == ap_mode - >
P_current
end ,
Sane = projection_transition_is_sane ( P_cur_for_sanity , P_latest , MyName ) ,
2015-09-04 08:17:49 +00:00
if Sane == true - >
ok ;
true - >
2015-09-13 12:51:20 +00:00
QQ_current = lists : flatten ( io_lib : format ( " cur= ~w : ~w , ~w /calc= ~w : ~w , ~w " , [ P_current #projection_v1.epoch_number , P_current #projection_v1.upi , P_current #projection_v1.repairing , P_current_calc #projection_v1.epoch_number , P_current_calc #projection_v1.upi , P_current_calc #projection_v1.repairing ] ) ) ,
2015-09-04 08:17:49 +00:00
QQ_latest = lists : flatten ( io_lib : format ( " ~w : ~w , ~w " , [ P_latest #projection_v1.epoch_number , P_latest #projection_v1.upi , P_latest #projection_v1.repairing ] ) ) ,
? V ( " \n ~w -insane- ~w -auth= ~w ~s -> ~s ~w \n ~p \n ~p \n " , [ ? LINE , MyName , P_newprop #projection_v1.author_server , QQ_current , QQ_latest , Sane , get ( why2 ) , get ( react ) ] )
end ,
2015-09-13 12:51:20 +00:00
? REACT ( { c100 , ? LINE , [ zoo , { me , MyName } ,
{ author_latest , Author_latest } ,
{ why2 , get ( why2 ) } ] } ) ,
2015-07-15 08:23:17 +00:00
2015-07-15 03:44:56 +00:00
%% Note: The value of `Sane' may be `true', `false', or `term() /= true'.
%% The error value `false' is reserved for chain order violations.
%% Any other non-true value can be used for projection structure
%% construction errors, checksum error, etc.
2015-07-04 06:52:44 +00:00
case Sane of
2015-04-10 13:41:22 +00:00
_ when P_current #projection_v1.epoch_number == 0 - >
2015-08-28 09:37:11 +00:00
%% Epoch == 0 is reserved for first-time, just booting conditions
%% or for when we got stuck in an insane projection transition
%% and were forced to the none projection to recover.
2015-04-09 08:47:43 +00:00
? REACT ( { c100 , ? LINE , [ first_write ] } ) ,
2015-08-24 10:04:26 +00:00
if Sane == true - > ok ; true - > ? V ( " ~w -insane- ~w - ~w : ~w : ~w , " , [ ? LINE , MyName , P_newprop #projection_v1.epoch_number , P_newprop #projection_v1.upi , P_newprop #projection_v1.repairing ] ) end , %%% DELME!!!
2015-04-09 08:47:43 +00:00
react_to_env_C110 ( P_latest , S ) ;
2015-06-15 08:22:02 +00:00
true - >
2015-09-13 12:51:20 +00:00
? REACT ( { c100 , ? LINE , [ ] } ) ,
2015-08-24 10:04:26 +00:00
if Sane == true - > ok ; true - > ? V ( " ~w -insane- ~w - ~w : ~w : ~w @ ~w , " , [ ? LINE , MyName , P_newprop #projection_v1.epoch_number , P_newprop #projection_v1.upi , P_newprop #projection_v1.repairing , ? LINE ] ) end , %%% DELME!!!
2015-09-10 09:05:55 +00:00
V = case file : read_file ( " /tmp/bugbug. " ++ atom_to_list ( S #ch_mgr.name ) ) of { ok , _ } - > true ; _ - > false end ,
if V - >
2015-09-13 06:58:45 +00:00
react_to_env_C103 ( P_newprop , P_latest , P_current_calc , S ) ;
2015-09-10 09:05:55 +00:00
true - >
react_to_env_C110 ( P_latest , S )
end ;
%% ORIGINAL react_to_env_C110(P_latest, S);
2015-09-04 08:17:49 +00:00
NotSaneBummer - >
? REACT ( { c100 , ? LINE , [ { not_sane , NotSaneBummer } ] } ) ,
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
react_to_env_C100_inner ( Author_latest , NotSanesDict0 , MyName ,
2015-09-13 06:58:45 +00:00
P_newprop , P_latest , P_current_calc , S )
2015-04-06 05:16:20 +00:00
end .
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
react_to_env_C100_inner ( Author_latest , NotSanesDict0 , MyName ,
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
P_newprop , P_latest , P_current_calc , S ) - >
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
NotSanesDict = orddict : update_counter ( Author_latest , 1 , NotSanesDict0 ) ,
2015-07-20 05:04:25 +00:00
S2 = S #ch_mgr { not_sanes = NotSanesDict , sane_transitions = 0 } ,
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
case orddict : fetch ( Author_latest , NotSanesDict ) of
2015-09-13 15:04:53 +00:00
N when N > ? TOO_FREQUENT_BREAKER - >
2015-08-22 12:27:01 +00:00
? V ( " \n \n YOYO ~w breaking the cycle of: \n current: ~w \n new : ~w \n " , [ MyName , machi_projection : make_summary ( S #ch_mgr.proj ) , machi_projection : make_summary ( P_latest ) ] ) ,
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
? REACT ( { c100 , ? LINE , [ { not_sanes_author_count , N } ] } ) ,
2015-09-13 06:58:45 +00:00
react_to_env_C103 ( P_newprop , P_latest , P_current_calc , S2 ) ;
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
N - >
2015-07-18 15:43:10 +00:00
? V ( " YOYO, ~w , ~w , ~w , " , [ MyName , P_latest #projection_v1.epoch_number , N ] ) ,
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
? REACT ( { c100 , ? LINE , [ { not_sanes_author_count , N } ] } ) ,
2015-07-18 15:43:10 +00:00
%% P_latest is not sane.
%% By process of elimination, P_newprop is best,
%% so let's write it.
WIP: bugfix for rare flapping infinite loop (better fix I hope)
%% So, I'd tried this kind of "if everyone is doing it, then we
%% 'agree' and we can do something different" strategy before,
%% and it didn't work then. Silly me. Distributed systems
%% lesson #823: do not forget the past. In a situation created
%% by PULSE, of all=[a,b,c,d,e], b & d & e were scheduled
%% completely unfairly. So a & c were the only authors ever to
%% suceessfully write a suggested projection to a public store.
%% Oops.
%%
%% So, we're going to keep track in #ch_mgr state for the number
%% of times that this insane judgement has happened.
2015-07-17 05:51:39 +00:00
react_to_env_C300 ( P_newprop , P_latest , S2 )
end .
2015-08-25 09:43:55 +00:00
react_to_env_C103 ( #projection_v1 { epoch_number = _ Epoch_newprop } = _ P_newprop ,
2015-08-22 12:40:21 +00:00
#projection_v1 { epoch_number = Epoch_latest ,
2015-09-04 08:17:49 +00:00
all_members = All_list } = _ P_latest ,
2015-09-13 06:58:45 +00:00
P_current_calc ,
2015-07-20 05:04:25 +00:00
#ch_mgr { name = MyName , proj = P_current } = S ) - >
2015-08-24 10:04:26 +00:00
#projection_v1 { witnesses = Witness_list ,
members_dict = MembersDict } = P_current ,
2015-08-30 10:53:47 +00:00
P_none0 = make_none_projection ( Epoch_latest ,
MyName , All_list , Witness_list , MembersDict ) ,
2015-09-04 08:17:49 +00:00
P_none1 = P_none0 #projection_v1 { dbg = [ { none_projection , true } ] } ,
2015-07-15 08:23:17 +00:00
P_none = machi_projection : update_checksum ( P_none1 ) ,
2015-07-20 05:04:25 +00:00
? REACT ( { c103 , ? LINE ,
[ { current_epoch , P_current #projection_v1.epoch_number } ,
2015-08-24 10:04:26 +00:00
{ none_projection_epoch , P_none #projection_v1.epoch_number } ] } ) ,
2015-09-10 09:05:55 +00:00
io : format ( user , " SET add_admin_down( ~w ) at ~w ===================================== \n " , [ MyName , time ( ) ] ) ,
machi_fitness : add_admin_down ( S #ch_mgr.fitness_svr , MyName , [ ] ) ,
timer : sleep ( 5 * 1000 ) ,
io : format ( user , " SET delete_admin_down( ~w ) at ~w ===================================== \n " , [ MyName , time ( ) ] ) ,
machi_fitness : delete_admin_down ( S #ch_mgr.fitness_svr , MyName ) ,
2015-09-13 06:58:45 +00:00
react_to_env_C100 ( P_none , P_none , P_current_calc , S ) .
2015-07-15 08:23:17 +00:00
2015-09-04 08:17:49 +00:00
react_to_env_C110 ( P_latest , #ch_mgr { name = MyName } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( c110 ) ,
2015-08-24 10:04:26 +00:00
? REACT ( { c110 , ? LINE , [ { latest_epoch , P_latest #projection_v1.epoch_number } ] } ) ,
2015-09-04 08:17:49 +00:00
Extra1 = [ ] ,
2015-08-27 08:58:43 +00:00
Extra2 = [ { react , get ( react ) } ] ,
P_latest2 = machi_projection : update_dbg2 ( P_latest , Extra1 ++ Extra2 ) ,
2015-04-06 05:16:20 +00:00
2015-04-09 08:13:38 +00:00
MyNamePid = proxy_pid ( MyName , S ) ,
2015-07-02 19:30:05 +00:00
Goo = P_latest2 #projection_v1.epoch_number ,
2015-05-01 15:33:49 +00:00
%% This is the local projection store. Use a larger timeout, so
%% that things locally are pretty horrible if we're killed by a
%% timeout exception.
2015-05-06 02:41:04 +00:00
Goo = P_latest2 #projection_v1.epoch_number ,
2015-07-16 07:01:53 +00:00
%% ?V("HEE110 ~w ~w ~w\n", [S#ch_mgr.name, self(), lists:reverse(get(react))]),
2015-05-06 02:41:04 +00:00
2015-07-17 07:20:54 +00:00
case { ? FLU_PC : write_projection ( MyNamePid , private , P_latest2 , ? TO * 30 ) , Goo } of
{ ok , Goo } - >
2015-08-13 09:45:15 +00:00
? REACT ( { c120 , [ { write , ok } ] } ) ,
2015-08-13 10:10:48 +00:00
%% We very intentionally do *not* pass P_latest2 forward:
%% we must avoid bloating the dbg2 list!
2015-08-28 09:37:11 +00:00
P_latest2_perhaps_annotated =
2015-08-27 16:55:31 +00:00
machi_projection : update_dbg2 ( P_latest , Extra1 ) ,
2015-08-28 09:37:11 +00:00
perhaps_verbose_c110 ( P_latest2_perhaps_annotated , S ) ,
react_to_env_C120 ( P_latest2_perhaps_annotated , [ ] , S ) ;
2015-08-13 09:45:15 +00:00
{ { error , bad_arg } , _ Goo } - >
? REACT ( { c120 , [ { write , bad_arg } ] } ) ,
2015-08-13 10:10:48 +00:00
%% This bad_arg error is the result of an implicit pre-condition
%% failure that is now built-in to the projection store: when
%% writing a private projection, return {error, bad_arg} if there
%% the store contains a *public* projection with a higher epoch
%% number.
%%
%% In the context of AP mode, this is harmless: we avoid a bit of
%% extra work by adopting P_latest now.
%%
%% In the context of CP mode, this pre-condition failure is very
%% important: it signals to us that the world is changing (or
%% trying to change), and it is vital that we avoid doing
%% something based on stale info.
%%
%% Worst case: our humming consensus round was executing very
%% quickly until the point immediately before writing our private
%% projection above: immediately before the private proj write,
%% we go to sleep for 10 days. When we wake up after such a long
%% sleep, we would definitely notice the last projections made by
%% everyone, but we would miss the intermediate *history* of
%% chain changes over those 10 days. In CP mode it's vital that
%% we don't miss any of that history while we're running (i.e.,
%% here in this func) or when we're restarting after a
%% shutdown/crash.
%%
%% React to newer public write by restarting the iteration.
2015-08-13 09:45:15 +00:00
react_to_env_A20 ( 0 , S ) ;
2015-09-14 04:54:47 +00:00
{ error , TO_or_part } = Else when TO_or_part == timeout ;
TO_or_part == partition - >
%% TODO: Hrm, I know that there's currently a bug in the
%% machi_flu1_client that causes use of 'undefined' instead of a
%% valid TCP socket port() that causes {error,partition} problems
%% when talking to remote projection stores. However, I've now
%% twice witnessed (and also recognized ^_^) that this same error
%% can happen when talking to our local projection store. Verrry
%% interesting. Let's assume that this is a very rare
%% happenstance, and when it does happen, we just punt and go to
%% A50.
react_to_env_A50 ( P_latest , [ { c110_error , Else } ] , S ) ;
2015-07-17 07:20:54 +00:00
Else - >
2015-08-27 16:55:31 +00:00
Summ = machi_projection : make_summary ( P_latest2 ) ,
2015-08-13 09:45:15 +00:00
io : format ( user , " C110 error by ~w : ~w , ~w \n ~p \n " ,
2015-07-17 07:20:54 +00:00
[ MyName , Else , Summ , get ( react ) ] ) ,
2015-08-13 09:45:15 +00:00
error_logger : error_msg ( " C110 error by ~w : ~w , ~w , ~w \n " ,
2015-07-17 07:20:54 +00:00
[ MyName , Else , Summ , get ( react ) ] ) ,
exit ( { c110_failure , MyName , Else , Summ } )
2015-08-13 09:45:15 +00:00
end .
2015-04-06 05:16:20 +00:00
2015-07-20 05:04:25 +00:00
react_to_env_C120 ( P_latest , FinalProps , #ch_mgr { proj_history = H ,
sane_transitions = Xtns } = S ) - >
2015-04-06 05:16:20 +00:00
? REACT ( c120 ) ,
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
H2 = add_and_trunc_history ( P_latest , H , ? MAX_HISTORY_LENGTH ) ,
2015-04-06 05:16:20 +00:00
2015-08-27 11:27:33 +00:00
%% diversion_c120_verbose_goop(P_latest, S),
2015-04-09 08:13:38 +00:00
? REACT ( { c120 , [ { latest , machi_projection : make_summary ( P_latest ) } ] } ) ,
2015-08-27 16:55:31 +00:00
S2 = set_proj ( S #ch_mgr { proj_history = H2 ,
sane_transitions = Xtns + 1 } , P_latest ) ,
S3 = case is_annotated ( P_latest ) of
false - >
S2 ;
{ { _ ConfEpoch , _ ConfCSum } , ConfTime } - >
2015-09-04 08:17:49 +00:00
io : format ( user , " \n CONFIRM debug C120 ~w was annotated ~w \n " , [ S #ch_mgr.name , P_latest #projection_v1.epoch_number ] ) ,
2015-08-27 16:55:31 +00:00
S2 #ch_mgr { proj_unanimous = ConfTime }
end ,
2015-09-01 13:10:45 +00:00
V = case file : read_file ( " /tmp/moomoo. " ++ atom_to_list ( S #ch_mgr.name ) ) of { ok , _ } - > true ; _ - > false end ,
if V - > io : format ( " C120: ~w : ~p \n " , [ S #ch_mgr.name , get ( react ) ] ) ; true - > ok end ,
2015-08-27 16:55:31 +00:00
{ { now_using , FinalProps , P_latest #projection_v1.epoch_number } , S3 } .
2015-08-23 08:50:25 +00:00
add_and_trunc_history ( P_latest , H , MaxLength ) - >
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
Latest_U_R = { P_latest #projection_v1.upi , P_latest #projection_v1.repairing } ,
2015-08-23 08:50:25 +00:00
H2 = if P_latest #projection_v1.epoch_number > 0 - >
Re-use the flapping criteria for a different use (more)
Hooray, very early I ended up with a simulator example which kicked
in and tested this change. (A deterministice fault injection method
for testing would also be valuable, probably.)
machi_chain_manager1_converge_demo:t(7, [{private_write_verbose,true}]).
We switched partitions in the simulator like this:
SET partitions = [{b,f},{c,f},{d,e},{f,e}] (2 of 90252) at {14,37,5}
...
Stable projection at epoch 1429 upi=[b,c,g,a,d],repairing=[]
...
SET partitions = [{b,d},{c,b},{d,c},{f,a}] (3 of 90252) at {14,37,44}
Part of the chain reassembled quickly from the following UPIs: [g], then
[g,e], then [g,e,f] via a series of successful simulated repairs. For
the first two repairs, all parties (e & f & g) are unanimous about the
projections. For the final repair, very strange, not all three adopt
[g,e,f] chain: e says nothing, f & g use it.
Also weird, then g immediately moves f! upi=[g,e],repairing=[f].
Then e also adopts this chain of 2. From that point forward, f keeps
trying to use upi=[g,e,f],[] and the others try using only upi=[g,e],[f].
There are lots of messages from g saying that it's insane (correctly!)
to try calc=1487:[g,e],[f] -> 1494:[g,e,f],[] without a valid repair
author.
It's worth checking why g dropped from [g,e,f] -> [g,e]. But even
still, this new use for the flapping counter & reset via C103 is
working. ... Ah, now I understand. The very occasional undefined
socket bug in machi_flu1_client appears to be the cause: g had a
one-time problem talking with f and so decided f was down long enough to
make the shorter UPI. The other participants didn't have any such
problem with f and so kept f in the UPI. This would have been a
deadlock/infinite loop case without someone deciding to reset state.
2015-09-14 05:42:52 +00:00
queue : in ( Latest_U_R , H ) ;
2015-08-23 08:50:25 +00:00
true - >
H
end ,
case queue : len ( H2 ) of
X when X > MaxLength - >
{ _ V , Hxx } = queue : out ( H2 ) ,
Hxx ;
_ - >
H2
end .
2015-04-06 05:16:20 +00:00
react_to_env_C200 ( Retries , P_latest , S ) - >
? REACT ( c200 ) ,
try
2015-07-02 19:30:05 +00:00
AuthorProxyPid = proxy_pid ( P_latest #projection_v1.author_server , S ) ,
? FLU_PC : kick_projection_reaction ( AuthorProxyPid , [ ] )
2015-04-06 05:16:20 +00:00
catch _ Type : _ Err - >
2015-07-16 07:01:53 +00:00
%% ?V("TODO: tell_author_yo is broken: ~p ~p\n",
2015-04-06 05:16:20 +00:00
%% [_Type, _Err]),
ok
end ,
react_to_env_C210 ( Retries , S ) .
react_to_env_C210 ( Retries , #ch_mgr { name = MyName , proj = Proj } = S ) - >
? REACT ( c210 ) ,
sleep_ranked_order ( 10 , 100 , MyName , Proj #projection_v1.all_members ) ,
react_to_env_C220 ( Retries , S ) .
react_to_env_C220 ( Retries , S ) - >
? REACT ( c220 ) ,
react_to_env_A20 ( Retries + 1 , S ) .
react_to_env_C300 ( #projection_v1 { epoch_number = _ Epoch_newprop } = P_newprop ,
#projection_v1 { epoch_number = _ Epoch_latest } = _ P_latest , S ) - >
? REACT ( c300 ) ,
2015-04-09 08:13:38 +00:00
react_to_env_C310 ( machi_projection : update_checksum ( P_newprop ) , S ) .
2015-04-06 05:16:20 +00:00
react_to_env_C310 ( P_newprop , S ) - >
? REACT ( c310 ) ,
Epoch = P_newprop #projection_v1.epoch_number ,
{ WriteRes , S2 } = cl_write_public_proj_skip_local_error ( Epoch , P_newprop , S ) ,
? REACT ( { c310 , ? LINE ,
2015-04-09 08:13:38 +00:00
[ { newprop , machi_projection : make_summary ( P_newprop ) } ,
2015-04-06 05:16:20 +00:00
{ write_result , WriteRes } ] } ) ,
react_to_env_A10 ( S2 ) .
projection_transitions_are_sane ( Ps , RelativeToServer ) - >
projection_transitions_are_sane ( Ps , RelativeToServer , false ) .
- ifdef ( TEST ) .
projection_transitions_are_sane_retrospective ( Ps , RelativeToServer ) - >
projection_transitions_are_sane ( Ps , RelativeToServer , true ) .
- endif . % TEST
projection_transitions_are_sane ( [ ] , _ RelativeToServer , _ RetrospectiveP ) - >
true ;
projection_transitions_are_sane ( [ _ ] , _ RelativeToServer , _ RetrospectiveP ) - >
true ;
projection_transitions_are_sane ( [ P1 , P2 | T ] , RelativeToServer , RetrospectiveP ) - >
case projection_transition_is_sane ( P1 , P2 , RelativeToServer ,
RetrospectiveP ) of
true - >
projection_transitions_are_sane ( [ P2 | T ] , RelativeToServer ,
RetrospectiveP ) ;
Else - >
Else
end .
- ifdef ( TEST ) .
projection_transition_is_sane_retrospective ( P1 , P2 , RelativeToServer ) - >
projection_transition_is_sane ( P1 , P2 , RelativeToServer , true ) .
- endif . % TEST
2015-07-04 06:52:44 +00:00
projection_transition_is_sane ( P1 , P2 , RelativeToServer ) - >
projection_transition_is_sane ( P1 , P2 , RelativeToServer , false ) .
2015-07-15 03:44:56 +00:00
%% @doc Check if a projection transition is sane & safe.
%%
%% NOTE: The return value convention is `true' for sane/safe and
%% `term() /= true' for any unsafe/insane value.
2015-07-04 06:52:44 +00:00
projection_transition_is_sane ( P1 , P2 , RelativeToServer , RetrospectiveP ) - >
2015-08-13 15:12:13 +00:00
put ( myname , RelativeToServer ) ,
2015-07-05 05:52:50 +00:00
put ( why2 , [ ] ) ,
2015-07-04 06:52:44 +00:00
case projection_transition_is_sane_with_si_epoch (
P1 , P2 , RelativeToServer , RetrospectiveP ) of
true - >
2015-09-04 08:17:49 +00:00
projection_transition_is_sane_final_review ( P1 , P2 ,
? RETURN2 ( true ) ) ;
2015-09-10 09:05:55 +00:00
{ epoch_not_si , SameEpoch , not_gt , SameEpoch } = Reason - >
if P1 #projection_v1.upi == [ ] ,
P2 #projection_v1.upi == [ ] - >
%% None proj -> none proj is ok
? RETURN2 ( true ) ;
true - >
? RETURN2 ( Reason )
end ;
2015-07-04 06:52:44 +00:00
Else - >
2015-09-04 08:17:49 +00:00
? RETURN2 ( Else )
2015-07-04 06:52:44 +00:00
end .
2015-08-24 10:04:26 +00:00
projection_transition_is_sane_final_review (
2015-09-04 08:17:49 +00:00
_ P1 , P2 , { expected_author2 , UPI1_tail , _ } = Else ) - >
if UPI1_tail == P2 #projection_v1.author_server - >
2015-07-05 05:52:50 +00:00
? RETURN2 ( true ) ;
true - >
2015-08-22 12:27:01 +00:00
? RETURN2 ( { gazzuknkgazzuknk , Else , gazzuknk } )
2015-07-05 05:52:50 +00:00
end ;
2015-08-13 15:12:13 +00:00
projection_transition_is_sane_final_review (
#projection_v1 { mode = CMode1 } = _ P1 ,
#projection_v1 { mode = CMode2 } = _ P2 ,
_ ) when CMode1 / = CMode2 - >
{ wtf , cmode1 , CMode1 , cmode2 , CMode2 } ;
projection_transition_is_sane_final_review (
2015-08-30 11:39:58 +00:00
#projection_v1 { mode = cp_mode , upi = UPI1 , dbg = P1_dbg } = _ P1 ,
#projection_v1 { mode = cp_mode , upi = UPI2 , witnesses = Witness_list } = _ P2 ,
2015-08-13 15:12:13 +00:00
true ) - >
%% All earlier sanity checks has said that this transition is sane, but
2015-09-01 13:10:45 +00:00
%% we also need to make certain that any CP mode transition preserves at
2015-08-13 15:12:13 +00:00
%% least one non-witness server in the UPI list. Earlier checks have
%% verified that the ordering of the FLUs within the UPI list is ok.
UPI1_s = ordsets : from_list ( UPI1 -- Witness_list ) ,
UPI2_s = ordsets : from_list ( UPI2 -- Witness_list ) ,
2015-08-30 10:53:47 +00:00
catch ? REACT ( { projection_transition_is_sane_final_review , ? LINE ,
[ { upi1 , UPI1 } , { upi2 , UPI2 } , { witnesses , Witness_list } ,
2015-08-30 11:39:58 +00:00
{ zerf_backstop , proplists : get_value ( zerf_backstop , P1_dbg ) } ,
2015-08-30 10:53:47 +00:00
{ upi1_s , UPI1 } , { upi2_s , UPI2 } ] } ) ,
2015-08-30 11:39:58 +00:00
case proplists : get_value ( zerf_backstop , P1_dbg ) of
2015-08-13 15:12:13 +00:00
true when UPI1 == [ ] - >
2015-09-01 13:10:45 +00:00
%% CAUTION, this is a dangerous case. If the old projection, P1,
%% has a 'zerf_backstop' annotation, then when this function
%% returns true, we are (in effect) saying, "We trust you." What
%% if we called make_zerf() a year ago because we took a 1 year
%% nap?? How can we trust this?
%%
%% The answer is: this is not our last safety enforcement for CP
%% mode, fortunately. We are going from the none projection to a
%% quorum majority projection, *and* we will not unwedge ourself
%% until we can verify that all UPI members of the chain are
%% unanimous for this epoch. So if we took a 1 year nap already,
%% or if we take a one year right now and delay writing our
%% private projection for 1 year, then if we disagree with the
%% quorum majority, we simply won't be able to unwedge.
2015-08-13 15:12:13 +00:00
? RETURN2 ( true ) ;
_ when UPI2 == [ ] - >
%% We're down to the none projection to wedge ourself. That's ok.
? RETURN2 ( true ) ;
_ - >
? RETURN2 ( not ordsets : is_disjoint ( UPI1_s , UPI2_s ) )
end ;
2015-09-13 12:51:20 +00:00
projection_transition_is_sane_final_review (
#projection_v1 { mode = cp_mode , upi = UPI1 , repairing = Repairing1 } = _ P1 ,
#projection_v1 { mode = cp_mode , upi = UPI2 , repairing = Repairing2 } = _ P2 ,
{ epoch_not_si , EpochX , not_gt , EpochY } = Else ) - >
if EpochX == EpochY , UPI1 == UPI2 , Repairing1 == Repairing2 - >
%% TODO: So, technically speaking, I think, probably, that this is
%% cheating. But it's also safe cheating: the UPI & repairing
%% lists are identical. If we end up in this case, where the
%% epoch isn't strictly increasing, I'm *guessing*, is because
%% there's a flaw in the adoption of the null projection (as a
%% safety fall-back) and not always (?) writing that none proj
%% through the usual C300-and-reiterate cycle? In terms of chain
%% state transition, equal UPI and equal repairing is 100% safe.
%% However, in acceptin this transition, we run a risk of creating
%% an infinite-loop-via-always-same-epoch-number problem.
? RETURN2 ( true ) ;
true - >
? RETURN2 ( Else )
end ;
2015-07-05 05:52:50 +00:00
projection_transition_is_sane_final_review ( _ P1 , _ P2 , Else ) - >
? RETURN2 ( Else ) .
2015-07-15 03:44:56 +00:00
%% @doc Check if a projection transition is sane & safe with a
%% strictly increasing epoch number.
%%
%% NOTE: The return value convention is `true' for sane/safe and
%% `term() /= true' for any unsafe/insane value.
2015-07-04 06:52:44 +00:00
projection_transition_is_sane_with_si_epoch (
2015-07-04 05:57:38 +00:00
#projection_v1 { epoch_number = Epoch1 } = P1 ,
#projection_v1 { epoch_number = Epoch2 } = P2 ,
RelativeToServer , RetrospectiveP ) - >
2015-07-04 06:52:44 +00:00
case projection_transition_is_sane_except_si_epoch (
2015-07-04 05:57:38 +00:00
P1 , P2 , RelativeToServer , RetrospectiveP ) of
true - >
2015-07-04 06:52:44 +00:00
%% Must be a strictly increasing epoch.
2015-07-15 03:44:56 +00:00
case Epoch2 > Epoch1 of
true - >
? RETURN2 ( true ) ;
false - >
? RETURN2 ( { epoch_not_si , Epoch2 , 'not_gt' , Epoch1 } )
end ;
2015-07-04 05:57:38 +00:00
Else - >
2015-07-05 05:52:50 +00:00
? RETURN2 ( Else )
2015-07-04 05:57:38 +00:00
end .
2015-07-15 03:44:56 +00:00
%% @doc Check if a projection transition is sane & safe with the
%% exception of a strictly increasing epoch number (equality is ok).
%%
%% NOTE: The return value convention is `true' for sane/safe and
%% `term() /= true' for any unsafe/insane value.
2015-07-04 06:52:44 +00:00
projection_transition_is_sane_except_si_epoch (
2015-04-06 05:16:20 +00:00
#projection_v1 { epoch_number = Epoch1 ,
2015-08-06 08:48:22 +00:00
epoch_csum = CSum1 ,
creation_time = CreationTime1 ,
mode = CMode1 ,
author_server = AuthorServer1 ,
all_members = All_list1 ,
witnesses = Witness_list1 ,
down = Down_list1 ,
upi = UPI_list1 ,
repairing = Repairing_list1 ,
dbg = Dbg1 } = P1 ,
2015-04-06 05:16:20 +00:00
#projection_v1 { epoch_number = Epoch2 ,
2015-08-06 08:48:22 +00:00
epoch_csum = CSum2 ,
creation_time = CreationTime2 ,
mode = CMode2 ,
author_server = AuthorServer2 ,
all_members = All_list2 ,
witnesses = Witness_list2 ,
down = Down_list2 ,
upi = UPI_list2 ,
repairing = Repairing_list2 ,
dbg = Dbg2 } = P2 ,
2015-07-03 15:13:13 +00:00
RelativeToServer , _ _ TODO_RetrospectiveP ) - >
2015-07-05 05:52:50 +00:00
? RETURN2 ( undefined ) ,
2015-04-06 05:16:20 +00:00
try
%% General notes:
%%
%% I'm making no attempt to be "efficient" here. All of these data
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
%% structures are small, and the funcs aren't called zillions of times per
2015-04-06 05:16:20 +00:00
%% second.
2015-08-06 08:48:22 +00:00
CMode1 = CMode2 ,
2015-04-06 05:16:20 +00:00
true = is_integer ( Epoch1 ) andalso is_integer ( Epoch2 ) ,
true = is_binary ( CSum1 ) andalso is_binary ( CSum2 ) ,
{ _ , _ , _ } = CreationTime1 ,
{ _ , _ , _ } = CreationTime2 ,
2015-04-14 07:17:49 +00:00
true = is_atom ( AuthorServer1 ) andalso is_atom ( AuthorServer2 ) , % todo type may change?
2015-04-06 05:16:20 +00:00
true = is_list ( All_list1 ) andalso is_list ( All_list2 ) ,
2015-08-06 08:48:22 +00:00
true = is_list ( Witness_list1 ) andalso is_list ( Witness_list2 ) ,
2015-04-06 05:16:20 +00:00
true = is_list ( Down_list1 ) andalso is_list ( Down_list2 ) ,
true = is_list ( UPI_list1 ) andalso is_list ( UPI_list2 ) ,
true = is_list ( Repairing_list1 ) andalso is_list ( Repairing_list2 ) ,
true = is_list ( Dbg1 ) andalso is_list ( Dbg2 ) ,
2015-07-04 06:52:44 +00:00
%% Don't check for strictly increasing epoch here: that's the job of
%% projection_transition_is_sane_with_si_epoch().
true = Epoch2 > = Epoch1 ,
2015-04-06 05:16:20 +00:00
%% No duplicates
2015-08-06 08:48:22 +00:00
true = lists : sort ( Witness_list2 ) == lists : usort ( Witness_list2 ) ,
2015-04-06 05:16:20 +00:00
true = lists : sort ( Down_list2 ) == lists : usort ( Down_list2 ) ,
true = lists : sort ( UPI_list2 ) == lists : usort ( UPI_list2 ) ,
true = lists : sort ( Repairing_list2 ) == lists : usort ( Repairing_list2 ) ,
%% Disjoint-ness
2015-07-04 06:52:44 +00:00
All_list1 = All_list2 , % todo will probably change
2015-08-06 08:48:22 +00:00
%% true = lists:sort(All_list2) == lists:sort(Down_list2 ++ UPI_list2 ++
%% Repairing_list2),
[ ] = [ X | | X < - Witness_list2 , not lists : member ( X , All_list2 ) ] ,
2015-04-06 05:16:20 +00:00
[ ] = [ X | | X < - Down_list2 , not lists : member ( X , All_list2 ) ] ,
[ ] = [ X | | X < - UPI_list2 , not lists : member ( X , All_list2 ) ] ,
[ ] = [ X | | X < - Repairing_list2 , not lists : member ( X , All_list2 ) ] ,
DownS2 = sets : from_list ( Down_list2 ) ,
UPIS2 = sets : from_list ( UPI_list2 ) ,
RepairingS2 = sets : from_list ( Repairing_list2 ) ,
true = sets : is_disjoint ( DownS2 , UPIS2 ) ,
true = sets : is_disjoint ( DownS2 , RepairingS2 ) ,
true = sets : is_disjoint ( UPIS2 , RepairingS2 ) ,
2015-07-15 03:44:56 +00:00
%% We won't check the checksum of P1, but we will of P2.
P2 = machi_projection : update_checksum ( P2 ) ,
2015-04-06 05:16:20 +00:00
2015-08-31 13:14:28 +00:00
%% CP mode extra sanity checks
if CMode1 == cp_mode - >
Majority = full_majority_size ( All_list2 ) ,
if length ( UPI_list2 ) == 0 - >
ok ; % none projection
length ( UPI_list2 ) > = Majority - >
%% We have at least one non-witness
true = ( length ( UPI_list2 -- Witness_list2 ) > 0 ) ;
true - >
error ( { majority_not_met , UPI_list2 } )
end ;
CMode1 == ap_mode - >
ok
end ,
2015-07-05 05:52:50 +00:00
%% Hooray, all basic properties of the projection's elements are
%% not obviously bad. Now let's check if the UPI+Repairing->UPI
%% transition is good.
2015-08-06 08:48:22 +00:00
%%
%% NOTE: chain_state_transition_is_sane() only cares about strong
%% consistency violations and (because witness servers don't store
%% any data) doesn't care about witness servers. So we remove all
%% witnesses from the UPI lists before calling
%% chain_state_transition_is_sane()
UPI_list1w = UPI_list1 -- Witness_list1 ,
UPI_list2w = UPI_list2 -- Witness_list2 ,
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
? RETURN2 (
2015-08-06 08:48:22 +00:00
chain_state_transition_is_sane ( AuthorServer1 , UPI_list1w , Repairing_list1 ,
2015-09-11 08:32:52 +00:00
AuthorServer2 , UPI_list2w ,
RelativeToServer ) )
2015-04-06 05:16:20 +00:00
catch
_ Type : _ Err - >
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
? RETURN2 ( oops ) ,
2015-04-09 08:13:38 +00:00
S1 = machi_projection : make_summary ( P1 ) ,
S2 = machi_projection : make_summary ( P2 ) ,
2015-04-06 05:16:20 +00:00
Trace = erlang : get_stacktrace ( ) ,
2015-07-15 03:44:56 +00:00
%% There are basic data structure checks only, do not return `false'
%% here.
2015-04-06 05:16:20 +00:00
{ err , _ Type , _ Err , from , S1 , to , S2 , relative_to , RelativeToServer ,
2015-08-18 11:49:36 +00:00
react , get ( react ) ,
2015-04-06 05:16:20 +00:00
stack , Trace }
end .
2015-09-11 12:47:05 +00:00
poll_private_proj_is_upi_unanimous ( #ch_mgr { proj_unanimous = { _ , _ , _ } } = S ) - >
2015-08-26 09:47:39 +00:00
S ;
2015-09-11 12:47:05 +00:00
poll_private_proj_is_upi_unanimous ( #ch_mgr { consistency_mode = ap_mode ,
proj = Proj } = S ) - >
if Proj #projection_v1.upi == [ ] % Nobody to poll?
orelse
Proj #projection_v1.epoch_number == 0 - > % Skip polling for epoch 0?
S ;
true - >
%% Try it just a single time, no other waiting
poll_private_proj_is_upi_unanimous3 ( S )
end ;
2015-08-26 09:47:39 +00:00
poll_private_proj_is_upi_unanimous ( #ch_mgr { consistency_mode = cp_mode ,
proj_unanimous = false ,
proj = Proj } = S ) - >
if Proj #projection_v1.upi == [ ] % Nobody to poll?
orelse
Proj #projection_v1.epoch_number == 0 - > % Skip polling for epoch 0?
S ;
true - >
poll_private_proj_is_upi_unanimous_sleep ( 0 , S )
end .
poll_private_proj_is_upi_unanimous_sleep ( Count , S ) when Count > 2 - >
S ;
2015-08-29 09:30:53 +00:00
poll_private_proj_is_upi_unanimous_sleep ( Count , #ch_mgr { runenv = RunEnv } = S ) - >
Denom = case proplists : get_value ( use_partition_simulator , RunEnv , false ) of
true - >
20 ;
_ - >
1
end ,
timer : sleep ( ( ( Count * Count ) * 50 ) div Denom ) ,
2015-08-26 09:47:39 +00:00
case poll_private_proj_is_upi_unanimous3 ( S ) of
2015-08-27 08:58:43 +00:00
#ch_mgr { proj_unanimous = false } = S2 - >
poll_private_proj_is_upi_unanimous_sleep ( Count + 1 , S2 ) ;
2015-08-26 09:47:39 +00:00
S2 - >
2015-08-27 08:58:43 +00:00
S2
2015-08-26 09:47:39 +00:00
end .
2015-08-27 08:58:43 +00:00
poll_private_proj_is_upi_unanimous3 ( #ch_mgr { name = MyName , proj = P_current ,
2015-08-26 09:47:39 +00:00
opts = MgrOpts } = S ) - >
2015-09-04 08:17:49 +00:00
UPI = P_current #projection_v1.upi ,
EpochID = machi_projection : make_epoch_id ( P_current ) ,
2015-08-26 09:47:39 +00:00
{ Rs , S2 } = read_latest_projection_call_only2 ( private , UPI , S ) ,
2015-08-28 09:37:11 +00:00
Rs2 = [ if is_record ( R , projection_v1 ) - >
2015-09-04 08:17:49 +00:00
machi_projection : make_epoch_id ( R ) ;
2015-08-26 09:47:39 +00:00
true - >
2015-08-28 09:37:11 +00:00
R % probably {error, unwritten}
end | | R < - Rs ] ,
2015-08-26 09:47:39 +00:00
case lists : usort ( Rs2 ) of
[ EID ] when EID == EpochID - >
2015-08-28 09:37:11 +00:00
%% We have a debugging problem, alas. It would be really great
%% if we could preserve the dbg2 info that's in the current
%% projection that's on disk. However, the full dbg2 list
%% with 'react' trace data isn't in the #ch_mgr.proj copy of
%% the projection. So, go read it from the store.
%%
%% But of course there's another small problem. P_current could
%% be the result of make_zerf(), which helps us "fast forward" to
%% a newer CP mode projection. And so what we just read in the
%% 'Rs' at the top of this function may be for a new epoch that
%% we've never seen before and therefore doesn't exist in our
%% local private projection store. But if it came from
%% make_zerf(), by definition it must be annotated, so don't try
%% to proceed any further.
ProxyPid = proxy_pid ( MyName , S ) ,
2015-09-04 08:17:49 +00:00
Epoch = P_current #projection_v1.epoch_number ,
case ? FLU_PC : read_projection ( ProxyPid , private , Epoch ) of
2015-08-28 09:37:11 +00:00
{ ok , P_currentFull } - >
Now = os : timestamp ( ) ,
Annotation = make_annotation ( EpochID , Now ) ,
NewDbg2 = [ Annotation | P_currentFull #projection_v1.dbg2 ] ,
NewProj = P_currentFull #projection_v1 { dbg2 = NewDbg2 } ,
ProjStore = case get_projection_store_regname ( MgrOpts ) of
undefined - >
machi_flu_psup : make_proj_supname ( MyName ) ;
PStr - >
PStr
end ,
#projection_v1 { epoch_number = _ EpochRep ,
epoch_csum = < < _ CSumRep : 4 / binary , _ / binary > > ,
upi = _ UPIRep ,
2015-09-04 08:17:49 +00:00
repairing = _ RepairingRep } = NewProj ,
2015-08-28 09:37:11 +00:00
ok = machi_projection_store : write ( ProjStore , private , NewProj ) ,
2015-09-13 12:51:20 +00:00
io : format ( user , " \n CONFIRM epoch ~w ~w upi ~w rep ~w by ~w \n " , [ _ EpochRep , _ CSumRep , _ UPIRep , _ RepairingRep , MyName ] ) ,
2015-08-28 09:37:11 +00:00
%% Unwedge our FLU.
{ ok , NotifyPid } = machi_projection_store : get_wedge_notify_pid ( ProjStore ) ,
_ = machi_flu1 : update_wedge_state ( NotifyPid , false , EpochID ) ,
S2 #ch_mgr { proj_unanimous = Now } ;
_ - >
S2
end ;
2015-08-26 09:47:39 +00:00
_ Else - >
2015-08-27 11:27:33 +00:00
%% io:format(user, "poll by ~w: want ~W got ~W\n",
%% [MyName, EpochID, 6, _Else, 8]),
2015-08-26 09:47:39 +00:00
S2
end .
2015-04-06 05:16:20 +00:00
sleep_ranked_order ( MinSleep , MaxSleep , FLU , FLU_list ) - >
2015-04-09 08:13:38 +00:00
USec = calc_sleep_ranked_order ( MinSleep , MaxSleep , FLU , FLU_list ) ,
timer : sleep ( USec ) ,
USec .
calc_sleep_ranked_order ( MinSleep , MaxSleep , FLU , FLU_list ) - >
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
Front = lists : takewhile ( fun ( X ) - > X / = FLU end ,
lists : reverse ( lists : sort ( FLU_list ) ) ) ,
2015-05-06 02:41:04 +00:00
Index = length ( Front ) ,
2015-04-06 05:16:20 +00:00
NumNodes = length ( FLU_list ) ,
2015-04-30 08:28:43 +00:00
SleepChunk = if NumNodes == 0 - > 0 ;
2015-05-06 02:41:04 +00:00
true - > ( MaxSleep - MinSleep ) div NumNodes
2015-04-30 08:28:43 +00:00
end ,
2015-05-06 02:41:04 +00:00
MinSleep + ( SleepChunk * Index ) .
2015-04-06 05:16:20 +00:00
2015-04-09 05:44:58 +00:00
proxy_pid ( Name , #ch_mgr { proxies_dict = ProxiesDict } ) - >
orddict : fetch ( Name , ProxiesDict ) .
2015-05-02 07:59:28 +00:00
make_chmgr_regname ( A ) when is_atom ( A ) - >
2015-04-30 08:28:43 +00:00
list_to_atom ( atom_to_list ( A ) ++ " _chmgr " ) ;
2015-05-02 07:59:28 +00:00
make_chmgr_regname ( B ) when is_binary ( B ) - >
2015-04-30 08:28:43 +00:00
list_to_atom ( binary_to_list ( B ) ++ " _chmgr " ) .
2015-07-03 07:18:40 +00:00
gobble_calls ( StaticCall ) - >
2015-07-02 19:30:05 +00:00
receive
2015-07-03 07:18:40 +00:00
{ '$gen_call' , From , { trigger_react_to_env } } - >
2015-07-02 19:30:05 +00:00
gen_server : reply ( From , todo_overload ) ,
2015-07-03 07:18:40 +00:00
gobble_calls ( StaticCall )
2015-07-02 19:30:05 +00:00
after 1 - > % after 0 angers pulse.
ok
end .
2015-04-06 05:16:20 +00:00
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015-07-20 07:25:42 +00:00
perhaps_start_repair ( #ch_mgr { name = MyName ,
2015-08-05 07:05:03 +00:00
consistency_mode = CMode ,
2015-07-20 07:25:42 +00:00
repair_worker = undefined ,
proj = P_current } = S ) - >
2015-09-04 08:17:49 +00:00
case P_current of
2015-07-20 07:25:42 +00:00
#projection_v1 { creation_time = Start ,
upi = [ _ | _ ] = UPI ,
repairing = [ _ | _ ] } - >
RepairId = { MyName , os : timestamp ( ) } ,
RepairOpts = [ { repair_mode , repair } , verbose , { repair_id , RepairId } ] ,
%% RepairOpts = [{repair_mode, check}, verbose],
2015-08-05 07:05:03 +00:00
RepairFun = fun ( ) - > do_repair ( S , RepairOpts , CMode ) end ,
2015-07-20 07:25:42 +00:00
LastUPI = lists : last ( UPI ) ,
IgnoreStabilityTime_p = proplists : get_value ( ignore_stability_time ,
S #ch_mgr.opts , false ) ,
case timer : now_diff ( os : timestamp ( ) , Start ) div 1000000 of
N when MyName == LastUPI andalso
( IgnoreStabilityTime_p orelse
N > = ? REPAIR_START_STABILITY_TIME ) - >
{ WorkerPid , _ Ref } = spawn_monitor ( RepairFun ) ,
S #ch_mgr { repair_worker = WorkerPid ,
repair_start = os : timestamp ( ) ,
repair_final_status = undefined } ;
_ - >
S
end ;
2015-05-11 10:50:13 +00:00
_ - >
S
end ;
perhaps_start_repair ( S ) - >
S .
2015-07-20 07:25:42 +00:00
do_repair ( #ch_mgr { name = MyName ,
2015-08-05 06:50:32 +00:00
proj = #projection_v1 { witnesses = Witness_list ,
upi = UPI0 ,
2015-07-20 07:25:42 +00:00
repairing = [ _ | _ ] = Repairing ,
members_dict = MembersDict } } = S ,
2015-08-05 07:05:03 +00:00
Opts , RepairMode ) - >
2015-05-12 14:37:20 +00:00
ETS = ets : new ( repair_stats , [ private , set ] ) ,
ETS_T_Keys = [ t_in_files , t_in_chunks , t_in_bytes ,
t_out_files , t_out_chunks , t_out_bytes ,
t_bad_chunks , t_elapsed_seconds ] ,
[ ets : insert ( ETS , { K , 0 } ) | | K < - ETS_T_Keys ] ,
2015-07-20 07:25:42 +00:00
{ ok , MyProj } = ? FLU_PC : read_latest_projection ( proxy_pid ( MyName , S ) ,
private ) ,
MyEpochID = machi_projection : get_epoch_id ( MyProj ) ,
RepairEpochIDs = [ case ? FLU_PC : read_latest_projection ( proxy_pid ( Rep , S ) ,
private ) of
{ ok , Proj } - >
machi_projection : get_epoch_id ( Proj ) ;
_ - >
unknown
end | | Rep < - Repairing ] ,
case lists : usort ( RepairEpochIDs ) of
[ MyEpochID ] - >
T1 = os : timestamp ( ) ,
RepairId = proplists : get_value ( repair_id , Opts , id1 ) ,
error_logger : info_msg (
" Repair start: tail ~p of ~p -> ~p , ~p ID ~w \n " ,
2015-08-05 06:50:32 +00:00
[ MyName , UPI0 , Repairing , RepairMode , RepairId ] ) ,
2015-07-20 07:25:42 +00:00
2015-08-05 06:50:32 +00:00
UPI = UPI0 -- Witness_list ,
2015-08-05 07:05:03 +00:00
Res = machi_chain_repair : repair ( RepairMode , MyName , Repairing , UPI ,
2015-07-20 07:25:42 +00:00
MembersDict , ETS , Opts ) ,
T2 = os : timestamp ( ) ,
Elapsed = ( timer : now_diff ( T2 , T1 ) div 1000 ) / 1000 ,
ets : insert ( ETS , { t_elapsed_seconds , Elapsed } ) ,
Summary = case Res of ok - > " success " ;
2015-05-12 14:37:20 +00:00
_ - > " FAILURE "
2015-07-20 07:25:42 +00:00
end ,
Stats = [ { K , ets : lookup_element ( ETS , K , 2 ) } | | K < - ETS_T_Keys ] ,
error_logger : info_msg (
" Repair ~s : tail ~p of ~p finished ~p repair ID ~w : "
" ~p \n Stats ~p \n " ,
2015-08-05 06:50:32 +00:00
[ Summary , MyName , UPI0 , RepairMode , RepairId ,
2015-07-20 07:25:42 +00:00
Res , Stats ] ) ,
ets : delete ( ETS ) ,
exit ( { repair_final_status , Res } ) ;
_ - >
exit ( not_all_in_same_epoch )
end .
2015-05-12 14:37:20 +00:00
2015-05-13 08:58:54 +00:00
sanitize_repair_state ( #ch_mgr { repair_final_status = Res ,
proj = #projection_v1 { upi = [ _ | _ ] } } = S )
2015-05-11 10:50:13 +00:00
when Res / = undefined - >
S #ch_mgr { repair_worker = undefined , repair_start = undefined ,
repair_final_status = undefined } ;
sanitize_repair_state ( S ) - >
S .
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015-09-11 07:03:49 +00:00
perhaps_call_t ( #ch_mgr { name = MyName } = S , Partitions , FLU , DoIt ) - >
2015-09-11 07:45:29 +00:00
ProxyPid = proxy_pid ( FLU , S ) ,
perhaps_call ( ProxyPid , MyName , Partitions , FLU , DoIt ) .
perhaps_call ( ProxyPid , MyName , Partitions , FLU , DoIt ) - >
2015-04-06 05:16:20 +00:00
try
2015-09-11 07:45:29 +00:00
perhaps_call2 ( ProxyPid , MyName , Partitions , FLU , DoIt )
2015-04-06 05:16:20 +00:00
catch
exit : timeout - >
2015-09-08 07:11:54 +00:00
update_remember_down_list ( FLU ) ,
2015-05-01 15:33:49 +00:00
{ error , partition } ;
exit : { timeout , _ } - >
2015-09-08 07:11:54 +00:00
update_remember_down_list ( FLU ) ,
2015-05-01 15:33:49 +00:00
{ error , partition }
2015-04-06 05:16:20 +00:00
end .
2015-09-11 07:45:29 +00:00
perhaps_call2 ( ProxyPid , MyName , Partitions , FLU , DoIt ) - >
2015-04-09 08:13:38 +00:00
RemoteFLU_p = FLU / = MyName ,
2015-05-01 15:33:49 +00:00
erase ( bad_sock ) ,
2015-04-06 05:16:20 +00:00
case RemoteFLU_p andalso lists : member ( { MyName , FLU } , Partitions ) of
false - >
2015-04-09 05:44:58 +00:00
Res = DoIt ( ProxyPid ) ,
2015-05-01 15:33:49 +00:00
if Res == { error , partition } - >
2015-09-08 07:11:54 +00:00
update_remember_down_list ( FLU ) ;
2015-05-01 15:33:49 +00:00
true - >
ok
end ,
2015-04-06 05:16:20 +00:00
case RemoteFLU_p andalso lists : member ( { FLU , MyName } , Partitions ) of
false - >
Res ;
_ - >
2015-04-09 08:13:38 +00:00
( catch put ( react , [ { timeout2 , me , MyName , to , FLU , RemoteFLU_p , Partitions } | get ( react ) ] ) ) ,
2015-04-06 05:16:20 +00:00
exit ( timeout )
end ;
2015-09-08 07:11:54 +00:00
true - >
2015-04-09 08:13:38 +00:00
( catch put ( react , [ { timeout1 , me , MyName , to , FLU , RemoteFLU_p , Partitions } | get ( react ) ] ) ) ,
2015-04-06 05:16:20 +00:00
exit ( timeout )
end .
2015-09-08 07:11:54 +00:00
%% Why are we using the process dictionary for this? In part because
%% we're lazy and in part because we don't want to clutter up the
%% return value of perhaps_call_t() in order to make perhaps_call_t()
%% a 100% pure function.
init_remember_down_list ( ) - >
put ( remember_down_list , [ ] ) .
update_remember_down_list ( FLU ) - >
2015-09-11 07:45:29 +00:00
catch put ( remember_down_list ,
lists : usort ( [ FLU | get_remember_down_list ( ) ] ) ) .
2015-05-01 15:33:49 +00:00
2015-09-08 07:11:54 +00:00
get_remember_down_list ( ) - >
get ( remember_down_list ) .
2015-04-06 05:16:20 +00:00
2015-05-01 15:33:49 +00:00
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015-07-03 10:21:41 +00:00
%% @doc A simple technique for checking chain state transition safety.
%%
%% Math tells us that any change state `UPI1' plus `Repair1' to state
%% `UPI2' is OK as long as `UPI2' is a concatenation of some
%% order-preserving combination from `UPI1' with some order-preserving
%% combination from `Repair1'.
%%
%% ```
%% Good_UPI2s = [ X ++ Y || X <- machi_util:ordered_combinations(UPI1),
%% Y <- machi_util:ordered_combinations(Repair1)]'''
%%
%% Rather than creating that list and then checking if `UPI2' is in
%% it, we try a `diff'-like technique to check for basic state
%% transition safety. See docs for {@link mk/3} for more detail.
%%
%% ```
%% 2> machi_chain_manager1:mk([a,b], [], [a]).
%% {[keep,del],[]} %% good transition
%% 3> machi_chain_manager1:mk([a,b], [], [b,a]).
%% {[del,keep],[]} %% bad transition: too few 'keep' for UPI2's length 2
%% 4> machi_chain_manager1:mk([a,b], [c,d,e], [a,d]).
%% {[keep,del],[2]} %% good transition
%% 5> machi_chain_manager1:mk([a,b], [c,d,e], [a,bogus]).
%% {[keep,del],[error]} %% bad transition: 'bogus' not in Repair1'''
simple_chain_state_transition_is_sane ( UPI1 , Repair1 , UPI2 ) - >
2015-07-05 05:52:50 +00:00
? RETURN2 ( simple_chain_state_transition_is_sane ( undefined , UPI1 , Repair1 ,
undefined , UPI2 ) ) .
2015-07-03 13:05:35 +00:00
2015-07-15 03:44:56 +00:00
%% @doc Simple check if a projection transition is sane & safe: we assume
%% that the caller has checked basic projection data structure contents.
%%
%% NOTE: The return value convention is `true' for sane/safe and
%% `term() /= true' for any unsafe/insane value.
2015-07-03 15:13:13 +00:00
simple_chain_state_transition_is_sane ( _ Author1 , UPI1 , Repair1 , Author2 , UPI2 ) - >
2015-07-03 10:21:41 +00:00
{ KeepsDels , Orders } = mk ( UPI1 , Repair1 , UPI2 ) ,
NumKeeps = length ( [ x | | keep < - KeepsDels ] ) ,
NumOrders = length ( Orders ) ,
2015-07-15 03:44:56 +00:00
NoErrorInOrders = ( false == lists : member ( error , Orders ) ) ,
OrdersOK = ( Orders == lists : sort ( Orders ) ) ,
UPI2LengthOK = ( length ( UPI2 ) == NumKeeps + NumOrders ) ,
Answer1 = NoErrorInOrders andalso OrdersOK andalso UPI2LengthOK ,
catch ? REACT ( { simple , ? LINE ,
[ { sane , answer1 , Answer1 ,
author1 , _ Author1 , upi1 , UPI1 , repair1 , Repair1 ,
author2 , Author2 , upi2 , UPI2 ,
keepsdels , KeepsDels , orders , Orders , numKeeps , NumKeeps ,
2015-09-01 13:10:45 +00:00
numOrders , NumOrders , answer1 , Answer1 } ,
{ why2 , get ( why2 ) } ] } ) ,
2015-07-03 13:05:35 +00:00
if not Answer1 - >
2015-07-03 14:17:34 +00:00
? RETURN2 ( Answer1 ) ;
2015-07-03 13:05:35 +00:00
true - >
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
if Orders == [ ] - >
%% No repairing have joined UPI2. Keep original answer.
2015-07-03 14:17:34 +00:00
? RETURN2 ( Answer1 ) ;
2015-07-03 13:05:35 +00:00
Author2 == undefined - >
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
%% At least one Repairing1 element is now in UPI2.
%% We need Author2 to make better decision. Go
%% with what we know, silly caller for not giving
%% us what we need.
2015-07-03 14:17:34 +00:00
? RETURN2 ( Answer1 ) ;
2015-07-03 13:05:35 +00:00
Author2 / = undefined - >
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
%% At least one Repairing1 element is now in UPI2.
%% We permit only the tail to author such a UPI2.
2015-07-03 13:05:35 +00:00
case catch ( lists : last ( UPI1 ) ) of
UPI1_tail when UPI1_tail == Author2 - >
2015-07-03 14:17:34 +00:00
? RETURN2 ( true ) ;
2015-07-05 05:52:50 +00:00
UPI1_tail - >
2015-08-24 10:04:26 +00:00
? RETURN2 ( { expected_author2 , UPI1_tail ,
[ { upi1 , UPI1 } ,
{ repair1 , Repair1 } ,
{ author2 , Author2 } ,
{ upi2 , UPI2 } ] } )
2015-07-03 13:05:35 +00:00
end
end
end .
2015-07-03 10:21:41 +00:00
2015-07-15 03:44:56 +00:00
%% @doc Check if a projection transition is sane & safe: we assume
%% that the caller has checked basic projection data structure contents.
%%
%% NOTE: The return value convention is `true' for sane/safe and `term() /=
%% true' for any unsafe/insane value. This function (and its callee
%% functions) are the only functions (throughout all of the chain state
%% transition sanity checking functions) that is allowed to return `false'.
2015-09-11 08:32:52 +00:00
chain_state_transition_is_sane ( Author1 , UPI1 , Repair1 , Author2 , UPI2 ,
RelativeToServer ) - >
2015-07-03 10:21:41 +00:00
ToSelfOnly_p = if UPI2 == [ Author2 ] - > true ;
true - > false
end ,
2015-07-03 13:05:35 +00:00
Disjoint_UPIs = ordsets : is_disjoint ( ordsets : from_list ( UPI1 ) ,
ordsets : from_list ( UPI2 ) ) ,
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
%% This if statement contains the only exceptions that we make to
%% the judgement of simple_chain_state_transition_is_sane().
2015-07-03 10:21:41 +00:00
if ToSelfOnly_p - >
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
%% The transition is to UPI2=[Author2].
2015-07-04 06:52:44 +00:00
%% For AP mode, this transition is always safe (though not
%% always optimal for highest availability).
2015-07-03 14:17:34 +00:00
? RETURN2 ( true ) ;
2015-07-03 13:05:35 +00:00
Disjoint_UPIs - >
WIP: 1st part of moving old chain state transtion code to new
Ha, famous last words, amirite?
%% The chain sequence/order checks at the bottom of this function aren't
%% as easy-to-read as they ought to be. However, I'm moderately confident
%% that it isn't buggy. TODO: refactor them for clarity.
So, now machi_chain_manager1:projection_transition_is_sane() is using
newer, far less buggy code to make sanity decisions.
TODO: Add support for Retrospective mode. TODO is it really needed?
Examples of how the old code sucks and the new code sucks less.
138> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxxx..x.xxxxxx..x.x....x..xx........................................................Failed! After 69 tests.
[a,b,c]
{c,[a,b,c],[c,b],b,[b,a],[b,a,c]}
Old_res ([335,192,166,160,153,139]): true
New_res: false (why line [1936])
Shrinking xxxxxxxxxxxx.xxxxxxx.xxx.xxxxxxxxxxxxxxxxx(3 times)
[a,b,c]
%% {Author1,UPI1, Repair1,Author2,UPI2, Repair2} %%
{c, [a,b,c],[], a, [b,a],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: we've swapped order of a & b, which is bad.
139> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxx..x...xx..........xxx..x..............x......x............................................(x10)...(x1)........Failed! After 120 tests.
[b,c,a]
{c,[c,a],[c],a,[a,b],[b,a]}
Old_res ([335,192,185,160,153,123]): true
New_res: false (why line [1936])
Shrinking xx.xxxxxx.x.xxxxxxxx.xxxxxxxxxxx(4 times)
[b,a,c]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{a, [c], [], c, [c,b],[]}
Old_res ([338,185,160,153,147]): true
New_res: false (why line [1936])
false
Old code is wrong: b wasn't repairing in the previous state.
150> eqc:quickcheck(eqc:testing_time(10, machi_chain_manager1_test:prop_compare_legacy_with_v2_chain_transition_check(whole))).
xxxxxxxxxxx....x...xxxxx..xx.....x.......xxx..x.......xxx...................x................x......(x10).....(x1)........xFailed! After 130 tests.
[c,a,b]
{b,[c],[b,a,c],c,[c,a,b],[b]}
Old_res ([335,214,185,160,153,147]): true
New_res: false (why line [1936])
Shrinking xxxx.x.xxx.xxxxxxx.xxxxxxxxx(4 times)
[c,b,a]
%% {Author1,UPI1,Repair1,Author2,UPI2, Repair2} %%
{c, [c], [a,b], c, [c,b,a],[]}
Old_res ([335,328,185,160,153,111]): true
New_res: false (why line [1981,1679])
false
Old code is wrong: a & b were repairing but UPI2 has a & b in the wrong order.
2015-07-03 15:01:54 +00:00
%% The transition from UPI1 -> UPI2 where the two are
%% disjoint/no FLUs in common.
2015-09-11 08:32:52 +00:00
%% For AP mode, this transition is safe (though not
%% always optimal for highest availability) if I am not
%% a member of the new UPI.
? RETURN2 ( not lists : member ( RelativeToServer , UPI2 ) ) ;
2015-07-03 10:21:41 +00:00
true - >
2015-07-05 05:52:50 +00:00
? RETURN2 (
simple_chain_state_transition_is_sane ( Author1 , UPI1 , Repair1 ,
Author2 , UPI2 ) )
2015-07-03 10:21:41 +00:00
end .
%% @doc Create a 2-tuple that describes how `UPI1' + `Repair1' are
%% transformed into `UPI2' in a chain state change.
%%
%% The 1st part of the 2-tuple is a list of `keep' and `del' instructions,
%% relative to the items in UPI1 and whether they are present (`keep') or
%% absent (`del') in `UPI2'.
%%
%% The 2nd part of the 2-tuple is `list(non_neg_integer()|error)' that
%% describes the relative order of items in `Repair1' that appear in
%% `UPI2'. The `error' atom is used to denote items not present in
%% `Repair1'.
mk ( UPI1 , Repair1 , UPI2 ) - >
mk ( UPI1 , Repair1 , UPI2 , [ ] ) .
mk ( [ X | UPI1 ] , Repair1 , [ X | UPI2 ] , Acc ) - >
mk ( UPI1 , Repair1 , UPI2 , [ keep | Acc ] ) ;
mk ( [ X | UPI1 ] , Repair1 , UPI2 , Acc ) - >
mk ( UPI1 , Repair1 , UPI2 -- [ X ] , [ del | Acc ] ) ;
mk ( [ ] , [ ] , [ ] , Acc ) - >
{ lists : reverse ( Acc ) , [ ] } ;
mk ( [ ] , Repair1 , UPI2 , Acc ) - >
{ lists : reverse ( Acc ) , machi_util : mk_order ( UPI2 , Repair1 ) } .
2015-07-15 12:58:21 +00:00
scan_dir ( Dir , FileFilterFun , FoldEachFun , FoldEachAcc ) - >
Files = filelib : wildcard ( Dir ++ " /* " ) ,
Xs = [ binary_to_term ( element ( 2 , file : read_file ( File ) ) ) | | File < - Files ] ,
Xs2 = FileFilterFun ( Xs ) ,
lists : foldl ( FoldEachFun , FoldEachAcc , Xs2 ) .
get_ps ( #projection_v1 { epoch_number = Epoch , dbg = Dbg } , Acc ) - >
[ { Epoch , proplists : get_value ( ps , Dbg , [ ] ) } | Acc ] .
strip_dbg2 ( P ) - >
P #projection_v1 { dbg2 = [ stripped ] } .
has_not_sane ( #projection_v1 { epoch_number = Epoch , dbg2 = Dbg2 } , Acc ) - >
Reacts = proplists : get_value ( react , Dbg2 , [ ] ) ,
case [ X | | { _ State , _ Line , [ not_sane | _ ] } = X < - Reacts ] of
[ ] - >
Acc ;
Xs - >
[ { Epoch , Xs } | Acc ]
end .
2015-08-12 08:53:39 +00:00
full_majority_size ( N ) when is_integer ( N ) - >
( N div 2 ) + 1 ;
full_majority_size ( L ) when is_list ( L ) - >
full_majority_size ( length ( L ) ) .
make_zerf ( #projection_v1 { epoch_number = OldEpochNum ,
all_members = AllMembers ,
members_dict = MembersDict ,
2015-09-04 08:17:49 +00:00
witnesses = OldWitness_list
2015-08-12 08:53:39 +00:00
} = _ LastProj ,
#ch_mgr { name = MyName ,
consistency_mode = cp_mode ,
runenv = RunEnv1 } = S ) - >
{ Up , _ Partitions , _ RunEnv2 } = calc_up_nodes ( MyName ,
AllMembers , RunEnv1 ) ,
2015-08-27 16:55:31 +00:00
( catch put ( yyy_hack , [ { up , Up } | get ( yyy_hack ) ] ) ) ,
2015-08-12 08:53:39 +00:00
MajoritySize = full_majority_size ( AllMembers ) ,
case length ( Up ) > = MajoritySize of
false - >
2015-08-29 09:01:13 +00:00
%% Make it appear like nobody is up now: we'll have to
%% wait until the Up list changes so that
%% zerf_find_last_common() can confirm a common stable
%% last stable epoch.
2015-08-30 10:53:47 +00:00
P = make_none_projection ( OldEpochNum ,
MyName , AllMembers , OldWitness_list ,
2015-08-29 09:01:13 +00:00
MembersDict ) ,
machi_projection : update_checksum (
2015-08-30 10:53:47 +00:00
P #projection_v1 { mode = cp_mode ,
2015-08-29 09:01:13 +00:00
dbg2 = [ zerf_none , { up , Up } , { maj , MajoritySize } ] } ) ;
2015-08-12 08:53:39 +00:00
true - >
make_zerf2 ( OldEpochNum , Up , MajoritySize , MyName ,
2015-09-04 08:17:49 +00:00
AllMembers , OldWitness_list , MembersDict , S )
2015-08-12 08:53:39 +00:00
end .
2015-08-27 07:19:22 +00:00
make_zerf2 ( OldEpochNum , Up , MajoritySize , MyName , AllMembers , OldWitness_list ,
2015-09-04 08:17:49 +00:00
MembersDict , S ) - >
2015-08-12 08:53:39 +00:00
try
2015-08-28 11:06:09 +00:00
#projection_v1 { epoch_number = Epoch } = Proj =
zerf_find_last_common ( MajoritySize , Up , S ) ,
2015-09-04 08:17:49 +00:00
Proj2 = Proj #projection_v1 { dbg2 = [ { make_zerf , Epoch } ] } ,
2015-08-27 11:27:33 +00:00
%% io:format(user, "ZERF ~w\n",[machi_projection:make_summary(Proj2)]),
2015-08-27 07:19:22 +00:00
Proj2
2015-08-12 08:53:39 +00:00
catch
throw : { zerf , no_common } - >
2015-08-27 07:19:22 +00:00
%% Epoch 0 special case: make the "all" projection.
%% calc_projection2() will then filter out any FLUs that
%% aren't currently up to create the first chain. If not
%% enough are up now, then it will fail to create a first
%% chain.
%%
%% If epoch 0 isn't the only epoch that we've looked at,
%% but we still couldn't find a common projection, then
%% we still need to default to the "all" projection and let
%% subsequent chain calculations do their calculations....
P = make_all_projection ( MyName , AllMembers , OldWitness_list ,
MembersDict ) ,
2015-09-13 06:58:45 +00:00
P2 = machi_projection : update_checksum (
P #projection_v1 { epoch_number = OldEpochNum ,
mode = cp_mode , dbg2 = [ zerf_all ] } ) ,
2015-08-27 07:19:22 +00:00
P2 ;
2015-08-12 08:53:39 +00:00
_ X : _ Y - >
throw ( { zerf , { damn_exception , Up , _ X , _ Y , erlang : get_stacktrace ( ) } } )
end .
2015-08-27 07:19:22 +00:00
zerf_find_last_common ( MajoritySize , Up , S ) - >
case lists : reverse (
lists : sort (
lists : flatten (
[ zerf_find_last_annotated ( FLU , MajoritySize , S ) | | FLU < - Up ] ) ) ) of
2015-08-12 08:53:39 +00:00
[ ] - >
2015-08-27 07:19:22 +00:00
throw ( { zerf , no_common } ) ;
2015-08-27 08:58:43 +00:00
[ P | _ ] = _ TheList - >
2015-08-27 07:19:22 +00:00
%% TODO is this simple sort really good enough?
P
end .
zerf_find_last_annotated ( FLU , MajoritySize , S ) - >
Proxy = proxy_pid ( FLU , S ) ,
{ ok , Epochs } = ? FLU_PC : list_all_projections ( Proxy , private , 60 * 1000 ) ,
P = lists : foldl (
2015-08-27 08:58:43 +00:00
fun ( _ Epoch , #projection_v1 { } = Proj ) - >
2015-08-27 07:19:22 +00:00
Proj ;
( Epoch , Acc ) - >
{ ok , Proj } = ? FLU_PC : read_projection ( Proxy , private ,
Epoch , ? TO * 10 ) ,
2015-08-27 16:55:31 +00:00
case is_annotated ( Proj ) of
false - >
( catch put ( yyy_hack , [ { FLU , Epoch , not_annotated } | get ( yyy_hack ) ] ) ) ,
2015-08-27 07:19:22 +00:00
Acc ;
{ { ConfEpoch , ConfCSum } , _ ConfTime } - >
Px = if ConfEpoch == Epoch - >
2015-09-04 08:17:49 +00:00
( catch put ( yyy_hack , [ { FLU , Epoch , ok1 } | get ( yyy_hack ) ] ) ) ,
2015-08-27 07:19:22 +00:00
Proj ;
true - >
%% Sanity checking
2015-09-04 08:17:49 +00:00
ConfEpoch = Proj #projection_v1.epoch_number ,
ConfCSum = Proj #projection_v1.epoch_csum ,
( catch put ( yyy_hack , [ { FLU , Epoch , ok2 } | get ( yyy_hack ) ] ) ) ,
2015-08-27 16:55:31 +00:00
Proj
2015-08-27 07:19:22 +00:00
end ,
if length ( Px #projection_v1.upi ) > = MajoritySize - >
2015-09-04 08:17:49 +00:00
( catch put ( yyy_hack , [ { FLU , Epoch , yay } | get ( yyy_hack ) ] ) ) ,
2015-08-27 07:19:22 +00:00
Px ;
true - >
2015-09-04 08:17:49 +00:00
( catch put ( yyy_hack , [ { FLU , Epoch , skip } | get ( yyy_hack ) ] ) ) ,
2015-08-27 07:19:22 +00:00
Acc
end
end
2015-08-27 08:58:43 +00:00
end , first_accumulator , lists : reverse ( Epochs ) ) ,
2015-08-27 07:19:22 +00:00
if is_record ( P , projection_v1 ) - >
P ;
true - >
[ ] % lists:flatten() will destroy
2015-08-12 08:53:39 +00:00
end .
2015-08-13 09:45:15 +00:00
perhaps_verbose_c110 ( P_latest2 , S ) - >
case proplists : get_value ( private_write_verbose , S #ch_mgr.opts ) of
true - >
{ _ , _ , C } = os : timestamp ( ) ,
MSec = trunc ( C / 1000 ) ,
{ HH , MM , SS } = time ( ) ,
2015-08-28 09:37:11 +00:00
Dbg2X = lists : keydelete ( react , 1 ,
P_latest2 #projection_v1.dbg2 ) ++
[ { is_annotated , is_annotated ( P_latest2 ) } ] ,
P_latest2x = P_latest2 #projection_v1 { dbg2 = Dbg2X } , % limit verbose len.
2015-09-04 08:17:49 +00:00
Last2 = get ( last_verbose ) ,
Summ2 = machi_projection : make_summary ( P_latest2x ) ,
case proplists : get_value ( private_write_verbose ,
S #ch_mgr.opts ) of
true when Summ2 / = Last2 - >
put ( last_verbose , Summ2 ) ,
? V ( " \n ~2..0w : ~2..0w : ~2..0w . ~3..0w ~p uses plain: ~w \n " ,
[ HH , MM , SS , MSec , S #ch_mgr.name , Summ2 ] ) ;
_ - >
ok
2015-08-13 09:45:15 +00:00
end ;
_ - >
ok
end .
2015-08-14 13:29:20 +00:00
2015-08-26 09:47:39 +00:00
calc_consistency_mode ( _ Witness_list = [ ] ) - >
ap_mode ;
calc_consistency_mode ( _ Witness_list ) - >
cp_mode .
set_proj ( S , Proj ) - >
S #ch_mgr { proj = Proj , proj_unanimous = false } .
2015-08-27 08:58:43 +00:00
make_annotation ( EpochID , Time ) - >
{ private_proj_is_upi_unanimous , { EpochID , Time } } .
2015-08-27 16:55:31 +00:00
is_annotated ( #projection_v1 { dbg2 = Dbg2 } ) - >
proplists : get_value ( private_proj_is_upi_unanimous , Dbg2 , false ) .
2015-08-28 11:06:09 +00:00
2015-08-31 08:57:37 +00:00
make_basic_comparison_stable ( P ) - >
P #projection_v1 { creation_time = undefined ,
dbg = [ ] ,
dbg2 = [ ] ,
members_dict = [ ] } .
2015-08-29 10:59:46 +00:00
has_make_zerf_annotation ( P ) - >
case proplists : get_value ( make_zerf , P #projection_v1.dbg2 ) of
Z_epoch when Z_epoch == P #projection_v1.epoch_number - >
true ;
_ - >
false
end .
2015-09-04 09:51:01 +00:00
2015-09-08 07:11:54 +00:00
get_unfit_list ( FitnessServer ) - >
try
machi_fitness : get_unfit_list ( FitnessServer )
catch exit : { noproc , _ } - >
%% We are probably operating in an eunit test that hasn't used
%% the machi_flu_psup supervisor for startup.
[ ]
end .
2015-09-07 06:38:23 +00:00