Add kick_projection_reaction, implement yo:tell_author_yo()

This commit is contained in:
Scott Lystig Fritchie 2015-07-03 04:30:05 +09:00
parent c6870a1c86
commit 2b64028bbd
9 changed files with 140 additions and 38 deletions

View file

@ -525,6 +525,11 @@ message Mpb_LL_ListAllProjectionsResp {
repeated uint32 epochs = 2; repeated uint32 epochs = 2;
} }
// Low level API: kick_projection_reaction request, NO RESPONSE!
message Mpb_LL_KickProjectionReactionReq {
}
////////////////////////////////////////// //////////////////////////////////////////
// //
// Low API request & response wrapper // Low API request & response wrapper
@ -552,6 +557,7 @@ message Mpb_LL_Request {
optional Mpb_LL_WriteProjectionReq proj_wp = 15; optional Mpb_LL_WriteProjectionReq proj_wp = 15;
optional Mpb_LL_GetAllProjectionsReq proj_ga = 16; optional Mpb_LL_GetAllProjectionsReq proj_ga = 16;
optional Mpb_LL_ListAllProjectionsReq proj_la = 17; optional Mpb_LL_ListAllProjectionsReq proj_la = 17;
optional Mpb_LL_KickProjectionReactionReq proj_kp = 18;
optional Mpb_LL_AppendChunkReq append_chunk = 30; optional Mpb_LL_AppendChunkReq append_chunk = 30;
optional Mpb_LL_WriteChunkReq write_chunk = 31; optional Mpb_LL_WriteChunkReq write_chunk = 31;
@ -585,6 +591,7 @@ message Mpb_LL_Response {
optional Mpb_LL_WriteProjectionResp proj_wp = 15; optional Mpb_LL_WriteProjectionResp proj_wp = 15;
optional Mpb_LL_GetAllProjectionsResp proj_ga = 16; optional Mpb_LL_GetAllProjectionsResp proj_ga = 16;
optional Mpb_LL_ListAllProjectionsResp proj_la = 17; optional Mpb_LL_ListAllProjectionsResp proj_la = 17;
// No reponse to Mpb_LL_KickProjectionReactionReq = 18;
optional Mpb_LL_AppendChunkResp append_chunk = 30; optional Mpb_LL_AppendChunkResp append_chunk = 30;
optional Mpb_LL_WriteChunkResp write_chunk = 31; optional Mpb_LL_WriteChunkResp write_chunk = 31;

View file

@ -284,7 +284,8 @@ handle_call({test_read_latest_public_projection, ReadRepairP}, _From, S) ->
do_cl_read_latest_public_projection(ReadRepairP, S), do_cl_read_latest_public_projection(ReadRepairP, S),
Res = {Perhaps, Val, ExtraInfo}, Res = {Perhaps, Val, ExtraInfo},
{reply, Res, S2}; {reply, Res, S2};
handle_call({test_react_to_env}, _From, S) -> handle_call({test_react_to_env}=Call, _From, S) ->
gobble_calls(Call),
{TODOtodo, S2} = do_react_to_env(S), {TODOtodo, S2} = do_react_to_env(S),
{reply, TODOtodo, S2}; {reply, TODOtodo, S2};
handle_call(_Call, _From, S) -> handle_call(_Call, _From, S) ->
@ -304,9 +305,7 @@ handle_info(tick_check_environment, S) ->
N when is_integer(N), N > 0 -> N when is_integer(N), N > 0 ->
%% We are flapping. Set ignore_timer=true and schedule a %% We are flapping. Set ignore_timer=true and schedule a
%% reminder to stop ignoring. This slows down the rate of %% reminder to stop ignoring. This slows down the rate of
%% flapping. If/when the yo:tell_author_yo() function in %% flapping.
%% state C200 is ever implemented, then it should be
%% implemented via the test_react_to_env style.
erlang:send_after(N*1000, self(), stop_ignoring_timer), erlang:send_after(N*1000, self(), stop_ignoring_timer),
{noreply, S3#ch_mgr{ignore_timer=true}}; {noreply, S3#ch_mgr{ignore_timer=true}};
_ -> _ ->
@ -1349,13 +1348,10 @@ react_to_env_C110(P_latest, #ch_mgr{name=MyName} = S) ->
P_latest2 = machi_projection:update_dbg2(P_latest, Extra_todo), P_latest2 = machi_projection:update_dbg2(P_latest, Extra_todo),
MyNamePid = proxy_pid(MyName, S), MyNamePid = proxy_pid(MyName, S),
Goo = P_latest2#projection_v1.epoch_number,
%% This is the local projection store. Use a larger timeout, so %% This is the local projection store. Use a larger timeout, so
%% that things locally are pretty horrible if we're killed by a %% that things locally are pretty horrible if we're killed by a
%% timeout exception. %% timeout exception.
%% ok = ?FLU_PC:write_projection(MyNamePid, private, P_latest2, ?TO*30),
Goo = P_latest2#projection_v1.epoch_number,
%% io:format(user, "HEE110 ~w ~w ~w\n", [S#ch_mgr.name, self(), lists:reverse(get(react))]),
{ok,Goo} = {?FLU_PC:write_projection(MyNamePid, private, P_latest2, ?TO*30),Goo}, {ok,Goo} = {?FLU_PC:write_projection(MyNamePid, private, P_latest2, ?TO*30),Goo},
case proplists:get_value(private_write_verbose, S#ch_mgr.opts) of case proplists:get_value(private_write_verbose, S#ch_mgr.opts) of
true -> true ->
@ -1412,13 +1408,11 @@ react_to_env_C120(P_latest, FinalProps, #ch_mgr{proj_history=H} = S) ->
react_to_env_C200(Retries, P_latest, S) -> react_to_env_C200(Retries, P_latest, S) ->
?REACT(c200), ?REACT(c200),
try try
%% TODO: This code works "well enough" without actually AuthorProxyPid = proxy_pid(P_latest#projection_v1.author_server, S),
%% telling anybody anything. Do we want to rip this out? ?FLU_PC:kick_projection_reaction(AuthorProxyPid, [])
%% Actually implement it? None of the above?
yo:tell_author_yo(P_latest#projection_v1.author_server)
catch _Type:_Err -> catch _Type:_Err ->
%% io:format(user, "TODO: tell_author_yo is broken: ~p ~p\n", io:format(user, "TODO: tell_author_yo error is probably ignorable: ~p ~p\n",
%% [_Type, _Err]), [_Type, _Err]),
ok ok
end, end,
react_to_env_C210(Retries, S). react_to_env_C210(Retries, S).
@ -2009,6 +2003,15 @@ make_chmgr_regname(A) when is_atom(A) ->
make_chmgr_regname(B) when is_binary(B) -> make_chmgr_regname(B) when is_binary(B) ->
list_to_atom(binary_to_list(B) ++ "_chmgr"). list_to_atom(binary_to_list(B) ++ "_chmgr").
gobble_calls(_StaticCall) ->
receive
{'$gen_call',From,{test_react_to_env}} ->
gen_server:reply(From, todo_overload),
gobble_calls(_StaticCall)
after 1 -> % after 0 angers pulse.
ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
perhaps_start_repair( perhaps_start_repair(

View file

@ -261,7 +261,7 @@ append_server_loop(FluPid, #state{data_dir=DataDir, wedged=Wedged_p,
%% this new world. %% this new world.
Chmgr = machi_chain_manager1:make_chmgr_regname(FluName), Chmgr = machi_chain_manager1:make_chmgr_regname(FluName),
spawn(fun() -> spawn(fun() ->
catch machi_chain_manager:test_react_to_env(Chmgr) catch machi_chain_manager1:test_react_to_env(Chmgr)
end), end),
append_server_loop(FluPid, S#state{wedged=true}); append_server_loop(FluPid, S#state{wedged=true});
true -> true ->
@ -295,14 +295,18 @@ net_server_loop(Sock, S) ->
case machi_pb:decode_mpb_ll_request(Bin) of case machi_pb:decode_mpb_ll_request(Bin) of
LL_req when LL_req#mpb_ll_request.do_not_alter == 2 -> LL_req when LL_req#mpb_ll_request.do_not_alter == 2 ->
{R, NewS} = do_pb_ll_request(LL_req, S), {R, NewS} = do_pb_ll_request(LL_req, S),
{machi_pb:encode_mpb_ll_response(R), mode(low, NewS)}; {maybe_encode_response(R), mode(low, NewS)};
_ -> _ ->
HL_req = machi_pb:decode_mpb_request(Bin), HL_req = machi_pb:decode_mpb_request(Bin),
1 = HL_req#mpb_request.do_not_alter, 1 = HL_req#mpb_request.do_not_alter,
{R, NewS} = do_pb_hl_request(HL_req, make_high_clnt(S)), {R, NewS} = do_pb_hl_request(HL_req, make_high_clnt(S)),
{machi_pb:encode_mpb_response(R), mode(high, NewS)} {machi_pb:encode_mpb_response(R), mode(high, NewS)}
end, end,
ok = gen_tcp:send(Sock, RespBin), if RespBin == async_no_response ->
ok;
true ->
ok = gen_tcp:send(Sock, RespBin)
end,
net_server_loop(Sock, S2); net_server_loop(Sock, S2);
{error, SockError} -> {error, SockError} ->
Msg = io_lib:format("Socket error ~w", [SockError]), Msg = io_lib:format("Socket error ~w", [SockError]),
@ -317,6 +321,11 @@ net_server_loop(Sock, S) ->
exit(normal) exit(normal)
end. end.
maybe_encode_response(async_no_response=X) ->
X;
maybe_encode_response(R) ->
machi_pb:encode_mpb_ll_response(R).
mode(Mode, #state{pb_mode=undefined}=S) -> mode(Mode, #state{pb_mode=undefined}=S) ->
S#state{pb_mode=Mode}; S#state{pb_mode=Mode};
mode(_, S) -> mode(_, S) ->
@ -452,7 +461,16 @@ do_server_proj_request({get_all_projections, ProjType},
machi_projection_store:get_all_projections(ProjStore, ProjType); machi_projection_store:get_all_projections(ProjStore, ProjType);
do_server_proj_request({list_all_projections, ProjType}, do_server_proj_request({list_all_projections, ProjType},
#state{proj_store=ProjStore}) -> #state{proj_store=ProjStore}) ->
machi_projection_store:list_all_projections(ProjStore, ProjType). machi_projection_store:list_all_projections(ProjStore, ProjType);
do_server_proj_request({kick_projection_reaction},
#state{flu_name=FluName}) ->
%% Tell my chain manager that it might want to react to
%% this new world.
Chmgr = machi_chain_manager1:make_chmgr_regname(FluName),
spawn(fun() ->
catch machi_chain_manager1:test_react_to_env(Chmgr)
end),
async_no_response.
do_server_append_chunk(PKey, Prefix, Chunk, CSum_tag, CSum, do_server_append_chunk(PKey, Prefix, Chunk, CSum_tag, CSum,
ChunkExtra, S) -> ChunkExtra, S) ->

View file

@ -63,6 +63,7 @@
write_projection/3, write_projection/4, write_projection/3, write_projection/4,
get_all_projections/2, get_all_projections/3, get_all_projections/2, get_all_projections/3,
list_all_projections/2, list_all_projections/3, list_all_projections/2, list_all_projections/3,
kick_projection_reaction/2, kick_projection_reaction/3,
%% Common API %% Common API
echo/2, echo/3, echo/2, echo/3,
@ -374,6 +375,27 @@ list_all_projections(Host, TcpPort, ProjType)
disconnect(Sock) disconnect(Sock)
end. end.
%% @doc Kick (politely) the remote chain manager to react to a
%% projection change.
-spec kick_projection_reaction(port_wrap(), list()) ->
ok.
kick_projection_reaction(Sock, Options) ->
kick_projection_reaction2(Sock, Options).
%% @doc Kick (politely) the remote chain manager to react to a
%% projection change.
-spec kick_projection_reaction(machi_dt:inet_host(), machi_dt:inet_port(), list()) ->
ok.
kick_projection_reaction(Host, TcpPort, Options) ->
Sock = connect(#p_srvr{proto_mod=?MODULE, address=Host, port=TcpPort}),
try
kick_projection_reaction2(Sock, Options)
after
disconnect(Sock)
end.
%% @doc Echo -- test protocol round-trip. %% @doc Echo -- test protocol round-trip.
-spec echo(port_wrap(), string()) -> -spec echo(port_wrap(), string()) ->
@ -604,11 +626,21 @@ list_all_projections2(Sock, ProjType) ->
ReqID, {low_proj, {list_all_projections, ProjType}}), ReqID, {low_proj, {list_all_projections, ProjType}}),
do_pb_request_common(Sock, ReqID, Req). do_pb_request_common(Sock, ReqID, Req).
kick_projection_reaction2(Sock, _Options) ->
ReqID = <<42>>,
Req = machi_pb_translate:to_pb_request(
ReqID, {low_proj, {kick_projection_reaction}}),
do_pb_request_common(Sock, ReqID, Req, false).
do_pb_request_common(Sock, ReqID, Req) -> do_pb_request_common(Sock, ReqID, Req) ->
do_pb_request_common(Sock, ReqID, Req, true).
do_pb_request_common(Sock, ReqID, Req, GetReply_p) ->
erase(bad_sock), erase(bad_sock),
try try
ReqBin = list_to_binary(machi_pb:encode_mpb_ll_request(Req)), ReqBin = list_to_binary(machi_pb:encode_mpb_ll_request(Req)),
ok = w_send(Sock, ReqBin), ok = w_send(Sock, ReqBin),
if GetReply_p ->
case w_recv(Sock, 0) of case w_recv(Sock, 0) of
{ok, RespBin} -> {ok, RespBin} ->
Resp = machi_pb:decode_mpb_ll_response(RespBin), Resp = machi_pb:decode_mpb_ll_response(RespBin),
@ -617,6 +649,9 @@ do_pb_request_common(Sock, ReqID, Req) ->
Reply; Reply;
{error, _}=Err -> {error, _}=Err ->
throw(Err) throw(Err)
end;
not GetReply_p ->
ok
end end
catch catch
throw:Error -> throw:Error ->

View file

@ -135,6 +135,10 @@ from_pb_request(#mpb_ll_request{
req_id=ReqID, req_id=ReqID,
proj_la=#mpb_ll_listallprojectionsreq{type=ProjType}}) -> proj_la=#mpb_ll_listallprojectionsreq{type=ProjType}}) ->
{ReqID, {low_proj, {list_all_projections, conv_to_type(ProjType)}}}; {ReqID, {low_proj, {list_all_projections, conv_to_type(ProjType)}}};
from_pb_request(#mpb_ll_request{
req_id=ReqID,
proj_kp=#mpb_ll_kickprojectionreactionreq{}}) ->
{ReqID, {low_proj, {kick_projection_reaction}}};
%%qqq %%qqq
from_pb_request(#mpb_request{req_id=ReqID, from_pb_request(#mpb_request{req_id=ReqID,
echo=#mpb_echoreq{message=Msg}}) -> echo=#mpb_echoreq{message=Msg}}) ->
@ -313,6 +317,7 @@ from_pb_response(#mpb_ll_response{
_ -> _ ->
{ReqID< machi_pb_high_client:convert_general_status_code(Status)} {ReqID< machi_pb_high_client:convert_general_status_code(Status)}
end. end.
%% No response for proj_kp/kick_projection_reaction
%% TODO: move the #mbp_* record making code from %% TODO: move the #mbp_* record making code from
%% machi_pb_high_client:do_send_sync() clauses into to_pb_request(). %% machi_pb_high_client:do_send_sync() clauses into to_pb_request().
@ -403,9 +408,14 @@ to_pb_request(ReqID, {low_proj, {get_all_projections, ProjType}}) ->
proj_ga=#mpb_ll_getallprojectionsreq{type=conv_from_type(ProjType)}}; proj_ga=#mpb_ll_getallprojectionsreq{type=conv_from_type(ProjType)}};
to_pb_request(ReqID, {low_proj, {list_all_projections, ProjType}}) -> to_pb_request(ReqID, {low_proj, {list_all_projections, ProjType}}) ->
#mpb_ll_request{req_id=ReqID, do_not_alter=2, #mpb_ll_request{req_id=ReqID, do_not_alter=2,
proj_la=#mpb_ll_listallprojectionsreq{type=conv_from_type(ProjType)}}. proj_la=#mpb_ll_listallprojectionsreq{type=conv_from_type(ProjType)}};
to_pb_request(ReqID, {low_proj, {kick_projection_reaction}}) ->
#mpb_ll_request{req_id=ReqID, do_not_alter=2,
proj_kp=#mpb_ll_kickprojectionreactionreq{}}.
%%qqq %%qqq
to_pb_response(_ReqID, _, async_no_response=X) ->
X;
to_pb_response(ReqID, _, {low_error, ErrCode, ErrMsg}) -> to_pb_response(ReqID, _, {low_error, ErrCode, ErrMsg}) ->
make_ll_error_resp(ReqID, ErrCode, ErrMsg); make_ll_error_resp(ReqID, ErrCode, ErrMsg);
to_pb_response(ReqID, {low_echo, _BogusEpochID, _Msg}, Resp) -> to_pb_response(ReqID, {low_echo, _BogusEpochID, _Msg}, Resp) ->
@ -567,6 +577,7 @@ to_pb_response(ReqID, {low_proj, {list_all_projections, _ProjType}}, Resp)->
proj_la=#mpb_ll_listallprojectionsresp{ proj_la=#mpb_ll_listallprojectionsresp{
status=Status}} status=Status}}
end; end;
%% No response for {kick_projection_reaction}!
%%qqq %%qqq
to_pb_response(ReqID, _, {high_error, ErrCode, ErrMsg}) -> to_pb_response(ReqID, _, {high_error, ErrCode, ErrMsg}) ->
make_error_resp(ReqID, ErrCode, ErrMsg); make_error_resp(ReqID, ErrCode, ErrMsg);

View file

@ -68,6 +68,7 @@
write_projection/3, write_projection/4, write_projection/3, write_projection/4,
get_all_projections/2, get_all_projections/3, get_all_projections/2, get_all_projections/3,
list_all_projections/2, list_all_projections/3, list_all_projections/2, list_all_projections/3,
kick_projection_reaction/2, kick_projection_reaction/3,
%% Common API %% Common API
quit/1, quit/1,
@ -243,6 +244,19 @@ list_all_projections(PidSpec, ProjType, Timeout) ->
gen_server:call(PidSpec, {req, {list_all_projections, ProjType}}, gen_server:call(PidSpec, {req, {list_all_projections, ProjType}},
Timeout). Timeout).
%% @doc Kick (politely) the remote chain manager to react to a
%% projection change.
kick_projection_reaction(PidSpec, Options) ->
kick_projection_reaction(PidSpec, Options, infinity).
%% @doc Kick (politely) the remote chain manager to react to a
%% projection change.
kick_projection_reaction(PidSpec, Options, Timeout) ->
gen_server:call(PidSpec, {req, {kick_projection_reaction, Options}},
Timeout).
%% @doc Quit &amp; close the connection to remote FLU and stop our %% @doc Quit &amp; close the connection to remote FLU and stop our
%% proxy process. %% proxy process.
@ -373,7 +387,10 @@ make_req_fun({get_all_projections, ProjType},
fun() -> Mod:get_all_projections(Sock, ProjType) end; fun() -> Mod:get_all_projections(Sock, ProjType) end;
make_req_fun({list_all_projections, ProjType}, make_req_fun({list_all_projections, ProjType},
#state{sock=Sock,i=#p_srvr{proto_mod=Mod}}) -> #state{sock=Sock,i=#p_srvr{proto_mod=Mod}}) ->
fun() -> Mod:list_all_projections(Sock, ProjType) end. fun() -> Mod:list_all_projections(Sock, ProjType) end;
make_req_fun({kick_projection_reaction, Options},
#state{sock=Sock,i=#p_srvr{proto_mod=Mod}}) ->
fun() -> Mod:kick_projection_reaction(Sock, Options) end.
connected_p(#state{sock=SockMaybe, connected_p(#state{sock=SockMaybe,
i=#p_srvr{proto_mod=Mod}=_I}=_S) -> i=#p_srvr{proto_mod=Mod}=_I}=_S) ->

View file

@ -82,7 +82,7 @@ command(S) ->
{ 1, {call, ?MODULE, change_partitions, { 1, {call, ?MODULE, change_partitions,
[gen_old_threshold(), gen_no_partition_threshold()]}}, [gen_old_threshold(), gen_no_partition_threshold()]}},
{50, {call, ?MODULE, do_ticks, {50, {call, ?MODULE, do_ticks,
[choose(5, 200), S#state.pids, [choose(5, 100), S#state.pids,
gen_old_threshold(), gen_no_partition_threshold()]}} gen_old_threshold(), gen_no_partition_threshold()]}}
]). ]).
@ -280,7 +280,7 @@ dump_state() ->
prop_pulse() -> prop_pulse() ->
?FORALL({Cmds0, Seed}, {non_empty(commands(?MODULE)), pulse:seed()}, ?FORALL({Cmds0, Seed}, {non_empty(commands(?MODULE)), pulse:seed()},
?IMPLIES(1 < length(Cmds0) andalso length(Cmds0) < 5, ?IMPLIES(1 < length(Cmds0) andalso length(Cmds0) < 6,
begin begin
ok = shutdown_hard(), ok = shutdown_hard(),
%% PULSE can be really unfair, of course, including having exec_ticks %% PULSE can be really unfair, of course, including having exec_ticks
@ -421,10 +421,15 @@ private_projections_are_stable_check(ProxiesDict, All_listE) ->
%% also check for flapping, and if yes, to see if all_hosed are %% also check for flapping, and if yes, to see if all_hosed are
%% all exactly equal. %% all exactly equal.
_ = exec_ticks(40, All_listE), %% gobble_calls() workaround: many small exec_ticks() calls +
%% sleep after each.
[begin
_ = exec_ticks(10, All_listE),
timer:sleep(10)
end|| _ <- lists:seq(1, 40)],
Private1 = [?FLU_PC:get_latest_epochid(Proxy, private) || Private1 = [?FLU_PC:get_latest_epochid(Proxy, private) ||
{_FLU, Proxy} <- orddict:to_list(ProxiesDict)], {_FLU, Proxy} <- orddict:to_list(ProxiesDict)],
_ = exec_ticks(5, All_listE), _ = exec_ticks(3*20, All_listE),
Private2 = [?FLU_PC:get_latest_epochid(Proxy, private) || Private2 = [?FLU_PC:get_latest_epochid(Proxy, private) ||
{_FLU, Proxy} <- orddict:to_list(ProxiesDict)], {_FLU, Proxy} <- orddict:to_list(ProxiesDict)],

View file

@ -238,16 +238,17 @@ nonunanimous_setup_and_fix_test() ->
{ok, P2pa} = ?FLU_PC:read_latest_projection(Proxy_a, private), {ok, P2pa} = ?FLU_PC:read_latest_projection(Proxy_a, private),
P2 = P2pa#projection_v1{dbg2=[]}, P2 = P2pa#projection_v1{dbg2=[]},
%% FLUb should have nothing written to private because it hasn't %% %% FLUb should have nothing written to private because it hasn't
%% reacted yet. %% %% reacted yet.
{error, not_written} = ?FLU_PC:read_latest_projection(Proxy_b, private), %% {error, not_written} = ?FLU_PC:read_latest_projection(Proxy_b, private),
%% Poke FLUb to react ... should be using the same private proj %% %% Poke FLUb to react ... should be using the same private proj
%% as FLUa. %% %% as FLUa.
{now_using, _, EpochNum_a} = ?MGR:test_react_to_env(Mb), %% {now_using, _, EpochNum_a} = ?MGR:test_react_to_env(Mb),
{ok, P2pb} = ?FLU_PC:read_latest_projection(Proxy_b, private), {ok, P2pb} = ?FLU_PC:read_latest_projection(Proxy_b, private),
P2 = P2pb#projection_v1{dbg2=[]}, P2 = P2pb#projection_v1{dbg2=[]},
timer:sleep(3000),
ok ok
after after
ok = ?MGR:stop(Ma), ok = ?MGR:stop(Ma),

View file

@ -75,6 +75,11 @@ api_smoke_test() ->
<<"foo-file">>, 99832, <<"foo-file">>, 99832,
MyChunk_badcs), MyChunk_badcs),
%% Put kick_projection_reaction() in the middle of the test so
%% that any problems with its async nature will (hopefully)
%% cause problems later in the test.
ok = ?MUT:kick_projection_reaction(Prox1, []),
%% Alright, now for the rest of the API, whee %% Alright, now for the rest of the API, whee
BadFile = <<"no-such-file">>, BadFile = <<"no-such-file">>,
{error, no_such_file} = ?MUT:checksum_list(Prox1, FakeEpoch, BadFile), {error, no_such_file} = ?MUT:checksum_list(Prox1, FakeEpoch, BadFile),