Current section

Files

Jump to
discord_bot_light src gun_wrapper.erl
Raw

src/gun_wrapper.erl

%%%===================================================================
%%% Gun Wrapper
%%% Provides Gun functions with proper timeouts to avoid infinite blocking
%%%===================================================================
-module(gun_wrapper).
-behaviour(gen_server).
%% API
-export([start_link/0, open_with_timeout/3, open_with_timeout/4]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(DEFAULT_TIMEOUT, 15000). % 15 seconds
%% @doc Start the gun wrapper service
-spec start_link() -> {ok, pid()} | {error, term()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% @doc Open Gun connection with default timeout
-spec open_with_timeout(string(), pos_integer(), map()) -> {ok, pid()} | {error, term()}.
open_with_timeout(Host, Port, Opts) ->
open_with_timeout(Host, Port, Opts, ?DEFAULT_TIMEOUT).
%% @doc Open Gun connection with custom timeout
-spec open_with_timeout(string(), pos_integer(), map(), pos_integer()) -> {ok, pid()} | {error, term()}.
open_with_timeout(Host, Port, Opts, Timeout) ->
gen_server:call(?MODULE, {open, Host, Port, Opts, Timeout}, Timeout + 1000).
init([]) ->
{ok, #{}}.
handle_call({open, Host, Port, Opts, Timeout}, _From, State) ->
io:format("Opening Gun connection to ~s:~p with timeout ~pms~n", [Host, Port, Timeout]),
Parent = self(),
Ref = make_ref(),
% Spawn worker process
Worker = spawn_link(fun() ->
try
Result = gun:open(Host, Port, Opts),
Parent ! {Ref, Result}
catch
Class:Error:Stack ->
Parent ! {Ref, {error, {exception, Class, Error, Stack}}}
end
end),
% Wait for result with timeout
Result = receive
{Ref, Res} ->
case Res of
{ok, ConnPid} when is_pid(ConnPid) ->
io:format("Gun connection successful to ~s:~p~n", [Host, Port]),
{ok, ConnPid};
{error, Reason} ->
io:format("Gun connection failed to ~s:~p: ~p~n", [Host, Port, Reason]),
{error, Reason}
end
after Timeout ->
% Kill the worker
unlink(Worker),
exit(Worker, kill),
io:format("Gun connection timeout to ~s:~p after ~pms~n", [Host, Port, Timeout]),
{error, timeout}
end,
{reply, Result, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.