Packages
Typed distributed messaging for Gleam on the BEAM.
Current section
Files
Jump to
Current section
Files
src/distribute@cluster.erl
-module(distribute@cluster).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/distribute/cluster.gleam").
-export([nodes/0, self_node/0, ping/1, is_distributed/0, connected_count/0, is_healthy/0, start_node/2, connect/1, health/0]).
-export_type([start_error/0, connect_error/0, cluster_health/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type start_error() :: {invalid_node_name, binary()} |
already_started |
cookie_too_long |
{network_error, binary()} |
{system_error, binary()} |
{start_failed, binary()}.
-type connect_error() :: connect_timeout |
node_not_found |
{connect_network_error, binary()} |
connect_ignored.
-type cluster_health() :: {cluster_health,
binary(),
boolean(),
list(binary()),
integer(),
list(binary()),
list(binary())}.
-file("src/distribute/cluster.gleam", 102).
?DOC(" List all currently connected nodes.\n").
-spec nodes() -> list(binary()).
nodes() ->
cluster_ffi:nodes().
-file("src/distribute/cluster.gleam", 107).
?DOC(" Get the current node's name.\n").
-spec self_node() -> binary().
self_node() ->
cluster_ffi:self_node().
-file("src/distribute/cluster.gleam", 112).
?DOC(" Ping a remote node. Returns `True` if it responds.\n").
-spec ping(binary()) -> boolean().
ping(Node) ->
cluster_ffi:ping(Node).
-file("src/distribute/cluster.gleam", 117).
?DOC(" Whether this node is running as a distributed node.\n").
-spec is_distributed() -> boolean().
is_distributed() ->
self_node() /= <<"nonode@nohost"/utf8>>.
-file("src/distribute/cluster.gleam", 122).
?DOC(" Number of currently connected nodes.\n").
-spec connected_count() -> integer().
connected_count() ->
erlang:length(nodes()).
-file("src/distribute/cluster.gleam", 127).
?DOC(" Quick health check: distributed and has at least one connection.\n").
-spec is_healthy() -> boolean().
is_healthy() ->
is_distributed() andalso (nodes() /= []).
-file("src/distribute/cluster.gleam", 180).
-spec validate_node_name(binary()) -> {ok, nil} | {error, start_error()}.
validate_node_name(Name) ->
case gleam_stdlib:contains_string(Name, <<"@"/utf8>>) of
true ->
{ok, nil};
false ->
{error, {invalid_node_name, <<"Node name must contain '@'"/utf8>>}}
end.
-file("src/distribute/cluster.gleam", 187).
-spec validate_cookie(binary()) -> {ok, nil} | {error, start_error()}.
validate_cookie(Cookie) ->
case string:length(Cookie) > 255 of
true ->
{error, cookie_too_long};
false ->
{ok, nil}
end.
-file("src/distribute/cluster.gleam", 194).
-spec classify_start_reason(binary()) -> start_error().
classify_start_reason(Reason) ->
case Reason of
<<"already_started"/utf8>> ->
already_started;
<<"invalid_node_name"/utf8>> ->
{invalid_node_name, <<"Invalid node name"/utf8>>};
<<"cookie_too_long"/utf8>> ->
cookie_too_long;
_ ->
case (gleam_stdlib:contains_string(Reason, <<"network"/utf8>>)
orelse gleam_stdlib:contains_string(Reason, <<"eaddrinuse"/utf8>>))
orelse gleam_stdlib:contains_string(Reason, <<"connection"/utf8>>) of
true ->
{network_error, Reason};
false ->
case gleam_stdlib:contains_string(
Reason,
<<"permission"/utf8>>
)
orelse gleam_stdlib:contains_string(
Reason,
<<"access"/utf8>>
) of
true ->
{system_error, Reason};
false ->
{start_failed, Reason}
end
end
end.
-file("src/distribute/cluster.gleam", 66).
?DOC(
" Start a distributed BEAM node.\n"
"\n"
" `name` must contain `@` (e.g. `\"myapp@127.0.0.1\"`).\n"
" `cookie` must be ≤ 255 characters.\n"
).
-spec start_node(binary(), binary()) -> {ok, nil} | {error, start_error()}.
start_node(Name, Cookie) ->
case validate_node_name(Name) of
{error, E} ->
{error, E};
{ok, _} ->
case validate_cookie(Cookie) of
{error, E@1} ->
{error, E@1};
{ok, _} ->
Res = cluster_ffi:start_node(Name, Cookie),
case cluster_ffi:is_ok_atom(Res) of
true ->
{ok, nil};
false ->
{error,
classify_start_reason(
cluster_ffi:get_error_reason(Res)
)}
end
end
end.
-file("src/distribute/cluster.gleam", 218).
-spec classify_connect_reason(binary()) -> connect_error().
classify_connect_reason(Reason) ->
case gleam_stdlib:contains_string(Reason, <<"timeout"/utf8>>) of
true ->
connect_timeout;
false ->
{connect_network_error, Reason}
end.
-file("src/distribute/cluster.gleam", 84).
?DOC(" Connect to a remote node. Returns `Ok(Nil)` on success.\n").
-spec connect(binary()) -> {ok, nil} | {error, connect_error()}.
connect(Node) ->
case gleam_stdlib:contains_string(Node, <<"@"/utf8>>) of
false ->
{error, node_not_found};
true ->
Result = cluster_ffi:connect(Node),
case cluster_ffi:is_true(Result) of
true ->
{ok, nil};
false ->
case cluster_ffi:is_ignored(Result) of
true ->
{error, connect_ignored};
false ->
{error,
classify_connect_reason(
cluster_ffi:get_error_reason(Result)
)}
end
end
end.
-file("src/distribute/cluster.gleam", 229).
-spec do_partition(list(binary()), list(binary()), list(binary())) -> {list(binary()),
list(binary())}.
do_partition(Remaining, Reachable, Unreachable) ->
case Remaining of
[] ->
{lists:reverse(Reachable), lists:reverse(Unreachable)};
[Node | Rest] ->
case ping(Node) of
true ->
do_partition(Rest, [Node | Reachable], Unreachable);
false ->
do_partition(Rest, Reachable, [Node | Unreachable])
end
end.
-file("src/distribute/cluster.gleam", 225).
-spec partition_by_ping(list(binary())) -> {list(binary()), list(binary())}.
partition_by_ping(Node_list) ->
do_partition(Node_list, [], []).
-file("src/distribute/cluster.gleam", 148).
?DOC(" Perform a cluster health check.\n").
-spec health() -> cluster_health().
health() ->
Self = self_node(),
Is_dist = is_distributed(),
case Is_dist of
false ->
{cluster_health, Self, false, [], 0, [], []};
true ->
Connected = nodes(),
{Reachable, Unreachable} = partition_by_ping(Connected),
{cluster_health,
Self,
true,
Connected,
erlang:length(Connected),
Reachable,
Unreachable}
end.