You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2017/10/11 01:38:17 UTC

[couchdb] branch master updated: Fix erlang time module compatibility

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/master by this push:
     new a99cc6f  Fix erlang time module compatibility
a99cc6f is described below

commit a99cc6fda04e35e2266953a73a182c724ed928de
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Mon Oct 9 17:04:21 2017 -0400

    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.
---
 src/couch/rebar.config.script                      |   2 +
 src/couch/src/couch_auth_cache.erl                 |   5 +-
 src/couch/src/couch_lru.erl                        |   4 +-
 src/couch/src/couch_util.erl                       |  15 +++
 src/couch/src/couch_uuids.erl                      | 105 +++++++++++++++++----
 src/couch/src/test_util.erl                        |   2 +-
 src/couch_epi/test/couch_epi_tests.erl             |   2 +-
 .../src/couch_replicator_worker.erl                |  10 +-
 src/fabric/src/fabric_db_create.erl                |   2 +-
 src/mango/src/mango_execution_stats.erl            |   6 +-
 src/mem3/src/mem3_shards.erl                       |   4 +-
 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 bd35e34..498ce3a 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 16c59d1..45b34e1 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 023515e..6ad7c65 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 631f00b..54a92fc 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 ebe145c..5c7359b 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 8a05e88..e0a53a6 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 99a06f3..79122d7 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 344b8f2..45ccefa 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 a7f4ed9..d793f4f 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 95b9038..afdb417 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 be7e5aa..5e215e2 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 614c3fc..3d3f272 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,

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].