Packages

A distributed Id generator

Current section

Files

Jump to
erlflake src generator.erl
Raw

src/generator.erl

%%%---------------------------------------------------------------------------
%%% Copyright (C) 2021, Fix Solution All Rights Reserved
%%% Unauthorized copy of this file is through any medium strictly not allowed.
%%%
%%% Authors : Alpha Shaw <shawalpha5@gmail.com>
%%% Created : 20 Apr 2021 by <shawalpha5@gmail.com>
%%% 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
>> =
<<Flake:?FLAKE_BITS/unsigned-integer>>,
{Timestamp, Sequence, Worker}.
create_flake(Timestamp, Sequence, Worker) ->
<<Flake:?FLAKE_BITS/unsigned-integer>> =
<<
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.