Skip to main content

src/client/livery_client_timeout.erl

-module(livery_client_timeout).
-moduledoc """
Client layer: bound a request to a deadline.

Runs the downstream call in a monitored child process and returns
`{error, timeout}` if it does not finish within `Ms`, killing the child
(which tears down the in-flight connection). Add it with
`livery_client:timeout/1`.
""".

-export([call/3]).

-spec call(livery_client:request(), livery_client:next(), pos_integer()) ->
    {ok, livery_client:response()} | {error, term()}.
call(Req, Next, Ms) ->
    Self = self(),
    Ref = make_ref(),
    {Pid, MRef} = spawn_monitor(fun() ->
        Result =
            try
                Next(Req)
            catch
                Class:Reason:Stack -> {'$crash', Class, Reason, Stack}
            end,
        Self ! {Ref, Result}
    end),
    receive
        {Ref, {'$crash', Class, Reason, Stack}} ->
            erlang:demonitor(MRef, [flush]),
            erlang:raise(Class, Reason, Stack);
        {Ref, Result} ->
            erlang:demonitor(MRef, [flush]),
            Result;
        {'DOWN', MRef, process, Pid, Reason} ->
            {error, {worker_down, Reason}}
    after Ms ->
        exit(Pid, kill),
        erlang:demonitor(MRef, [flush]),
        {error, timeout}
    end.