Current section
Files
Jump to
Current section
Files
lib/request_timeout.ex
defmodule RequestTimeout do
require Logger
import Plug.Conn
alias Plug.Conn
@default_timeout_ms 30_000
@default_kill_msg :kill
@default_http_ct "text/plain"
@default_http_msg "Resource Limit Reached"
@default_http_code 508
@default_sup nil
defstruct after: @default_timeout_ms,
code: @default_http_code,
ct: @default_http_ct,
err: @default_kill_msg,
msg: @default_http_msg,
sup: @default_sup
@doc false
def init(options) do
%__MODULE__{}
|> case do
s -> %{s | after: options |> Keyword.get(:after, s.after)}
end
|> case do
s -> %{s | code: options |> Keyword.get(:code, s.code)}
end
|> case do
s -> %{s | ct: options |> Keyword.get(:content_type, s.ct)}
end
|> case do
s -> %{s | err: options |> Keyword.get(:error, s.err)}
end
|> case do
s -> %{s | msg: options |> Keyword.get(:msg, s.msg)}
end
|> case do
s -> %{s | sup: options |> Keyword.get(:sup, s.sup)}
end
end
@doc false
def call(conn = %Conn{}, opts = %__MODULE__{sup: sup}) do
md = Logger.metadata()
parent = self()
if is_nil(sup) do
_child = spawn(fn -> circuit_breaker(md, conn, parent, opts) end)
else
# TODO: spawn under sup
throw(:not_implemented!)
end
conn
end
defp circuit_breaker(md, conn, parent, %__MODULE__{
after: ms,
code: code,
ct: ct,
err: err,
msg: msg
}) do
:ok = Logger.metadata(md)
ref = Process.monitor(parent)
receive do
{:DOWN, ^ref, :process, ^parent, reason} ->
Logger.debug("#{inspect(parent)} died with #{inspect(reason)}")
true = Process.demonitor(ref)
after
ms ->
Logger.warn(
"#{inspect(parent)} took longer than #{ms}ms, sending #{code} and exiting with #{err}"
)
true = Process.demonitor(ref)
conn
|> put_resp_content_type(ct)
|> send_resp(code, msg)
|> halt()
{:current_stacktrace, st} = Process.info(parent, :current_stacktrace)
Logger.error("\n#{Exception.format_stacktrace(st)}")
Process.exit(parent, err)
end
end
end