Current section

Files

Jump to
gmysql src gmysql@pool.erl
Raw

src/gmysql@pool.erl

-module(gmysql@pool).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([disconnect/1, restart_connection/2, restart_all_connections/1, new_connection/1, checkout_connection/2, checkin_connection/2, with_connection/3, connect/3]).
-export_type([pool/0, message/0, state/0, slot/0, with_connection_error/0]).
-opaque pool() :: {pool, gleam@erlang@process:subject(message())}.
-opaque message() :: {update_state, fun((state()) -> state())} |
{initialize, gleam@erlang@process:subject(message())} |
shutdown |
{restart, gmysql:connection()} |
restart_all |
start_new |
{checkout,
gleam@erlang@process:subject({ok, gmysql:connection()} | {error, nil})} |
{checkin, gmysql:connection()} |
{tick, gleam@erlang@process:subject(message())}.
-type state() :: {state,
list(slot()),
gmysql:config(),
gleam@otp@intensity_tracker:intensity_tracker()}.
-type slot() :: {slot, gleam@option:option(gmysql:connection()), boolean()}.
-type with_connection_error() :: could_not_checkout.
-spec disconnect(pool()) -> nil.
disconnect(Pool) ->
gleam@otp@actor:send(erlang:element(2, Pool), shutdown).
-spec restart_connection(pool(), gmysql:connection()) -> nil.
restart_connection(Pool, Connection) ->
gleam@otp@actor:send(erlang:element(2, Pool), {restart, Connection}).
-spec restart_all_connections(pool()) -> nil.
restart_all_connections(Pool) ->
gleam@otp@actor:send(erlang:element(2, Pool), restart_all).
-spec new_connection(pool()) -> nil.
new_connection(Pool) ->
gleam@otp@actor:send(erlang:element(2, Pool), start_new).
-spec checkout_connection(pool(), integer()) -> {ok, gmysql:connection()} |
{error, nil}.
checkout_connection(Pool, Timeout) ->
gleam@otp@actor:call(
erlang:element(2, Pool),
fun(Field@0) -> {checkout, Field@0} end,
Timeout
).
-spec checkin_connection(pool(), gmysql:connection()) -> nil.
checkin_connection(Pool, Connection) ->
gleam@otp@actor:send(erlang:element(2, Pool), {checkin, Connection}).
-spec with_connection_loop(
pool(),
integer(),
integer(),
fun((gmysql:connection()) -> HRO)
) -> {ok, HRO} | {error, with_connection_error()}.
with_connection_loop(Pool, Until, Timeout, Function) ->
gleam@bool:guard(
os:system_time(millisecond) > Until,
{error, could_not_checkout},
fun() -> case checkout_connection(Pool, Timeout div 3) of
{ok, Connection} ->
Result = Function(Connection),
checkin_connection(Pool, Connection),
{ok, Result};
{error, _} ->
gleam_erlang_ffi:sleep(Timeout div 6),
with_connection_loop(Pool, Until, Timeout, Function)
end end
).
-spec with_connection(pool(), integer(), fun((gmysql:connection()) -> HRL)) -> {ok,
HRL} |
{error, with_connection_error()}.
with_connection(Pool, Checkout_timeout, Function) ->
Until = os:system_time(millisecond) + Checkout_timeout,
with_connection_loop(Pool, Until, Checkout_timeout, Function).
-spec handle_update_state(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_update_state(Message, State) ->
{update_state, Transform} = case Message of
{update_state, _} -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"handle_update_state"/utf8>>,
line => 141})
end,
New_state = Transform(State),
{continue, New_state, none}.
-spec handle_crashed_connection(gleam@erlang@process:exit_message()) -> message().
handle_crashed_connection(Exit_message) ->
{exit_message, Pid, Reason} = Exit_message,
Connection = gmysql_ffi:from_pid(Pid),
case Reason of
normal ->
{update_state,
fun(State) ->
erlang:setelement(
2,
State,
gleam@list:filter(
erlang:element(2, State),
fun(Slot) ->
erlang:element(2, Slot) =:= {some, Connection}
end
)
)
end};
_ ->
{update_state,
fun(State@1) ->
erlang:setelement(
2,
State@1,
gleam@list:map(
erlang:element(2, State@1),
fun(Slot@1) ->
case erlang:element(2, Slot@1) =:= {some,
Connection} of
true ->
erlang:setelement(2, Slot@1, none);
false ->
Slot@1
end
end
)
)
end}
end.
-spec handle_init(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_init(Message, State) ->
{initialize, Self} = case Message of
{initialize, _} -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"handle_init"/utf8>>,
line => 177})
end,
gleam_erlang_ffi:trap_exits(true),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@1 = gleam@erlang@process:selecting(
_pipe,
Self,
fun gleam@function:identity/1
),
gleam@erlang@process:selecting_trapped_exits(
_pipe@1,
fun handle_crashed_connection/1
)
end,
gleam@erlang@process:send(Self, {tick, Self}),
{continue, State, {some, Selector}}.
-spec handle_shutdown(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_shutdown(_, State) ->
gleam@list:each(
erlang:element(2, State),
fun(Slot) -> case erlang:element(2, Slot) of
none ->
nil;
{some, Conn} ->
gmysql_ffi:close(Conn)
end end
),
{stop, normal}.
-spec handle_restart(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_restart(Message, State) ->
{restart, Conn} = case Message of
{restart, _} -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"handle_restart"/utf8>>,
line => 202})
end,
Pid = gmysql_ffi:to_pid(Conn),
gleam@erlang@process:kill(Pid),
gleam@otp@actor:continue(State).
-spec handle_restart_all(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_restart_all(_, State) ->
gleam@list:each(
erlang:element(2, State),
fun(Slot) -> case erlang:element(2, Slot) of
none ->
nil;
{some, Conn} ->
_pipe = gmysql_ffi:to_pid(Conn),
gleam@erlang@process:kill(_pipe)
end end
),
gleam@otp@actor:continue(State).
-spec handle_start_new(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_start_new(_, State) ->
gleam@otp@actor:continue(
erlang:setelement(
2,
State,
[{slot, none, false} | erlang:element(2, State)]
)
).
-spec handle_checkout(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_checkout(Message, State) ->
{checkout, Client} = case Message of
{checkout, _} -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"handle_checkout"/utf8>>,
line => 230})
end,
{Connection@1, Slots} = gleam@list:map_fold(
erlang:element(2, State),
none,
fun(Connection, Slot) -> case {Connection, Slot} of
{{some, _}, _} ->
{Connection, Slot};
{none, {slot, {some, Conn}, false}} ->
{{some, Conn}, erlang:setelement(3, Slot, true)};
{_, _} ->
{Connection, Slot}
end end
),
_pipe = Connection@1,
_pipe@1 = gleam@option:to_result(_pipe, nil),
gleam@erlang@process:send(Client, _pipe@1),
gleam@otp@actor:continue(erlang:setelement(2, State, Slots)).
-spec handle_checkin(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_checkin(Message, State) ->
{checkin, Connection} = case Message of
{checkin, _} -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"handle_checkin"/utf8>>,
line => 250})
end,
gleam@otp@actor:continue(
erlang:setelement(
2,
State,
gleam@list:map(
erlang:element(2, State),
fun(Slot) ->
case erlang:element(2, Slot) =:= {some, Connection} of
true ->
erlang:setelement(3, Slot, false);
false ->
Slot
end
end
)
)
).
-spec handle_tick(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_tick(Message, State) ->
{tick, Self} = case Message of
{tick, _} -> Message;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"handle_tick"/utf8>>,
line => 266})
end,
{Rate_limiter, Slots} = gleam@list:map_fold(
erlang:element(2, State),
erlang:element(4, State),
fun(Rate_limit, Slot) ->
case {gleam@otp@intensity_tracker:add_event(Rate_limit), Slot} of
{{ok, Limiter}, {slot, none, _}} ->
case gmysql:connect(erlang:element(3, State)) of
{ok, Connection} ->
{Limiter, {slot, {some, Connection}, false}};
{error, _} ->
{Limiter, Slot}
end;
{_, {slot, none, _}} ->
{Rate_limit, erlang:setelement(3, Slot, false)};
{_, Slot@1} ->
{Rate_limit, Slot@1}
end
end
),
gleam@erlang@process:send_after(Self, 250, {tick, Self}),
gleam@otp@actor:continue(
erlang:setelement(4, erlang:setelement(2, State, Slots), Rate_limiter)
).
-spec handle_message(message(), state()) -> gleam@otp@actor:next(message(), state()).
handle_message(Message, State) ->
case Message of
{update_state, _} ->
handle_update_state(Message, State);
{initialize, _} ->
handle_init(Message, State);
shutdown ->
handle_shutdown(Message, State);
{restart, _} ->
handle_restart(Message, State);
restart_all ->
handle_restart_all(Message, State);
start_new ->
handle_start_new(Message, State);
{checkout, _} ->
handle_checkout(Message, State);
{checkin, _} ->
handle_checkin(Message, State);
{tick, _} ->
handle_tick(Message, State)
end.
-spec connect(gmysql:config(), integer(), integer()) -> pool().
connect(Config, Count, Max_connections_per_second) ->
Slots = begin
_pipe = gleam@iterator:repeat({slot, none, false}),
_pipe@1 = gleam@iterator:take(_pipe, Count),
gleam@iterator:to_list(_pipe@1)
end,
_assert_subject = gleam@otp@actor:start(
{state,
Slots,
Config,
gleam@otp@intensity_tracker:new(Max_connections_per_second, 1000)},
fun handle_message/2
),
{ok, Actor} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gmysql/pool"/utf8>>,
function => <<"connect"/utf8>>,
line => 46})
end,
gleam@erlang@process:send(Actor, {initialize, Actor}),
{pool, Actor}.