-module(aws@internal@actor_lifecycle).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/actor_lifecycle.gleam").
-export([safe_call/3, shutdown_via_stop/2, shutdown_via_stop_sync/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Generic teardown helpers for actors that follow the SDK's\n"
" \"polite stop message\" convention. Used by `credentials_cache` and\n"
" `retry/rate_limiter` — both opaque types wrap a `Subject` whose\n"
" message variant set includes a `Stop` constructor that triggers\n"
" `actor.stop()` next dispatch.\n"
"\n"
" Lives in a separate module so the same fire-and-forget + monitor-\n"
" based synchronous teardown lands in exactly one place. Adding a\n"
" third long-lived actor later (a request-rate limiter, an event-\n"
" stream demuxer) reuses these directly.\n"
).
-file("src/aws/internal/actor_lifecycle.gleam", 28).
?DOC(
" A non-crashing replacement for `actor.call` / `process.call`.\n"
"\n"
" `actor.call` *panics* the caller when the callee has exited or fails to\n"
" reply within the timeout. That is fine for a supervised callee, but the\n"
" SDK's long-lived actors (credentials cache, retry rate-limiter) are\n"
" unsupervised — a single crashing provider would otherwise take down the\n"
" consumer's process on every subsequent call. This variant monitors the\n"
" callee and selects over both the reply and the `DOWN` signal, returning\n"
" `Error(Nil)` on a dead owner, an immediate `DOWN`, or a timeout, and\n"
" `Ok(reply)` on a normal reply. Callers map `Error(Nil)` onto their own\n"
" error shape so the consumer sees a recoverable error, never a panic.\n"
"\n"
" Mirrors `gleam_erlang`'s internal `perform_call`, minus the two\n"
" `let assert`/`panic` lines that make the stock `call` crash.\n"
).
-spec safe_call(
gleam@erlang@process:subject(LBN),
integer(),
fun((gleam@erlang@process:subject(LBP)) -> LBN)
) -> {ok, LBP} | {error, nil}.
safe_call(Subject, Timeout_ms, Make_request) ->
case gleam@erlang@process:subject_owner(Subject) of
{error, _} ->
{error, nil};
{ok, Callee} ->
Reply_subject = gleam@erlang@process:new_subject(),
Monitor = gleam@erlang@process:monitor(Callee),
gleam@erlang@process:send(Subject, Make_request(Reply_subject)),
Outcome = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@1 = gleam@erlang@process:select_map(
_pipe,
Reply_subject,
fun(Field@0) -> {ok, Field@0} end
),
_pipe@2 = gleam@erlang@process:select_specific_monitor(
_pipe@1,
Monitor,
fun(_) -> {error, nil} end
),
gleam_erlang_ffi:select(_pipe@2, Timeout_ms)
end,
gleam@erlang@process:demonitor_process(Monitor),
case Outcome of
{ok, Reply_result} ->
Reply_result;
{error, nil} ->
{error, nil}
end
end.
-file("src/aws/internal/actor_lifecycle.gleam", 57).
?DOC(
" Fire-and-forget teardown: send the supplied `Stop` message and\n"
" return immediately. The actor exits on its next dispatch. Safe to\n"
" call against a dead actor — Erlang silently drops sends to a\n"
" terminated Pid.\n"
).
-spec shutdown_via_stop(gleam@erlang@process:subject(LBT), LBT) -> nil.
shutdown_via_stop(Subject, Stop_message) ->
gleam@erlang@process:send(Subject, Stop_message).
-file("src/aws/internal/actor_lifecycle.gleam", 68).
?DOC(
" Synchronous teardown: monitor the owning Pid, send `Stop`, then\n"
" wait for the `DOWN` signal up to `timeout_ms`. Returns `Ok(Nil)`\n"
" on clean exit (already-dead actor short-circuits here too — the\n"
" `subject_owner` lookup fails fast). Returns `Error(Nil)` only on\n"
" real timeout — i.e. the actor is alive but didn't exit within the\n"
" window. The monitor is demonitored on the timeout path so the\n"
" caller's mailbox doesn't accumulate stray `DOWN` messages.\n"
).
-spec shutdown_via_stop_sync(gleam@erlang@process:subject(LBV), LBV, integer()) -> {ok,
nil} |
{error, nil}.
shutdown_via_stop_sync(Subject, Stop_message, Timeout_ms) ->
case gleam@erlang@process:subject_owner(Subject) of
{error, _} ->
{ok, nil};
{ok, Pid} ->
Monitor = gleam@erlang@process:monitor(Pid),
gleam@erlang@process:send(Subject, Stop_message),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select_specific_monitor(
_pipe,
Monitor,
fun(_) -> nil end
)
end,
case gleam_erlang_ffi:select(Selector, Timeout_ms) of
{ok, nil} ->
{ok, nil};
{error, nil} ->
gleam@erlang@process:demonitor_process(Monitor),
{error, nil}
end
end.