Skip to main content

src/roadrunner_acceptor.erl

-module(roadrunner_acceptor).
-moduledoc false.

%% Acceptor process — spins on `gen_tcp:accept/1` for a listen socket
%% and hands each accepted connection off to a `roadrunner_conn` worker.
%%
%% Spawn-linked to the owning `roadrunner_listener`: a listener stop closes
%% the listen socket, the acceptor's `accept/1` returns `{error, _}`,
%% and the acceptor exits cleanly. Unrelated acceptor crashes propagate
%% back via the link, taking the listener down for supervisor restart.
%% Connection workers are spawned **without** a link so that a crash
%% in one connection does not bring down the acceptor.

-export([start_link/3]).

-doc """
Spawn-link an acceptor process bound to `LSocket` with the given
`ProtoOpts` (handler + body limits) and a 1-based pool index. Each
accepted socket is handed to a `roadrunner_conn` worker that consumes
the same opts. The index is used in the `proc_lib` label so
`observer` distinguishes `{roadrunner_acceptor, ListenerName, 1}`,
`{..., 2}`, etc., per listener.
""".
-spec start_link(roadrunner_transport:socket(), roadrunner_conn:proto_opts(), pos_integer()) ->
    {ok, pid()}.
start_link(LSocket, ProtoOpts, Index) ->
    ListenerName = maps:get(listener_name, ProtoOpts, undefined),
    Pid = proc_lib:spawn_link(fun() ->
        proc_lib:set_label({roadrunner_acceptor, ListenerName, Index}),
        loop(LSocket, ProtoOpts)
    end),
    {ok, Pid}.

-spec loop(roadrunner_transport:socket(), roadrunner_conn:proto_opts()) -> ok.
loop(LSocket, ProtoOpts) ->
    case roadrunner_transport:accept(LSocket) of
        {ok, Socket} ->
            handle_accepted(Socket, ProtoOpts),
            loop(LSocket, ProtoOpts);
        {error, _} ->
            %% Listen socket was closed (or another transport error) —
            %% terminate cleanly; the linked listener will tear us down.
            ok
    end.

-spec handle_accepted(roadrunner_transport:socket(), roadrunner_conn:proto_opts()) -> ok.
handle_accepted(Socket, ProtoOpts) ->
    case roadrunner_conn:try_acquire_slot(ProtoOpts) of
        true ->
            {ok, ConnPid} = roadrunner_conn:start(Socket, ProtoOpts),
            ok = roadrunner_transport:controlling_process(Socket, ConnPid),
            ConnPid ! shoot,
            ok;
        false ->
            %% Over max_clients — drop the new connection on the floor.
            _ = roadrunner_transport:close(Socket),
            ok
    end.