Current section

Files

Jump to
erlang_h1 src h1_acceptor.erl
Raw

src/h1_acceptor.erl

%% Copyright (c) 2026 Benoit Chesneau.
%% SPDX-License-Identifier: Apache-2.0
%%
%% @doc HTTP/1.1 acceptor loop.
%%
%% One lightweight process per acceptor. Blocks on accept against a
%% shared listen socket, spawns a server loop for each accepted
%% connection, and transfers socket ownership to it.
%%
%% The TLS handshake runs inside the server-loop process (not here) so
%% one slow handshake never blocks the accept queue.
-module(h1_acceptor).
-export([start_link/1]).
-export([loop/1]).
-define(ACCEPT_BACKOFF, 10).
-type transport() :: gen_tcp | ssl.
-type args() :: #{transport := transport(),
listen_socket := term(),
handler := term(),
conn_opts := map(),
server_opts := map()}.
-spec start_link(args()) -> {ok, pid()}.
start_link(Args) ->
Pid = proc_lib:spawn_link(?MODULE, loop, [Args]),
{ok, Pid}.
-spec loop(args()) -> no_return().
loop(#{transport := Transport, listen_socket := ListenSocket} = Args) ->
case accept(Transport, ListenSocket) of
{ok, Socket} ->
_ = spawn_connection(Socket, Args),
loop(Args);
{error, closed} ->
exit(normal);
{error, E} when E =:= emfile; E =:= enfile ->
timer:sleep(?ACCEPT_BACKOFF),
loop(Args);
{error, _Reason} ->
%% Unknown transient error — back off briefly so a persistent
%% failure can't spin this process at 100% CPU.
timer:sleep(?ACCEPT_BACKOFF),
loop(Args)
end.
accept(gen_tcp, ListenSocket) ->
gen_tcp:accept(ListenSocket, infinity);
accept(ssl, ListenSocket) ->
ssl:transport_accept(ListenSocket, infinity).
spawn_connection(Socket, #{transport := Transport,
handler := Handler,
conn_opts := ConnOpts,
server_opts := ServerOpts}) ->
Parent = self(),
Pid = erlang:spawn(fun() ->
h1_server:init_accepted(Parent, Socket, Transport,
Handler, ConnOpts, ServerOpts)
end),
case controlling_process(Transport, Socket, Pid) of
ok ->
Pid ! {h1_acceptor, socket_ready},
Pid;
{error, _Reason} ->
Pid ! {h1_acceptor, transfer_failed},
_ = close(Transport, Socket),
Pid
end.
controlling_process(gen_tcp, Socket, Pid) ->
gen_tcp:controlling_process(Socket, Pid);
controlling_process(ssl, Socket, Pid) ->
ssl:controlling_process(Socket, Pid).
close(gen_tcp, Socket) -> gen_tcp:close(Socket);
close(ssl, Socket) -> ssl:close(Socket).