Current section
Files
Jump to
Current section
Files
src/couchbeam_uuids.erl
%%% -*- erlang -*-
%%%
%%% This file is part of couchbeam released under the MIT license.
%%% See the NOTICE for more information.
-module(couchbeam_uuids).
-behaviour(gen_server).
-export([random/0,
utc_random/0]).
-export([start_link/0]).
-export([get_uuids/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("couchbeam.hrl").
-define(START_RETRY, 200).
-define(MAX_RETRY, 10000).
-record(state, {}).
%% @doc return a random uuid
-spec random() -> binary().
random() ->
list_to_binary(hackney_bstr:to_hex(crypto:strong_rand_bytes(16))).
%% @doc return a random uuid based on time
-spec utc_random() -> binary().
utc_random() ->
utc_suffix(hackney_bstr:to_hex(crypto:strong_rand_bytes(9))).
%% @doc Get a list of uuids from the server
%% @spec get_uuids(server(), integer()) -> lists()
get_uuids(Server, Count) ->
gen_server:call(?MODULE, {get_uuids, Server, Count}, infinity).
%%--------------------------------------------------------------------
%% Function: start_link/0
%% Description: Starts the server
%%--------------------------------------------------------------------
%% @doc Starts the couchbeam process linked to the calling process. Usually
%% invoked by the supervisor couchbeam_sup
%% @spec start_link() -> {ok, pid()}
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%%---------------------------------------------------------------------------
%% gen_server callbacks
%%---------------------------------------------------------------------------
%% @private
init(_) ->
process_flag(trap_exit, true),
ets:new(couchbeam_uuids, [named_table, public, {keypos, 2}]),
{ok, #state{}}.
handle_call({get_uuids, #server{url=Url}=Server, Count},
_From, State) ->
{ok, Uuids} = do_get_uuids(Server, Count, [],
ets:lookup(couchbeam_uuids, Url)),
{reply, Uuids, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_get_uuids(_Server, Count, Acc, _) when length(Acc) >= Count ->
{ok, Acc};
do_get_uuids(Server, Count, Acc, []) ->
{ok, ServerUuids} = get_new_uuids(Server),
do_get_uuids(Server, Count, Acc, [ServerUuids]);
do_get_uuids(Server, Count, Acc, [#server_uuids{uuids=Uuids}]) ->
case Uuids of
[] ->
{ok, ServerUuids} = get_new_uuids(Server),
do_get_uuids(Server, Count, Acc, [ServerUuids]);
_ when length(Uuids) < Count ->
{ok, ServerUuids} = get_new_uuids(Server, Uuids),
do_get_uuids(Server, Count, Acc, [ServerUuids]);
_ ->
{Acc1, Uuids1} = do_get_uuids1(Acc, Uuids, Count),
#server{url=Url} = Server,
ServerUuids = #server_uuids{server_url=Url, uuids=Uuids1},
ets:insert(couchbeam_uuids, ServerUuids),
do_get_uuids(Server, Count, Acc1, [ServerUuids])
end.
do_get_uuids1(Acc, Uuids, 0) ->
{Acc, Uuids};
do_get_uuids1(Acc, [Uuid|Rest], Count) ->
do_get_uuids1([Uuid|Acc], Rest, Count-1).
get_new_uuids(Server) ->
get_new_uuids(Server, []).
get_new_uuids(Server, Acc) ->
get_new_uuids(Server, ?START_RETRY, Acc).
get_new_uuids(#server{url=ServerUrl, options=Opts}=Server, Backoff, Acc) ->
Count = list_to_binary(integer_to_list(1000 - length(Acc))),
Url = hackney_url:make_url(ServerUrl, <<"/_uuids">>, [{<<"count">>, Count}]),
case couchbeam_httpc:request(get, Url, [], <<>>, Opts) of
{ok, 200, _, Ref} ->
{ok, Body} = hackney:body(Ref),
{[{<<"uuids">>, Uuids}]} = couchbeam_ejson:decode(Body),
ServerUuids = #server_uuids{server_url=ServerUrl,
uuids=(Acc ++ Uuids)},
ets:insert(couchbeam_uuids, ServerUuids),
{ok, ServerUuids};
{ok, Status, Headers, Ref} ->
{ok, Body} = hackney:body(Ref),
{error, {bad_response, {Status, Headers, Body}}};
{error, closed} ->
wait_for_retry(Server, Backoff, Acc);
{error, connect_timeout} ->
wait_for_retry(Server, Backoff, Acc);
Error ->
Error
end.
wait_for_retry(_S, ?MAX_RETRY, _A) ->
{error, max_retry};
wait_for_retry(S, B, A) ->
%% increment backoff
B2 = min(B bsl 1, ?MAX_RETRY),
timer:sleep(B2),
get_new_uuids(S, B2, A).
utc_suffix(Suffix) ->
Now = {_, _, Micro} = erlang:timestamp(),
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).