diff options
author | Nick Vatamaniuc <vatamane@apache.org> | 2017-10-09 17:04:21 -0400 |
---|---|---|
committer | Joan Touzet <wohali@users.noreply.github.com> | 2017-10-19 19:13:25 -0400 |
commit | 74a29677d5c1af0e381d3fc4a6ad91ff6f0bc6f7 (patch) | |
tree | 99ddfd7f506223dbd8e22c8c0694c58e523fe7e8 | |
parent | cd6bda2386b37fee0e114ee15d119d98e77ba6be (diff) | |
download | couchdb-74a29677d5c1af0e381d3fc4a6ad91ff6f0bc6f7.tar.gz |
Fix erlang time module compatibility
`now/0` is deprecated since Erlang 18.0, and a set of new time related
functions are available.
Usually `now/0` can be replaced with `os:timestamp/0`, however in some
instances it was used effectively to produce monotonically incrementing values
rather than timestamps. So added a new `couch_util:unique_monotonic_integer/0`.
Most functional changes are in couch_uuid module. There `now/0` was used both
as a timestamp and for uniqueness. To emulate previous behavior, a local
incrementing clock sequence is used. If `os:timestamp/0` does not advance since
last call then the local clock is advanced by 1 microsecond and that's used to
generate the next V1 UUIDs. As soon as os:timestamp/0` catches up, the local
sequence reset to that latest value.
Also exported function `utc_random/0` was not used, after updating the function
it wasn't exported anymore.
-rw-r--r-- | src/couch/rebar.config.script | 2 | ||||
-rw-r--r-- | src/couch/src/couch_auth_cache.erl | 5 | ||||
-rw-r--r-- | src/couch/src/couch_lru.erl | 4 | ||||
-rw-r--r-- | src/couch/src/couch_util.erl | 15 | ||||
-rw-r--r-- | src/couch/src/couch_uuids.erl | 105 | ||||
-rw-r--r-- | src/couch/src/test_util.erl | 2 | ||||
-rw-r--r-- | src/couch_epi/test/couch_epi_tests.erl | 2 | ||||
-rw-r--r-- | src/couch_replicator/src/couch_replicator_worker.erl | 10 | ||||
-rw-r--r-- | src/fabric/src/fabric_db_create.erl | 2 | ||||
-rw-r--r-- | src/mango/src/mango_execution_stats.erl | 6 | ||||
-rw-r--r-- | src/mem3/src/mem3_shards.erl | 4 | ||||
-rw-r--r-- | src/rexi/src/rexi_server.erl | 2 |
12 files changed, 123 insertions, 36 deletions
diff --git a/src/couch/rebar.config.script b/src/couch/rebar.config.script index bd35e34bd..498ce3a82 100644 --- a/src/couch/rebar.config.script +++ b/src/couch/rebar.config.script @@ -132,6 +132,8 @@ PortSpecs = case os:type() of BaseSpecs end, PlatformDefines = [ + {platform_define, "^R16", 'PRE18TIMEFEATURES'}, + {platform_define, "^17", 'PRE18TIMEFEATURES'}, {platform_define, "^R16", 'NORANDMODULE'}, {platform_define, "^17", 'NORANDMODULE'}, {platform_define, "win32", 'WINDOWS'} diff --git a/src/couch/src/couch_auth_cache.erl b/src/couch/src/couch_auth_cache.erl index 16c59d19a..45b34e1bd 100644 --- a/src/couch/src/couch_auth_cache.erl +++ b/src/couch/src/couch_auth_cache.erl @@ -203,7 +203,8 @@ handle_call({fetch, UserName}, _From, State) -> [] -> couch_stats:increment_counter([couchdb, auth_cache_misses]), Creds = get_user_props_from_db(UserName), - State1 = add_cache_entry(UserName, Creds, erlang:now(), State), + ATime = couch_util:unique_monotonic_integer(), + State1 = add_cache_entry(UserName, Creds, ATime, State), {Creds, State1} end, {reply, Credentials, NewState}; @@ -311,7 +312,7 @@ free_mru_cache_entry() -> cache_hit(UserName, Credentials, ATime) -> - NewATime = erlang:now(), + NewATime = couch_util:unique_monotonic_integer(), true = ets:delete(?BY_ATIME, ATime), true = ets:insert(?BY_ATIME, {NewATime, UserName}), true = ets:insert(?BY_USER, {UserName, {Credentials, NewATime}}). diff --git a/src/couch/src/couch_lru.erl b/src/couch/src/couch_lru.erl index 023515e7c..6ad7c65cd 100644 --- a/src/couch/src/couch_lru.erl +++ b/src/couch/src/couch_lru.erl @@ -19,13 +19,13 @@ new() -> {gb_trees:empty(), dict:new()}. insert(DbName, {Tree0, Dict0}) -> - Lru = erlang:now(), + Lru = couch_util:unique_monotonic_integer(), {gb_trees:insert(Lru, DbName, Tree0), dict:store(DbName, Lru, Dict0)}. update(DbName, {Tree0, Dict0}) -> case dict:find(DbName, Dict0) of {ok, Old} -> - New = erlang:now(), + New = couch_util:unique_monotonic_integer(), Tree = gb_trees:insert(New, DbName, gb_trees:delete(Old, Tree0)), Dict = dict:store(DbName, New, Dict0), {Tree, Dict}; diff --git a/src/couch/src/couch_util.erl b/src/couch/src/couch_util.erl index 631f00b61..54a92fcc1 100644 --- a/src/couch/src/couch_util.erl +++ b/src/couch/src/couch_util.erl @@ -34,6 +34,7 @@ -export([callback_exists/3, validate_callback_exists/3]). -export([with_proc/4]). -export([process_dict_get/2, process_dict_get/3]). +-export([unique_monotonic_integer/0]). -include_lib("couch/include/couch_db.hrl"). @@ -625,3 +626,17 @@ process_dict_get(Pid, Key, DefaultValue) -> undefined -> DefaultValue end. + + +-ifdef(PRE18TIMEFEATURES). + +unique_monotonic_integer() -> + {Ms, S, Us} = erlang:now(), + (Ms * 1000000 + S) * 1000000 + Us. + +-else. + +unique_monotonic_integer() -> + erlang:unique_integer([monotonic, positive]). + +-endif. diff --git a/src/couch/src/couch_uuids.erl b/src/couch/src/couch_uuids.erl index ebe145c17..5c7359b33 100644 --- a/src/couch/src/couch_uuids.erl +++ b/src/couch/src/couch_uuids.erl @@ -17,7 +17,7 @@ -behaviour(config_listener). -export([start/0, stop/0]). --export([new/0, random/0, utc_random/0]). +-export([new/0, random/0]). -export([init/1, terminate/2, code_change/3]). -export([handle_call/3, handle_cast/2, handle_info/2]). @@ -39,17 +39,6 @@ new() -> random() -> list_to_binary(couch_util:to_hex(crypto:strong_rand_bytes(16))). -utc_random() -> - utc_suffix(couch_util:to_hex(crypto:strong_rand_bytes(9))). - -utc_suffix(Suffix) -> - Now = {_, _, Micro} = erlang:now(), % uniqueness is used. - Nowish = calendar:now_to_universal_time(Now), - Nowsecs = calendar:datetime_to_gregorian_seconds(Nowish), - Then = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), - Prefix = io_lib:format("~14.16.0b", [(Nowsecs - Then) * 1000000 + Micro]), - list_to_binary(Prefix ++ Suffix). - init([]) -> ok = config:listen_for_changes(?MODULE, nil), {ok, state()}. @@ -59,10 +48,13 @@ terminate(_Reason, _State) -> handle_call(create, _From, random) -> {reply, random(), random}; -handle_call(create, _From, utc_random) -> - {reply, utc_random(), utc_random}; -handle_call(create, _From, {utc_id, UtcIdSuffix}) -> - {reply, utc_suffix(UtcIdSuffix), {utc_id, UtcIdSuffix}}; +handle_call(create, _From, {utc_random, ClockSeq}) -> + {UtcRandom, NewClockSeq} = utc_random(ClockSeq), + {reply, UtcRandom, {utc_random, NewClockSeq}}; +handle_call(create, _From, {utc_id, UtcIdSuffix, ClockSeq}) -> + Now = os:timestamp(), + {UtcId, NewClockSeq} = utc_suffix(UtcIdSuffix, ClockSeq, Now), + {reply, UtcId, {utc_id, UtcIdSuffix, NewClockSeq}}; handle_call(create, _From, {sequential, Pref, Seq}) -> Result = ?l2b(Pref ++ io_lib:format("~6.16.0b", [Seq])), case Seq >= 16#fff000 of @@ -111,12 +103,89 @@ state() -> random -> random; utc_random -> - utc_random; + ClockSeq = micros_since_epoch(os:timestamp()), + {utc_random, ClockSeq}; utc_id -> + ClockSeq = micros_since_epoch(os:timestamp()), UtcIdSuffix = config:get("uuids", "utc_id_suffix", ""), - {utc_id, UtcIdSuffix}; + {utc_id, UtcIdSuffix, ClockSeq}; sequential -> {sequential, new_prefix(), inc()}; Unknown -> throw({unknown_uuid_algorithm, Unknown}) end. + +micros_since_epoch({_, _, Micro} = Now) -> + Nowish = calendar:now_to_universal_time(Now), + Nowsecs = calendar:datetime_to_gregorian_seconds(Nowish), + Then = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), + (Nowsecs - Then) * 1000000 + Micro. + +utc_random(ClockSeq) -> + Suffix = couch_util:to_hex(crypto:strong_rand_bytes(9)), + utc_suffix(Suffix, ClockSeq, os:timestamp()). + +utc_suffix(Suffix, ClockSeq, Now) -> + OsMicros = micros_since_epoch(Now), + NewClockSeq = if + OsMicros =< ClockSeq -> + % Timestamp is lagging, use ClockSeq as Timestamp + ClockSeq + 1; + OsMicros > ClockSeq -> + % Timestamp advanced, use it, and reset ClockSeq with it + OsMicros + end, + Prefix = io_lib:format("~14.16.0b", [NewClockSeq]), + {list_to_binary(Prefix ++ Suffix), NewClockSeq}. + + +-ifdef(TEST). + +-include_lib("eunit/include/eunit.hrl"). + + +utc_id_time_does_not_advance_test() -> + % Timestamp didn't advance but local clock sequence should and new UUIds + % should be generated + Now = {0, 1, 2}, + ClockSeq0 = micros_since_epoch({3, 4, 5}), + {UtcId0, ClockSeq1} = utc_suffix("", ClockSeq0, Now), + ?assert(is_binary(UtcId0)), + ?assertEqual(ClockSeq0 + 1, ClockSeq1), + {UtcId1, ClockSeq2} = utc_suffix("", ClockSeq1, Now), + ?assertNotEqual(UtcId0, UtcId1), + ?assertEqual(ClockSeq1 + 1, ClockSeq2). + + +utc_id_time_advanced_test() -> + % Timestamp advanced, a new UUID generated and also the last clock sequence + % is updated to that timestamp. + Now0 = {0, 1, 2}, + ClockSeq0 = micros_since_epoch({3, 4, 5}), + {UtcId0, ClockSeq1} = utc_suffix("", ClockSeq0, Now0), + ?assert(is_binary(UtcId0)), + ?assertEqual(ClockSeq0 + 1, ClockSeq1), + Now1 = {9, 9, 9}, + {UtcId1, ClockSeq2} = utc_suffix("", ClockSeq1, Now1), + ?assert(is_binary(UtcId1)), + ?assertNotEqual(UtcId0, UtcId1), + ?assertEqual(micros_since_epoch(Now1), ClockSeq2). + +utc_random_test_time_does_not_advance_test() -> + {MSec, Sec, USec} = os:timestamp(), + Future = {MSec + 10, Sec, USec}, + ClockSeqFuture = micros_since_epoch(Future), + {UtcRandom, NextClockSeq} = utc_random(ClockSeqFuture), + ?assert(is_binary(UtcRandom)), + ?assertEqual(32, byte_size(UtcRandom)), + ?assertEqual(ClockSeqFuture + 1, NextClockSeq). + +utc_random_test_time_advance_test() -> + ClockSeqPast = micros_since_epoch({1, 1, 1}), + {UtcRandom, NextClockSeq} = utc_random(ClockSeqPast), + ?assert(is_binary(UtcRandom)), + ?assertEqual(32, byte_size(UtcRandom)), + ?assert(NextClockSeq > micros_since_epoch({1000, 0, 0})). + + +-endif. diff --git a/src/couch/src/test_util.erl b/src/couch/src/test_util.erl index 8a05e8830..e0a53a6f7 100644 --- a/src/couch/src/test_util.erl +++ b/src/couch/src/test_util.erl @@ -245,7 +245,7 @@ fake_db(Fields) -> end, #db{}, Fields). now_us() -> - {MegaSecs, Secs, MicroSecs} = now(), + {MegaSecs, Secs, MicroSecs} = os:timestamp(), (MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs. mock(Modules) when is_list(Modules) -> diff --git a/src/couch_epi/test/couch_epi_tests.erl b/src/couch_epi/test/couch_epi_tests.erl index 99a06f31a..79122d75a 100644 --- a/src/couch_epi/test/couch_epi_tests.erl +++ b/src/couch_epi/test/couch_epi_tests.erl @@ -26,7 +26,7 @@ -define(temp_atom, fun() -> - {A, B, C} = erlang:now(), + {A, B, C} = os:timestamp(), list_to_atom(lists:flatten(io_lib:format("~p~p~p", [A, B, C]))) end). diff --git a/src/couch_replicator/src/couch_replicator_worker.erl b/src/couch_replicator/src/couch_replicator_worker.erl index 344b8f286..45ccefa10 100644 --- a/src/couch_replicator/src/couch_replicator_worker.erl +++ b/src/couch_replicator/src/couch_replicator_worker.erl @@ -73,7 +73,7 @@ start_link(Cp, #httpdb{} = Source, Target, ChangesManager, MaxConns) -> start_link(Cp, Source, Target, ChangesManager, _MaxConns) -> Pid = spawn_link(fun() -> - erlang:put(last_stats_report, now()), + erlang:put(last_stats_report, os:timestamp()), queue_fetch_loop(Source, Target, Cp, Cp, ChangesManager) end), {ok, Pid}. @@ -85,7 +85,7 @@ init({Cp, Source, Target, ChangesManager, MaxConns}) -> LoopPid = spawn_link(fun() -> queue_fetch_loop(Source, Target, Parent, Cp, ChangesManager) end), - erlang:put(last_stats_report, now()), + erlang:put(last_stats_report, os:timestamp()), State = #state{ cp = Cp, max_parallel_conns = MaxConns, @@ -247,7 +247,7 @@ queue_fetch_loop(Source, Target, Parent, Cp, ChangesManager) -> end, close_db(Target2), ok = gen_server:call(Cp, {report_seq_done, ReportSeq, Stats}, infinity), - erlang:put(last_stats_report, now()), + erlang:put(last_stats_report, os:timestamp()), couch_log:debug("Worker reported completion of seq ~p", [ReportSeq]), queue_fetch_loop(Source, Target, Parent, Cp, ChangesManager) end. @@ -392,7 +392,7 @@ spawn_writer(Target, #batch{docs = DocList, size = Size}) -> after_full_flush(#state{stats = Stats, flush_waiter = Waiter} = State) -> gen_server:reply(Waiter, {ok, Stats}), - erlang:put(last_stats_report, now()), + erlang:put(last_stats_report, os:timestamp()), State#state{ stats = couch_replicator_stats:new(), flush_waiter = nil, @@ -543,7 +543,7 @@ find_missing(DocInfos, Target) -> maybe_report_stats(Cp, Stats) -> - Now = now(), + Now = os:timestamp(), case timer:now_diff(erlang:get(last_stats_report), Now) >= ?STATS_DELAY of true -> ok = gen_server:call(Cp, {add_stats, Stats}, infinity), diff --git a/src/fabric/src/fabric_db_create.erl b/src/fabric/src/fabric_db_create.erl index a7f4ed9d6..d793f4f13 100644 --- a/src/fabric/src/fabric_db_create.erl +++ b/src/fabric/src/fabric_db_create.erl @@ -56,7 +56,7 @@ validate_dbname(DbName, Options) -> end. generate_shard_map(DbName, Options) -> - {MegaSecs, Secs, _} = now(), + {MegaSecs, Secs, _} = os:timestamp(), Suffix = "." ++ integer_to_list(MegaSecs*1000000 + Secs), Shards = mem3:choose_shards(DbName, [{shard_suffix,Suffix} | Options]), case mem3_util:open_db_doc(DbName) of diff --git a/src/mango/src/mango_execution_stats.erl b/src/mango/src/mango_execution_stats.erl index 95b9038a8..afdb417b7 100644 --- a/src/mango/src/mango_execution_stats.erl +++ b/src/mango/src/mango_execution_stats.erl @@ -64,12 +64,12 @@ incr_results_returned(Stats) -> log_start(Stats) -> Stats#execution_stats { - executionStartTime = now() + executionStartTime = os:timestamp() }. log_end(Stats) -> - End = now(), + End = os:timestamp(), Diff = timer:now_diff(End, Stats#execution_stats.executionStartTime) / 1000, Stats#execution_stats { executionTimeMs = Diff @@ -86,4 +86,4 @@ maybe_add_stats(Opts, UserFun, Stats, UserAcc) -> FinalUserAcc; _ -> UserAcc - end.
\ No newline at end of file + end. diff --git a/src/mem3/src/mem3_shards.erl b/src/mem3/src/mem3_shards.erl index be7e5aaaf..5e215e201 100644 --- a/src/mem3/src/mem3_shards.erl +++ b/src/mem3/src/mem3_shards.erl @@ -429,7 +429,7 @@ create_if_missing(Name) -> end. cache_insert(#st{cur_size=Cur}=St, DbName, Writer, Timeout) -> - NewATime = now(), + NewATime = couch_util:unique_monotonic_integer(), true = ets:delete(?SHARDS, DbName), flush_write(DbName, Writer, Timeout), case ets:lookup(?DBS, DbName) of @@ -458,7 +458,7 @@ cache_remove(#st{cur_size=Cur}=St, DbName) -> cache_hit(DbName) -> case ets:lookup(?DBS, DbName) of [{DbName, ATime}] -> - NewATime = now(), + NewATime = couch_util:unique_monotonic_integer(), true = ets:delete(?ATIMES, ATime), true = ets:insert(?ATIMES, {NewATime, DbName}), true = ets:insert(?DBS, {DbName, NewATime}); diff --git a/src/rexi/src/rexi_server.erl b/src/rexi/src/rexi_server.erl index 614c3fc0c..3d3f272e4 100644 --- a/src/rexi/src/rexi_server.erl +++ b/src/rexi/src/rexi_server.erl @@ -144,7 +144,7 @@ init_p(From, {M,F,A}, Nonce) -> node(ClientPid), ClientPid, M, F, length(A), Class, Reason, Stack]), exit(#error{ - timestamp = now(), + timestamp = os:timestamp(), reason = {Class, Reason}, mfa = {M,F,A}, nonce = Nonce, |