%%%--------------------------------------------------------------------------- %%% Copyright (C) 2021, Fix Solution All Rights Reserved %%% Unauthorized copy of this file is through any medium strictly not allowed. %%% %%% Authors : Alpha Shaw %%% Created : 20 Apr 2021 by %%% Purpose : %%% %%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%%--------------------------------------------------------------------------- -module(generator). -author("Alpha Umaru Shaw"). -behaviour(gen_server). -include("erlflake.hrl"). %% API %% For testing... -export([create_flake/3]). -export([start_link/1]). -export([next_id/0, split/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { last_millis = 0, last_micros = 0, worker, sequence = 0 }). -define(SERVER, ?MODULE). %%%=================================================================== %%% API %%%=================================================================== start_link(Worker) -> gen_server:start_link({local, ?SERVER}, ?MODULE, [Worker], []). next_id() -> case gen_server:call(?SERVER, generate) of {Timestamp, Sequence, Worker} -> create_flake(Timestamp, Sequence, Worker); R -> error(bad_response, [R]) end. split(Flake) when is_binary(Flake) -> split(binary_to_integer(Flake)); split(Flake) when is_integer(Flake) -> << Timestamp:?TIMESTAMP_BITS/unsigned-integer, Sequence:?SEQUENCE_BITS/unsigned-integer, Worker:?WORKER_BITS/unsigned-integer >> = <>, {Timestamp, Sequence, Worker}. create_flake(Timestamp, Sequence, Worker) -> <> = << Timestamp:?TIMESTAMP_BITS/unsigned-integer, Sequence:?SEQUENCE_BITS/unsigned-integer, Worker:?WORKER_BITS/unsigned-integer >>, Flake. %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([Worker]) -> {ok, #state{worker = Worker}}. handle_call(generate, _From, #state{worker = Worker} = State) -> {MicroSecs, Sequence, NewState} = next_flake(State), {reply, {MicroSecs, Sequence, Worker}, NewState}; handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Request, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== next_flake(#state{last_micros = LastMicros, last_millis = LastMillis, sequence = Seq} = State) -> case timestamp:time_units() of {_MicroTs, LastMillis} when Seq > ?MAX_SEQUENCE -> %% Clock didn't move and the counter overflowed error_logger:warning_msg("Counter overflowed. " "Generator (~p) is generating too fast", [self()]), timer:sleep(1), %% Sleep so the clock can be ahead of us on wakeup next_flake(State); {_MicroSecs, LastMillis} -> %% Clock didn't move, increment the sequence {LastMicros, Seq, State#state{sequence = Seq + 1}}; {MicroSecs, MillSecs} -> %% Clock moved, reset the sequence {MicroSecs, 0, State#state{ last_micros = MicroSecs, last_millis = MillSecs, sequence = 1}} end.