<commit_before>-module(shackle_server).
-include("shackle_internal.hrl").

-compile(inline).
-compile({inline_size, 512}).

%% internal
-export([
    init/5,
    start_link/4
]).

%% sys behavior
-export([
    system_code_change/4,
    system_continue/3,
    system_get_state/1,
    system_terminate/4
]).

-record(state, {
    client           :: client(),
    header           :: undefined | iodata(),
    ip               :: inet:ip_address() | inet:hostname(),
    name             :: server_name(),
    parent           :: pid(),
    pool_name        :: pool_name(),
    port             :: inet:port_number(),
    protocol         :: protocol(),
    reconnect_state  :: undefined | reconnect_state(),
    socket           :: undefined | inet:socket(),
    socket_options   :: [gen_tcp:connect_option() | gen_udp:option()],
    timer_ref        :: undefined | reference()
}).

-type client_state() :: term().
-type state() :: #state {}.

%% public
-spec start_link(server_name(), pool_name(), client(), client_options()) ->
    {ok, pid()}.

start_link(Name, PoolName, Client, ClientOptions) ->
    proc_lib:start_link(?MODULE, init, [Name, PoolName, Client,
        ClientOptions, self()]).

-spec init(server_name(), pool_name(), client(), client_options(), pid()) ->
    no_return().

init(Name, PoolName, Client, ClientOptions, Parent) ->
    process_flag(trap_exit, true),
    proc_lib:init_ack(Parent, {ok, self()}),
    register(Name, self()),

    self() ! ?MSG_CONNECT,
    ok = shackle_backlog:new(Name),

    Ip = ?LOOKUP(ip, ClientOptions, ?DEFAULT_IP),
    Port = ?LOOKUP(port, ClientOptions),
    Protocol = ?LOOKUP(protocol, ClientOptions, ?DEFAULT_PROTOCOL),
    ReconnectState = reconnect_state(ClientOptions),
    SocketOptions = ?LOOKUP(socket_options, ClientOptions,
        ?DEFAULT_SOCKET_OPTS),

    loop(#state {
        client = Client,
        ip = Ip,
        name = Name,
        parent = Parent,
        pool_name = PoolName,
        port = Port,
        protocol = Protocol,
        reconnect_state = ReconnectState,
        socket_options = SocketOptions
    }, undefined).

%% sys callbacks
-spec system_code_change(state(), module(), undefined | term(), term()) ->
    {ok, state()}.

system_code_change(State, _Module, _OldVsn, _Extra) ->
    {ok, State}.

-spec system_continue(pid(), [], {state(), client_state()}) ->
    ok.

system_continue(_Parent, _Debug, {State, ClientState}) ->
    loop(State, ClientState).

-spec system_get_state(state()) ->
    {ok, state()}.

system_get_state(State) ->
    {ok, State}.

-spec system_terminate(term(), pid(), [], state()) ->
    none().

system_terminate(Reason, _Parent, _Debug, _State) ->
    exit(Reason).

%% private
close(#state {name = Name} = State, ClientState) ->
    reply_all(Name, {error, socket_closed}),
    reconnect(State, ClientState).

handle_msg(?MSG_CONNECT, #state {
        client = Client,
        ip = Ip,
        pool_name = PoolName,
        port = Port,
        protocol = Protocol,
        reconnect_state = ReconnectState,
        socket_options = SocketOptions
    } = State, ClientState) ->

    case inet:getaddrs(Ip, inet) of
        {ok, Addrs} ->
            Ip2 = shackle_utils:random_element(Addrs),
            Headers = shackle_udp:header(Ip2, Port),
            case Protocol:new(Ip2, Port, SocketOptions) of
                {ok, Socket} ->
                    {ok, ClientState2} = Client:init(),
                    inet:setopts(Socket, [{active, false}]),

                    case Client:setup(Socket, ClientState2) of
                        {ok, ClientState3} ->
                            inet:setopts(Socket, [{active, true}]),

                            {ok, State#state {
                                header = Headers,
                                reconnect_state =
                                    reconnect_state_reset(ReconnectState),
                                socket = Socket
                            }, ClientState3};
                        {error, Reason, ClientState3} ->
                            shackle_utils:warning_msg(PoolName,
                                "setup error: ~p", [Reason]),

                            reconnect(State, ClientState3)
                    end;
                {error, Reason} ->
                    shackle_utils:warning_msg(PoolName,
                        "~p connect error: ~p", [Protocol, Reason]),
                    reconnect(State, ClientState)
            end;

        {error, Reason} ->
            shackle_utils:warning_msg(PoolName,
                "~p getaddrs error: ~p", [Protocol, Reason]),
            reconnect(State, ClientState)
    end;
handle_msg(#cast {} = Cast, #state {
        socket = undefined,
        name = Name
    } = State, ClientState) ->

    reply(Name, {error, no_socket}, Cast),
    {ok, State, ClientState};
handle_msg(#cast {
        request = Request,
        timeout = Timeout
    } = Cast, #state {
        client = Client,
        header = Header,<commit_msg>Fix setup error socket leak<commit_after>-module(shackle_server).
-include("shackle_internal.hrl").

-compile(inline).
-compile({inline_size, 512}).

%% internal
-export([
    init/5,
    start_link/4
]).

%% sys behavior
-export([
    system_code_change/4,
    system_continue/3,
    system_get_state/1,
    system_terminate/4
]).

-record(state, {
    client           :: client(),
    header           :: undefined | iodata(),
    ip               :: inet:ip_address() | inet:hostname(),
    name             :: server_name(),
    parent           :: pid(),
    pool_name        :: pool_name(),
    port             :: inet:port_number(),
    protocol         :: protocol(),
    reconnect_state  :: undefined | reconnect_state(),
    socket           :: undefined | inet:socket(),
    socket_options   :: [gen_tcp:connect_option() | gen_udp:option()],
    timer_ref        :: undefined | reference()
}).

-type client_state() :: term().
-type state() :: #state {}.

%% public
-spec start_link(server_name(), pool_name(), client(), client_options()) ->
    {ok, pid()}.

start_link(Name, PoolName, Client, ClientOptions) ->
    proc_lib:start_link(?MODULE, init, [Name, PoolName, Client,
        ClientOptions, self()]).

-spec init(server_name(), pool_name(), client(), client_options(), pid()) ->
    no_return().

init(Name, PoolName, Client, ClientOptions, Parent) ->
    process_flag(trap_exit, true),
    proc_lib:init_ack(Parent, {ok, self()}),
    register(Name, self()),

    self() ! ?MSG_CONNECT,
    ok = shackle_backlog:new(Name),

    Ip = ?LOOKUP(ip, ClientOptions, ?DEFAULT_IP),
    Port = ?LOOKUP(port, ClientOptions),
    Protocol = ?LOOKUP(protocol, ClientOptions, ?DEFAULT_PROTOCOL),
    ReconnectState = reconnect_state(ClientOptions),
    SocketOptions = ?LOOKUP(socket_options, ClientOptions,
        ?DEFAULT_SOCKET_OPTS),

    loop(#state {
        client = Client,
        ip = Ip,
        name = Name,
        parent = Parent,
        pool_name = PoolName,
        port = Port,
        protocol = Protocol,
        reconnect_state = ReconnectState,
        socket_options = SocketOptions
    }, undefined).

%% sys callbacks
-spec system_code_change(state(), module(), undefined | term(), term()) ->
    {ok, state()}.

system_code_change(State, _Module, _OldVsn, _Extra) ->
    {ok, State}.

-spec system_continue(pid(), [], {state(), client_state()}) ->
    ok.

system_continue(_Parent, _Debug, {State, ClientState}) ->
    loop(State, ClientState).

-spec system_get_state(state()) ->
    {ok, state()}.

system_get_state(State) ->
    {ok, State}.

-spec system_terminate(term(), pid(), [], state()) ->
    none().

system_terminate(Reason, _Parent, _Debug, _State) ->
    exit(Reason).

%% private
close(#state {name = Name} = State, ClientState) ->
    reply_all(Name, {error, socket_closed}),
    reconnect(State, ClientState).

handle_msg(?MSG_CONNECT, #state {
        client = Client,
        ip = Ip,
        pool_name = PoolName,
        port = Port,
        protocol = Protocol,
        reconnect_state = ReconnectState,
        socket_options = SocketOptions
    } = State, ClientState) ->

    case inet:getaddrs(Ip, inet) of
        {ok, Addrs} ->
            Ip2 = shackle_utils:random_element(Addrs),
            Headers = shackle_udp:header(Ip2, Port),
            case Protocol:new(Ip2, Port, SocketOptions) of
                {ok, Socket} ->
                    {ok, ClientState2} = Client:init(),
                    inet:setopts(Socket, [{active, false}]),

                    case Client:setup(Socket, ClientState2) of
                        {ok, ClientState3} ->
                            inet:setopts(Socket, [{active, true}]),

                            {ok, State#state {
                                header = Headers,
                                reconnect_state =
                                    reconnect_state_reset(ReconnectState),
                                socket = Socket
                            }, ClientState3};
                        {error, Reason, ClientState3} ->
                            shackle_utils:warning_msg(PoolName,
                                "setup error: ~p", [Reason]),
                            Protocol:close(Socket),
                            reconnect(State, ClientState3)
                    end;
                {error, Reason} ->
                    shackle_utils:warning_msg(PoolName,
                        "~p connect error: ~p", [Protocol, Reason]),
                    reconnect(State, ClientState)
            end;

        {error, Reason} ->
            shackle_utils:warning_msg(PoolName,
                "~p getaddrs error: ~p", [Protocol, Reason]),
            reconnect(State, ClientState)
    end;
handle_msg(#cast {} = Cast, #state {
        socket = undefined,
        name = Name
    } = State, ClientState) ->

    reply(Name, {error, no_socket}, Cast),
    {ok, State, ClientState};
handle_msg(#cast {
        request = Request,
        timeout = Timeout
    } = Cast, #state {
        client = Client,
        header = Header,
{'fraction_non_alphanumeric': 0.13448378458792026, 'fraction_numerical': 0.0035703659625111574, 'mean_word_length': 2.0529821374508024, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '624271', 'n_tokens_mistral': 3009, 'n_tokens_neox': 2915, 'n_words': 622}