Current section
Files
Jump to
Current section
Files
src/reki.erl
-module(reki).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/reki.gleam").
-export([get_subject/1, new/0, lookup/2, do_start/4, lookup_or_start_with_timeout/4, lookup_or_start/3, get_pid/1, start/1, supervised/1]).
-export_type([registry/2, registry_message/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque registry(FNU, FNV) :: {registry,
gleam@erlang@process:name(registry_message(FNU, FNV)),
reki@ets:table_identifier(FNU, gleam@erlang@process:subject(FNV))}.
-opaque registry_message(FNW, FNX) :: {start_if_not_exists,
FNW,
fun((FNW) -> {ok,
gleam@otp@actor:started(gleam@erlang@process:subject(FNX))} |
{error, gleam@otp@actor:start_error()}),
gleam@erlang@process:subject({ok, gleam@erlang@process:subject(FNX)} |
{error, gleam@otp@actor:start_error()})} |
{process_exited, gleam@erlang@process:pid_()}.
-file("src/reki.gleam", 47).
?DOC(false).
-spec get_subject(registry(FNY, FNZ)) -> gleam@erlang@process:subject(registry_message(FNY, FNZ)).
get_subject(Registry) ->
gleam@erlang@process:named_subject(erlang:element(2, Registry)).
-file("src/reki.gleam", 137).
?DOC(
" Create a registry. Call this at the start of your program before\n"
" creating the supervision tree.\n"
"\n"
" **Important:** Each call creates a unique atom for the ETS table name.\n"
" Atoms are never garbage collected by the BEAM, so this function must\n"
" only be called a fixed number of times (e.g. once per registry at app\n"
" startup). Do not call `new()` dynamically in a loop or on each request.\n"
).
-spec new() -> registry(any(), any()).
new() ->
{registry,
gleam_erlang_ffi:new_name(<<"reki"/utf8>>),
reki_ets_ffi:new_unique_atom()}.
-file("src/reki.gleam", 175).
?DOC(
" Looks up an actor by key in the registry, without starting one if missing.\n"
"\n"
" Returns `None` if no actor exists for the given key. This is a direct ETS\n"
" read, so it's fast but may return a stale Subject if a process just died.\n"
"\n"
" Use `lookup_or_start` if you want to start an actor when the key is missing.\n"
).
-spec lookup(registry(FQH, FQI), FQH) -> gleam@option:option(gleam@erlang@process:subject(FQI)).
lookup(Registry, Key) ->
reki@ets:lookup(erlang:element(3, Registry), Key).
-file("src/reki.gleam", 207).
?DOC(false).
-spec do_start(
registry(FQY, FQZ),
FQY,
fun((FQY) -> {ok,
gleam@otp@actor:started(gleam@erlang@process:subject(FQZ))} |
{error, gleam@otp@actor:start_error()}),
integer()
) -> {ok, gleam@erlang@process:subject(FQZ)} |
{error, gleam@otp@actor:start_error()}.
do_start(Registry, Key, Start_fn, Timeout) ->
gleam@otp@actor:call(
get_subject(Registry),
Timeout,
fun(Reply_to) -> {start_if_not_exists, Key, Start_fn, Reply_to} end
).
-file("src/reki.gleam", 183).
?DOC(false).
-spec lookup_or_start_with_timeout(
registry(FQN, FQO),
FQN,
fun((FQN) -> {ok,
gleam@otp@actor:started(gleam@erlang@process:subject(FQO))} |
{error, gleam@otp@actor:start_error()}),
integer()
) -> {ok, gleam@erlang@process:subject(FQO)} |
{error, gleam@otp@actor:start_error()}.
lookup_or_start_with_timeout(Registry, Key, Start_fn, Timeout) ->
case lookup(Registry, Key) of
{some, Sub} ->
{ok, Sub};
none ->
do_start(Registry, Key, Start_fn, Timeout)
end.
-file("src/reki.gleam", 160).
?DOC(
" Looks up an actor by key, or starts one if it doesn't exist.\n"
"\n"
" This is the primary function for getting an actor from the registry. It\n"
" first checks ETS for an existing entry (fast O(1) lookup), and only goes\n"
" through the registry actor if the key isn't found.\n"
"\n"
" In rare cases, this may return a stale Subject if a process just died but\n"
" reki hasn't processed the EXIT yet. If calling the returned Subject fails,\n"
" retry with `lookup_or_start_slow`.\n"
).
-spec lookup_or_start(
registry(FPW, FPX),
FPW,
fun((FPW) -> {ok,
gleam@otp@actor:started(gleam@erlang@process:subject(FPX))} |
{error, gleam@otp@actor:start_error()})
) -> {ok, gleam@erlang@process:subject(FPX)} |
{error, gleam@otp@actor:start_error()}.
lookup_or_start(Registry, Key, Start_fn) ->
lookup_or_start_with_timeout(Registry, Key, Start_fn, 5000).
-file("src/reki.gleam", 220).
?DOC(false).
-spec get_pid(registry(any(), any())) -> {ok, gleam@erlang@process:pid_()} |
{error, nil}.
get_pid(Registry) ->
_pipe = get_subject(Registry),
gleam@erlang@process:subject_owner(_pipe).
-file("src/reki.gleam", 79).
-spec on_message(
reki@ets:table_identifier(FOO, gleam@erlang@process:subject(FOP)),
registry_message(FOO, FOP)
) -> gleam@otp@actor:next(reki@ets:table_identifier(FOO, gleam@erlang@process:subject(FOP)), registry_message(FOO, FOP)).
on_message(Table_id, Message) ->
case Message of
{process_exited, Pid} ->
case reki_ets_ffi:pdict_delete(Pid) of
{ok, Key} ->
_ = reki@ets:delete(Table_id, Key),
nil;
{error, nil} ->
nil
end,
gleam@otp@actor:continue(Table_id);
{start_if_not_exists, Key@1, Start_fn, Reply_to} ->
case reki@ets:lookup(Table_id, Key@1) of
{some, Subject} ->
gleam@erlang@process:send(Reply_to, {ok, Subject}),
gleam@otp@actor:continue(Table_id);
none ->
gleam@erlang@process:send(
Reply_to,
begin
gleam@result:map(
Start_fn(Key@1),
fun(Started) ->
{started, Pid@1, Subject@1} = Started,
case reki@ets:insert(
Table_id,
Key@1,
Subject@1
) of
{ok, nil} -> nil;
_assert_fail ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"reki"/utf8>>,
function => <<"on_message"/utf8>>,
line => 109,
value => _assert_fail,
start => 3477,
'end' => 3532,
pattern_start => 3488,
pattern_end => 3495}
)
end,
reki_ets_ffi:pdict_put(Pid@1, Key@1),
Subject@1
end
)
end
),
gleam@otp@actor:continue(Table_id)
end
end.
-file("src/reki.gleam", 53).
-spec start_registry_actor(registry(FOF, FOG)) -> {ok,
gleam@otp@actor:started(registry(FOF, FOG))} |
{error, gleam@otp@actor:start_error()}.
start_registry_actor(Registry) ->
_pipe@5 = gleam@otp@actor:new_with_initialiser(
1000,
fun(_) ->
gleam_erlang_ffi:trap_exits(true),
_ = reki_ets_ffi:new(erlang:element(3, Registry)),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@1 = gleam@erlang@process:select(
_pipe,
get_subject(Registry)
),
gleam@erlang@process:select_trapped_exits(
_pipe@1,
fun(Exit) -> {process_exited, erlang:element(2, Exit)} end
)
end,
_pipe@2 = gleam@otp@actor:initialised(erlang:element(3, Registry)),
_pipe@3 = gleam@otp@actor:selecting(_pipe@2, Selector),
_pipe@4 = gleam@otp@actor:returning(_pipe@3, Registry),
{ok, _pipe@4}
end
),
_pipe@6 = gleam@otp@actor:on_message(_pipe@5, fun on_message/2),
_pipe@7 = gleam@otp@actor:named(_pipe@6, erlang:element(2, Registry)),
gleam@otp@actor:start(_pipe@7).
-file("src/reki.gleam", 124).
?DOC(
" Start the registry. You likely want to use the `supervised` function instead,\n"
" to add the registry to your supervision tree, but this may be useful in tests.\n"
).
-spec start(registry(FPC, FPD)) -> {ok,
gleam@otp@actor:started(registry(FPC, FPD))} |
{error, gleam@otp@actor:start_error()}.
start(Registry) ->
start_registry_actor(Registry).
-file("src/reki.gleam", 145).
?DOC(" A specification for starting the registry under a supervisor.\n").
-spec supervised(registry(FPP, FPQ)) -> gleam@otp@supervision:child_specification(registry(FPP, FPQ)).
supervised(Registry) ->
gleam@otp@supervision:worker(fun() -> start_registry_actor(Registry) end).