Current section

Files

Jump to
brod src brod_kafka_apis.erl
Raw

src/brod_kafka_apis.erl

%%%
%%% Copyright (c) 2017-2021 Klarna Bank AB (publ)
%%%
%%% 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.
%%%
%% @private
%% Version ranges are cached per host and per connection pid in ets
-module(brod_kafka_apis).
-export([ default_version/1
, pick_version/2
, start_link/0
, stop/0
]).
-export([ code_change/3
, handle_call/3
, handle_cast/2
, handle_info/2
, init/1
, terminate/2
]).
-include("brod_int.hrl").
-export_type([ api/0
, vsn/0
]).
-define(SERVER, ?MODULE).
-define(ETS, ?MODULE).
-record(state, {}).
-type vsn() :: kpro:vsn().
-type range() :: {vsn(), vsn()}.
-type api() :: kpro:api().
-type conn() :: kpro:connection().
%% @doc Start process.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec stop() -> ok.
stop() ->
gen_server:call(?SERVER, stop, infinity).
%% @doc Get default supported version for the given API.
-spec default_version(api()) -> vsn().
default_version(API) ->
{Min, _Max} = supported_versions(API),
Min.
%% @doc Pick API version for the given API.
-spec pick_version(conn(), api()) -> vsn().
pick_version(Conn, API) ->
do_pick_version(Conn, API, supported_versions(API)).
%%%_* gen_server callbacks =====================================================
init([]) ->
?ETS = ets:new(?ETS, [named_table, public]),
{ok, #state{}}.
handle_info({'DOWN', _Mref, process, Conn, _Reason}, State) ->
_ = ets:delete(?ETS, Conn),
{noreply, State};
handle_info(Info, State) ->
?BROD_LOG_ERROR("unknown info ~p", [Info]),
{noreply, State}.
handle_cast({monitor_connection, Conn}, State) ->
erlang:monitor(process, Conn),
{noreply, State};
handle_cast(Cast, State) ->
?BROD_LOG_ERROR("unknown cast ~p", [Cast]),
{noreply, State}.
handle_call(stop, From, State) ->
gen_server:reply(From, ok),
{stop, normal, State};
handle_call(Call, _From, State) ->
{reply, {error, {unknown_call, Call}}, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
%%%_* Internals ================================================================
-spec do_pick_version(conn(), api(), range()) -> vsn().
do_pick_version(_Conn, _API, {V, V}) -> V;
do_pick_version(Conn, API, {Min, Max} = MyRange) ->
case lookup_vsn_range(Conn, API) of
none ->
Min; %% no version received from kafka, use min
{KproMin, KproMax} = Range when KproMin > Max orelse KproMax < Min ->
erlang:error({unsupported_vsn_range, API, MyRange, Range});
{_, KproMax} ->
min(KproMax, Max) %% try to use highest version
end.
%% Lookup API from cache, return 'none' if not found.
-dialyzer([{nowarn_function, [lookup_vsn_range/2]}]).
-spec lookup_vsn_range(conn(), api()) -> {vsn(), vsn()} | none.
lookup_vsn_range(Conn, API) ->
case ets:lookup(?ETS, Conn) of
[] ->
case kpro:get_api_versions(Conn) of
{ok, Versions} when is_map(Versions) ->
%% public ets, insert it by caller
ets:insert(?ETS, {Conn, Versions}),
%% tell ?SERVER to monitor the connection
%% so to delete it from cache when 'DOWN' is received
ok = monitor_connection(Conn),
maps:get(API, Versions, none);
{error, _Reason} ->
none %% connection died, ignore
end;
[{Conn, Vsns}] ->
maps:get(API, Vsns, none)
end.
%% Do not change range without verification.
supported_versions(API) ->
case API of
produce -> {0, 5};
fetch -> {0, 7};
list_offsets -> {0, 2};
metadata -> {0, 2};
offset_commit -> {2, 2};
offset_fetch -> {1, 2};
find_coordinator -> {0, 0};
join_group -> {0, 0};
heartbeat -> {0, 0};
leave_group -> {0, 0};
sync_group -> {0, 0};
describe_groups -> {0, 0};
list_groups -> {0, 0};
create_topics -> {0, 0};
delete_topics -> {0, 0};
_ -> erlang:error({unsupported_api, API})
end.
monitor_connection(Conn) ->
gen_server:cast(?SERVER, {monitor_connection, Conn}).
%%%_* Emacs ====================================================================
%%% Local Variables:
%%% allout-layout: t
%%% erlang-indent-level: 2
%%% End: