summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2017-10-09 17:04:21 -0400
committerJoan Touzet <wohali@users.noreply.github.com>2017-10-19 19:13:25 -0400
commit74a29677d5c1af0e381d3fc4a6ad91ff6f0bc6f7 (patch)
tree99ddfd7f506223dbd8e22c8c0694c58e523fe7e8
parentcd6bda2386b37fee0e114ee15d119d98e77ba6be (diff)
downloadcouchdb-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.script2
-rw-r--r--src/couch/src/couch_auth_cache.erl5
-rw-r--r--src/couch/src/couch_lru.erl4
-rw-r--r--src/couch/src/couch_util.erl15
-rw-r--r--src/couch/src/couch_uuids.erl105
-rw-r--r--src/couch/src/test_util.erl2
-rw-r--r--src/couch_epi/test/couch_epi_tests.erl2
-rw-r--r--src/couch_replicator/src/couch_replicator_worker.erl10
-rw-r--r--src/fabric/src/fabric_db_create.erl2
-rw-r--r--src/mango/src/mango_execution_stats.erl6
-rw-r--r--src/mem3/src/mem3_shards.erl4
-rw-r--r--src/rexi/src/rexi_server.erl2
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,