Use the same file write/sync/rename path where needed

When we want to be sure a file has been written before proceeding - we need a safer (that `file:write_file/2`) mechanism to be sure that it is written before proceeding.

This will:
open, write, sync, rename and then optionally read-back.

Changed so that manifest writing uses the safest form (including read back), and that sst writing uses a slightly looser form (with no read back to avoid performance issues).
This commit is contained in:
Martin Sumner 2019-11-19 15:50:59 +00:00
parent e16b650823
commit 693defb6d3
4 changed files with 58 additions and 16 deletions

View file

@ -14,7 +14,10 @@
-export([generate_uuid/0,
integer_now/0,
integer_time/1,
magic_hash/1]).
magic_hash/1,
safe_rename/4]).
-define(WRITE_OPS, [binary, raw, read, write]).
-spec generate_uuid() -> list().
@ -65,12 +68,33 @@ hash1(H, <<B:8/integer, Rest/bytes>>) ->
hash1(H2, Rest).
-spec safe_rename(string(), string(), binary(), boolean()) -> ok.
%% @doc
%% Write a file, sync it and rename it (and for super-safe mode read it back)
%% An attempt to prevent crashes leaving files with empty or partially written
%% values
safe_rename(TempFN, RealFN, BinData, ReadCheck) ->
{ok, TempFH} = file:open(TempFN, ?WRITE_OPS),
ok = file:write(TempFH, BinData),
ok = file:sync(TempFH),
ok = file:close(TempFH),
ok = file:rename(TempFN, RealFN),
case ReadCheck of
true ->
{ok, ReadBack} = file:read_file(RealFN),
true = (ReadBack == BinData),
ok;
false ->
ok
end.
%%%============================================================================
%%% Test
%%%============================================================================
-ifdef(TEST).
-define(TEST_AREA, "test/test_area/util/").
magichashperf_test() ->
KeyFun =
@ -86,4 +110,17 @@ magichashperf_test() ->
{TimeMH2, _HL1} = timer:tc(lists, map, [fun(K) -> magic_hash(K) end, KL]),
io:format(user, "1000 keys magic hashed in ~w microseconds~n", [TimeMH2]).
safe_rename_test() ->
ok = filelib:ensure_dir(?TEST_AREA),
TempFN = filename:join(?TEST_AREA, "test_manifest0.pnd"),
RealFN = filename:join(?TEST_AREA, "test_manifest0.man"),
ok = safe_rename(TempFN, RealFN, <<1:128/integer>>, false),
?assertMatch({ok, <<1:128/integer>>}, file:read_file(RealFN)),
TempFN1 = filename:join(?TEST_AREA, "test_manifest1.pnd"),
RealFN1 = filename:join(?TEST_AREA, "test_manifest1.man"),
ok = safe_rename(TempFN1, RealFN1, <<2:128/integer>>, true),
?assertMatch({ok, <<2:128/integer>>}, file:read_file(RealFN1)).
-endif.