Current section
Files
Jump to
Current section
Files
src/eredis_streams_worker.erl
-module(eredis_streams_worker).
-behaviour(gen_server).
%% API
-export([start_link/1]).
%% gen_server callbacks
-include_lib("eredis_streams/include/telemetry.hrl").
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%%--------------------------------------------------------------------
%% @doc
%% Starts producer
%% @end
%%--------------------------------------------------------------------
start_link(#{name := StreamName, worker_num := Num} = Params) ->
gen_server:start_link({local, process_name(StreamName, Num)}, ?MODULE, Params, []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%% @end
%%--------------------------------------------------------------------
init(Params) ->
process_flag(trap_exit, true),
erlang:send(self(), init),
{
ok,
#{
wid => new_wid(),
stream => stream_name(Params),
pool => stream_pool(Params),
worker_num => worker_num(Params),
xgroup => xgroup(Params),
process_name => xgroup_process_name(xgroup(Params)),
prefetch_count => prefetch_count(Params),
fetch_interval => fetch_interval_ms(Params),
master => undefined,
status => wait_for_init,
opts => Params
}
}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%% @end
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%% @end
%%--------------------------------------------------------------------
handle_cast(_Msg, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%% @end
%%--------------------------------------------------------------------
handle_info(
init,
#{
status := wait_for_init,
xgroup := Xgroup,
pool := Pool,
stream := Stream,
worker_num := WorkerNum,
opts := #{workers := #{module := Handler}}
} = State
) ->
?EXEC_TELEMETRY(?EREDIS_STREAMS_WORKER_INIT, #{}, #{stream => Stream, worker_num => WorkerNum}),
case eredis_streams_db:init_xgroup(Pool, Stream, Xgroup) of
ok ->
HandlerModule = maps:get(name, Handler),
HandlerArgs = maps:get(args, Handler, #{}),
HandlerModule:init(Handler#{args => HandlerArgs#{worker_num => WorkerNum}}),
erlang:send(self(), bootstrap_cluster),
{noreply, State#{status => wait_for_cluster}};
Err ->
erlang:send_after(100, self(), init),
?EXEC_TELEMETRY(?EREDIS_STREAMS_ERROR, #{}, #{error => Err}),
{noreply, State}
end;
handle_info(
bootstrap_cluster,
#{
status := wait_for_cluster,
stream := Stream,
worker_num := WorkerNum,
opts := #{workers := #{singleton := true}}
} = State
) ->
?EXEC_TELEMETRY(
?EREDIS_STREAMS_WORKER_READY,
#{},
#{stream => Stream, worker_num => WorkerNum, sigleton => true}
),
{noreply, update_master(State)};
handle_info(bootstrap_cluster, #{stream := Stream, worker_num := WorkerNum} = State) ->
?EXEC_TELEMETRY(
?EREDIS_STREAMS_WORKER_READY,
#{},
#{stream => Stream, worker_num => WorkerNum, sigleton => false}
),
erlang:send(self(), tick),
{noreply, State#{master => true, status => ready}};
handle_info(update_master, State) -> {noreply, update_master(State)};
handle_info(
tick,
#{
status := ready,
master := true,
worker_num := WorkerNum,
stream := Stream,
pool := Pool,
xgroup := Group,
process_name := Process,
prefetch_count := Count,
fetch_interval := FetchInterval,
opts := #{workers := #{module := Handler}}
} = State
) ->
HandlerModule = maps:get(name, Handler),
HandlerArgs = maps:get(args, Handler, #{}),
StartTime = eredis_streams_tools:now_mcs(),
Stats =
case
catch
eredis_streams_db:stream_fetch_messages(Pool, Stream, Group, Process, Count, FetchInterval) of
[] -> #{};
[_ | _] = MsgList ->
HandlerModule:handle_messages(
eredis_streams_tools:build_conn(Pool, Stream, Group, #{}),
MsgList,
HandlerArgs#{worker_num => WorkerNum}
);
Err ->
?EXEC_TELEMETRY(?EREDIS_STREAMS_ERROR, #{}, #{reason => Err}),
#{}
end,
?EXEC_TELEMETRY(
?EREDIS_STREAMS_WORKER_TICK,
#{total_msc => eredis_streams_tools:now_mcs() - StartTime, stats => Stats},
#{stream => Stream, worker_num => WorkerNum}
),
erlang:send_after(FetchInterval, self(), tick),
{noreply, State};
handle_info(tick, State) ->
% не генерируем новое событие tick, так как tick перезапустится в update_master
{noreply, State};
handle_info(Info, #{opts := #{workers := #{module := #{name := Handler}}}} = State) ->
Handler:handle_info(Info, State);
handle_info(_Info, State) -> {noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
?EXEC_TELEMETRY(?EREDIS_STREAMS_WORKER_TERMINATE, #{}, #{}),
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
update_master(
#{
pool := Pool,
stream := Stream,
wid := Wid,
opts := #{workers := WorkersOpts},
master := IsCurMaster
} = State
) ->
MasterTimeoutMs = maps:get(singleton_timeout_ms, WorkersOpts, 3000),
case eredis_streams_db:is_master(Pool, Stream, Wid, MasterTimeoutMs) of
false ->
erlang:send_after(300, self(), update_master),
State#{master => false, status => ready};
true ->
case IsCurMaster of
Nope when Nope =:= false orelse Nope =:= undefined -> erlang:send(self(), tick);
_ -> skip
end,
erlang:send_after(300, self(), update_master),
State#{master => true, status => ready}
end.
process_name(StreamName, Num) ->
binary_to_atom(
iolist_to_binary([["eredis_streams_worker_", StreamName, "_", integer_to_list(Num), "_sup"]]),
utf8
).
stream_name(#{name := Name} = _Params) -> Name.
new_wid() -> integer_to_binary(erlang:phash2({self(), eredis_streams_tools:now_mcs()})).
xgroup(#{xgroup := Xgroup} = _Config) -> Xgroup;
xgroup(_) -> "g_default".
xgroup_process_name(Xgroup) -> lists:flatten([Xgroup, "-", integer_to_list(erlang:phash2(node()))]).
prefetch_count(Params) ->
Workers = maps:get(workers, Params, #{}),
maps:get(prefetch_count, Workers, 1).
fetch_interval_ms(Params) ->
Workers = maps:get(workers, Params, #{}),
maps:get(fetch_interval_ms, Workers, 450).
stream_pool(#{pool := Pool} = _Params) -> Pool;
stream_pool(_Params) -> {error, undefined_pool}.
worker_num(#{worker_num := Num} = _Params) -> Num;
worker_num(_) -> 1.