-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.