From 60e996ba172eba657ddfadba0d0a3030156d46e2 Mon Sep 17 00:00:00 2001 From: Louis-Philippe Gauthier Date: Sat, 15 Aug 2026 16:11:33 -0400 Subject: [PATCH 1/3] Trim per-request overhead in the server loop Keep the in-flight request queue in the server state as a map instead of a shared ETS table; each request paid two ETS copies plus table lock traffic for data only its own process touches. Reply messages now carry just the request id instead of the whole cast record, and telemetry can be disabled with the shackle 'telemetry' app env, skipping the handler-table lookup on every event. Benchmarked at 64 callers / pool of 16 against the arithmetic test server: 156-158k -> 167-168k requests/s (+7%). --- src/shackle.erl | 4 +- src/shackle_app.erl | 2 + src/shackle_pool.erl | 2 - src/shackle_queue.erl | 80 --------------------------------------- src/shackle_server.erl | 76 +++++++++++++++++-------------------- src/shackle_telemetry.erl | 53 ++++++++++---------------- 6 files changed, 59 insertions(+), 158 deletions(-) delete mode 100644 src/shackle_queue.erl diff --git a/src/shackle.erl b/src/shackle.erl index 442f1586..1ddffe5d 100644 --- a/src/shackle.erl +++ b/src/shackle.erl @@ -103,7 +103,7 @@ cast(PoolName, Request, Pid, Timeout) -> receive_response(RequestId) -> receive - {#cast {request_id = RequestId}, Reply} -> + {shackle_reply, RequestId, Reply} -> Reply end. @@ -112,7 +112,7 @@ receive_response(RequestId) -> receive_response(RequestId, Timeout) -> receive - {#cast {request_id = RequestId}, Reply} -> + {shackle_reply, RequestId, Reply} -> Reply after Timeout -> {error, timeout} diff --git a/src/shackle_app.erl b/src/shackle_app.erl index bffca7fa..4fe136fa 100644 --- a/src/shackle_app.erl +++ b/src/shackle_app.erl @@ -31,6 +31,8 @@ stop() -> {ok, pid()}. start(_StartType, _StartArgs) -> + persistent_term:put({shackle, telemetry}, + application:get_env(?APP, telemetry, true)), shackle_sup:start_link(). -spec stop(term()) -> diff --git a/src/shackle_pool.erl b/src/shackle_pool.erl index c4c864b0..5a48d879 100644 --- a/src/shackle_pool.erl +++ b/src/shackle_pool.erl @@ -120,7 +120,6 @@ terminate() -> %% private cleanup(Name, OptionsRec) -> shackle_backlog:delete(Name), - shackle_queue:delete(Name), shackle_status:delete(Name), cleanup_ets(Name, OptionsRec), cleanup_foil(Name, OptionsRec). @@ -201,7 +200,6 @@ server_id(Name, PoolSize, round_robin) -> setup(Name, #pool_options {pool_size = PoolSize} = OptionsRec) -> shackle_backlog:new(Name), - shackle_queue:new(Name), shackle_status:new(Name, PoolSize), setup_ets(Name, OptionsRec), setup_foil(Name, OptionsRec). diff --git a/src/shackle_queue.erl b/src/shackle_queue.erl deleted file mode 100644 index ba5791ed..00000000 --- a/src/shackle_queue.erl +++ /dev/null @@ -1,80 +0,0 @@ --module(shackle_queue). - --compile(inline). --compile({inline_size, 512}). - -%% internal --export([ - add/5, - clear/2, - delete/1, - new/1, - remove/3, - table_name/1 -]). - -%% internal --spec add(shackle:table(), shackle_server:id(), shackle:external_request_id(), shackle:cast(), reference()) -> - ok. - -add(Table, ServerId, ExtRequestId, Cast, TimerRef) -> - Object = {{ServerId, ExtRequestId}, {Cast, TimerRef}}, - ets:insert(Table, Object), - ok. - --spec clear(shackle:table(), shackle_server:id()) -> - [{shackle:cast(), reference()}]. - -clear(Table, ServerId) -> - Match = {{ServerId, '_'}, '_'}, - case ets_match_take(Table, Match) of - [] -> - []; - Objects -> - [{Cast, TimerRef} || {_, {Cast, TimerRef}} <- Objects] - end. - --spec delete(shackle_pool:name()) -> - ok. - -delete(PoolName) -> - ets:delete(table_name(PoolName)), - ok. - --spec new(shackle_pool:name()) -> - ok. - -new(PoolName) -> - Table = ets:new(table_name(PoolName), shackle_utils:ets_options()), - ets:give_away(Table, whereis(shackle_ets_manager), undefined), - ok. - --spec remove(shackle:table(), shackle_server:id(), shackle:external_request_id()) -> - {ok, shackle:cast(), reference()} | {error, not_found}. - -remove(Table, ServerId, ExtRequestId) -> - case ets_take(Table, {ServerId, ExtRequestId}) of - [] -> - {error, not_found}; - [{_, {Cast, TimerRef}}] -> - {ok, Cast, TimerRef} - end. - -%% private -ets_match_take(Table, Match) -> - case ets:match_object(Table, Match) of - [] -> - []; - Objects -> - ets:match_delete(Table, Match), - Objects - end. - -ets_take(Table, Key) -> - ets:take(Table, Key). - --spec table_name(shackle_pool:name()) -> - shackle:table(). - -table_name(PoolName) -> - list_to_atom("shackle_queue_" ++ atom_to_list(PoolName)). diff --git a/src/shackle_server.erl b/src/shackle_server.erl index 0432c4f7..e22671be 100644 --- a/src/shackle_server.erl +++ b/src/shackle_server.erl @@ -26,7 +26,8 @@ pool_name :: shackle_pool:name(), port :: shackle:inet_port(), protocol :: shackle:protocol(), - queue :: shackle:table(), + queue = #{} :: #{shackle:external_request_id() => + {shackle:cast(), reference()}}, reconnect_state :: undefined | reconnect_state(), socket :: undefined | shackle:socket(), socket_options :: shackle:socket_options(), @@ -86,7 +87,6 @@ init(Name, Parent, Opts) -> pool_name = PoolName, port = Port, protocol = Protocol, - queue = shackle_queue:table_name(PoolName), reconnect_state = ReconnectState, socket_options = SocketOptions }, undefined}}. @@ -104,7 +104,6 @@ handle_msg({Request, #cast { timeout = Timeout } = Cast}, {#state { client = Client, - id = Id, pool_name = PoolName, protocol = Protocol, queue = Queue, @@ -118,14 +117,15 @@ handle_msg({Request, #cast { shackle_telemetry:send(Client, iolist_size(Data)), case ExtRequestId of undefined -> - reply(ok, Cast, State); + reply(ok, Cast, State), + {ok, {State, ClientState2}}; _ -> Msg = {timeout, ExtRequestId}, TimerRef = erlang:send_after(Timeout, self(), Msg), - shackle_queue:add(Queue, Id, ExtRequestId, Cast, - TimerRef) - end, - {ok, {State, ClientState2}}; + Queue2 = maps:put(ExtRequestId, {Cast, TimerRef}, + Queue), + {ok, {State#state {queue = Queue2}, ClientState2}} + end; {error, Reason} -> ?WARN(PoolName, "send error: ~p", [Reason]), Protocol:close(Socket), @@ -187,7 +187,6 @@ handle_msg(?MSG_CONNECT, {#state { end; handle_msg({timeout, ExtRequestId}, {#state { client = Client, - id = Id, pool_name = PoolName, protocol = Protocol, queue = Queue, @@ -199,8 +198,8 @@ handle_msg({timeout, ExtRequestId}, {#state { try Client:handle_timeout(ExtRequestId, ClientState) of {ok, Reply, ClientState2} -> shackle_telemetry:handle_timeout(Client), - process_responses([Reply], State), - {ok, {State, ClientState2}}; + State2 = process_responses([Reply], State), + {ok, {State2, ClientState2}}; {error, Reason, ClientState2} -> ?WARN(PoolName, "handle_timeout error: ~p", [Reason]), Protocol:close(Socket), @@ -213,14 +212,14 @@ handle_msg({timeout, ExtRequestId}, {#state { close(State, ClientState) end; false -> - case shackle_queue:remove(Queue, Id, ExtRequestId) of - {ok, Cast, _TimerRef} -> + case maps:take(ExtRequestId, Queue) of + {{Cast, _TimerRef}, Queue2} -> shackle_telemetry:timeout(Client), - reply({error, timeout}, Cast, State); - {error, not_found} -> - ok - end, - {ok, {State, ClientState}} + reply({error, timeout}, Cast, State), + {ok, {State#state {queue = Queue2}, ClientState}}; + error -> + {ok, {State, ClientState}} + end end; handle_msg(Msg, {#state { pool_name = PoolName @@ -303,8 +302,8 @@ client_setup(Client, PoolName, Protocol, Socket, ClientState) -> close(#state {id = Id} = State, ClientState) -> shackle_status:disable(Id), - reply_all({error, socket_closed}, State), - reconnect(State, ClientState). + State2 = reply_all({error, socket_closed}, State), + reconnect(State2, ClientState). connect(Protocol, Address, Port, SocketOptions, PoolName) -> case inet:getaddrs(Address, inet) of @@ -342,8 +341,8 @@ handle_msg_data(Socket, Data, #state { shackle_telemetry:recv(Client, size(Data)), try Client:handle_data(Data, ClientState) of {ok, Replies, ClientState2} -> - process_responses(Replies, State), - {ok, {State, ClientState2}}; + State2 = process_responses(Replies, State), + {ok, {State2, ClientState2}}; {error, Reason, ClientState2} -> ?WARN(PoolName, "handle_data error: ~p", [Reason]), Protocol:close(Socket), @@ -370,27 +369,26 @@ handle_msg_error(Socket, Reason, #state { handle_msg_error(_Socket, _Reason, State, ClientState) -> {ok, {State, ClientState}}. -process_responses([], _State) -> - ok; +process_responses([], State) -> + State; process_responses([{ExtRequestId, Reply} | T], #state { client = Client, - id = Id, queue = Queue } = State) -> shackle_telemetry:replies(Client), - case shackle_queue:remove(Queue, Id, ExtRequestId) of - {ok, #cast {timestamp = Timestamp} = Cast, TimerRef} -> + case maps:take(ExtRequestId, Queue) of + {{#cast {timestamp = Timestamp} = Cast, TimerRef}, Queue2} -> shackle_telemetry:found(Client), Diff = erlang:monotonic_time(microsecond) - Timestamp, shackle_telemetry:reply(Client, Diff), erlang:cancel_timer(TimerRef), - reply(Reply, Cast, State); - {error, not_found} -> + reply(Reply, Cast, State), + process_responses(T, State#state {queue = Queue2}); + error -> shackle_telemetry:not_found(Client), - ok - end, - process_responses(T, State). + process_responses(T, State) + end. reconnect(State, undefined) -> reconnect_timer(State, undefined); @@ -459,22 +457,18 @@ reply(_Reply, #cast {pid = undefined}, #state { shackle_backlog:decrement(Backlog, Id), ok; -reply(Reply, #cast {pid = Pid} = Cast, #state { +reply(Reply, #cast {pid = Pid, request_id = RequestId}, #state { backlog = Backlog, id = Id }) -> shackle_backlog:decrement(Backlog, Id), - Pid ! {Cast, Reply}, + Pid ! {shackle_reply, RequestId, Reply}, ok. -reply_all(Reply, #state { - id = Id, - queue = Queue - } = State) -> - - Requests = shackle_queue:clear(Queue, Id), - reply_all(Reply, Requests, State). +reply_all(Reply, #state {queue = Queue} = State) -> + reply_all(Reply, maps:values(Queue), State), + State#state {queue = #{}}. reply_all(_Reply, [], _State) -> ok; diff --git a/src/shackle_telemetry.erl b/src/shackle_telemetry.erl index eee1ffc9..bca0e0d8 100644 --- a/src/shackle_telemetry.erl +++ b/src/shackle_telemetry.erl @@ -19,66 +19,53 @@ -spec backlog_full(shackle:client()) -> ok. backlog_full(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, backlog_full], Measurements, Metadata). + execute([shackle, backlog_full], #{count => 1}, Client). -spec disabled(shackle:client()) -> ok. disabled(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, disabled], Measurements, Metadata). + execute([shackle, disabled], #{count => 1}, Client). -spec found(shackle:client()) -> ok. found(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, found], Measurements, Metadata). + execute([shackle, found], #{count => 1}, Client). -spec handle_timeout(shackle:client()) -> ok. handle_timeout(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, handle_timeout], Measurements, Metadata). + execute([shackle, handle_timeout], #{count => 1}, Client). -spec no_server(shackle:client()) -> ok. no_server(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, no_server], Measurements, Metadata). + execute([shackle, no_server], #{count => 1}, Client). -spec not_found(shackle:client()) -> ok. not_found(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, not_found], Measurements, Metadata). + execute([shackle, not_found], #{count => 1}, Client). -spec recv(shackle:client(), non_neg_integer()) -> ok. recv(Client, NBytes) -> - Measurements = #{count => 1, bytes => NBytes}, - Metadata = #{client => Client}, - telemetry:execute([shackle, recv], Measurements, Metadata). + execute([shackle, recv], #{count => 1, bytes => NBytes}, Client). -spec replies(shackle:client()) -> ok. replies(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, replies], Measurements, Metadata). + execute([shackle, replies], #{count => 1}, Client). -spec reply(shackle:client(), non_neg_integer()) -> ok. reply(Client, Microseconds) -> - Measurements = #{duration => Microseconds}, - Metadata = #{client => Client}, - telemetry:execute([shackle, reply], Measurements, Metadata). + execute([shackle, reply], #{duration => Microseconds}, Client). -spec send(shackle:client(), non_neg_integer()) -> ok. send(Client, NBytes) -> - Measurements = #{count => 1, bytes => NBytes}, - Metadata = #{client => Client}, - telemetry:execute([shackle, send], Measurements, Metadata). + execute([shackle, send], #{count => 1, bytes => NBytes}, Client). -spec timeout(shackle:client()) -> ok. timeout(Client) -> - Measurements = #{count => 1}, - Metadata = #{client => Client}, - telemetry:execute([shackle, timeout], Measurements, Metadata). + execute([shackle, timeout], #{count => 1}, Client). + +%% private +execute(Event, Measurements, Client) -> + case persistent_term:get({shackle, telemetry}, true) of + true -> + telemetry:execute(Event, Measurements, #{client => Client}); + false -> + ok + end. From 77615c08f172c87fbd81b1983d768a711cc95917 Mon Sep 17 00:00:00 2001 From: Louis-Philippe Gauthier Date: Sat, 15 Aug 2026 16:17:38 -0400 Subject: [PATCH 2/3] Add shackle_socket, a socket NIF based protocol An alternative to shackle_tcp built on the socket module. Accepted sockets run with {otp, select_read}, so the server gets one $socket message plus one recv per wake instead of the inet driver's active mode delivery, and send is a single NIF call instead of a port command with its monitor and inet_reply round trip. Benchmarked at 64 callers / pool of 16 against the arithmetic test server: 168-169k requests/s with shackle_tcp, 172-174k with shackle_socket (+2.5%). Requires OTP 27.3 for select_read; the default protocol is unchanged. --- src/shackle.erl | 4 +- src/shackle_server.erl | 18 ++++ src/shackle_socket.erl | 142 ++++++++++++++++++++++++++++++ test/arithmetic_socket_client.erl | 101 +++++++++++++++++++++ 4 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 src/shackle_socket.erl create mode 100644 test/arithmetic_socket_client.erl diff --git a/src/shackle.erl b/src/shackle.erl index 1ddffe5d..ad3abf51 100644 --- a/src/shackle.erl +++ b/src/shackle.erl @@ -22,10 +22,10 @@ -type external_request_id() :: term(). -type inet_address() :: inet:ip_address() | inet:hostname(). -type inet_port() :: inet:port_number(). --type protocol() :: shackle_ssl| shackle_tcp | shackle_udp. +-type protocol() :: shackle_socket | shackle_ssl | shackle_tcp | shackle_udp. -type request_id() :: {shackle_server:name(), reference()}. -type response() :: {external_request_id(), term()}. --type socket() :: inet:socket() | ssl:sslsocket(). +-type socket() :: inet:socket() | socket:socket() | ssl:sslsocket(). -type socket_option() :: gen_tcp:connect_option() | gen_udp:option() | ssl:tls_client_option(). -type socket_options() :: [socket_option()]. -type table() :: atom(). diff --git a/src/shackle_server.erl b/src/shackle_server.erl index e22671be..fceb5f55 100644 --- a/src/shackle_server.erl +++ b/src/shackle_server.erl @@ -139,6 +139,24 @@ handle_msg({Request, #cast { reply({error, client_crash}, Cast, State), {ok, {State, ClientState}} end; +handle_msg({'$socket', Socket, select, _Handle}, {#state { + socket = Socket + } = State, ClientState}) -> + + case shackle_socket:recv(Socket) of + {ok, Data} -> + handle_msg_data(Socket, Data, State, ClientState); + wait -> + {ok, {State, ClientState}}; + {error, closed} -> + handle_msg_close(Socket, State, ClientState); + {error, Reason} -> + handle_msg_error(Socket, Reason, State, ClientState) + end; +handle_msg({'$socket', _Socket, select, _Handle}, {State, ClientState}) -> + {ok, {State, ClientState}}; +handle_msg({'$socket', Socket, abort, _Info}, {State, ClientState}) -> + handle_msg_close(Socket, State, ClientState); handle_msg({ssl, Socket, Data}, {State, ClientState}) -> handle_msg_data(Socket, Data, State, ClientState); handle_msg({ssl_closed, Socket}, {State, ClientState}) -> diff --git a/src/shackle_socket.erl b/src/shackle_socket.erl new file mode 100644 index 00000000..1ec8c3a5 --- /dev/null +++ b/src/shackle_socket.erl @@ -0,0 +1,142 @@ +-module(shackle_socket). +-include("shackle_internal.hrl"). + +-compile(inline). +-compile({inline_size, 512}). + +%% The socket specs on OTP < 28 predate {otp, select_read} and its +%% recv return shapes, although both work at runtime from OTP 27.3. +-dialyzer({nowarn_function, [recv/1, setopts/2]}). + +-behavior(shackle_protocol). +-export([ + close/1, + connect/3, + send/2, + setopts/2 +]). + +%% internal +-export([ + recv/1 +]). + +%% callbacks +-spec close(shackle:socket()) -> + ok. + +close(Socket) -> + _ = socket:close(Socket), + ok. + +-spec connect(shackle:inet_address(), shackle:inet_port(), shackle:socket_options()) -> + {ok, shackle:socket()} | {error, atom()}. + +connect(Address, Port, SocketOptions) -> + case socket:open(inet, stream, tcp) of + {ok, Socket} -> + case connect_opts(Socket, SocketOptions) of + ok -> + SockAddr = #{family => inet, addr => Address, port => Port}, + case socket:connect(Socket, SockAddr, + ?DEFAULT_CONNECT_TIMEOUT) of + + ok -> + {ok, Socket}; + {error, Reason} -> + _ = socket:close(Socket), + {error, Reason} + end; + {error, Reason} -> + _ = socket:close(Socket), + {error, Reason} + end; + {error, _} = Error -> + Error + end. + +%% Reads whatever is available; with {otp, select_read} enabled the +%% successful recv re-arms the read select inside the same NIF call. +-spec recv(shackle:socket()) -> + {ok, binary()} | wait | {error, atom()}. + +recv(Socket) -> + case socket:recv(Socket, 0, [], nowait) of + {select_read, {_SelectInfo, Data}} -> + {ok, Data}; + {select, {_SelectInfo, Data}} -> + {ok, Data}; + {select, _SelectInfo} -> + wait; + {ok, Data} -> + %% select_read did not re-arm; force another recv round + self() ! {'$socket', Socket, select, undefined}, + {ok, Data}; + {error, {Reason, _Data}} -> + {error, Reason}; + {error, Reason} -> + {error, Reason} + end. + +-spec send(shackle:socket(), iodata()) -> + ok | {error, atom()}. + +send(Socket, Data) -> + case socket:send(Socket, Data) of + ok -> + ok; + {error, {Reason, _RestData}} -> + {error, Reason}; + {error, Reason} -> + {error, Reason} + end. + +-spec setopts(shackle:socket(), [gen_tcp:option()]) -> + ok | + {error, atom()}. + +setopts(Socket, [{active, false}]) -> + _ = socket:setopt(Socket, {otp, select_read}, false), + ok; +setopts(Socket, [{active, true}]) -> + ok = socket:setopt(Socket, {otp, select_read}, true), + case recv(Socket) of + {ok, Data} -> + %% shackle_server delivers this to the client like any + %% active-mode packet + self() ! {tcp, Socket, Data}, + ok; + wait -> + ok; + {error, _} = Error -> + Error + end; +setopts(Socket, Opts) -> + connect_opts(Socket, Opts). + +%% private +connect_opts(_Socket, []) -> + ok; +connect_opts(Socket, [{nodelay, Bool} | T]) -> + case socket:setopt(Socket, {tcp, nodelay}, Bool) of + ok -> + connect_opts(Socket, T); + {error, _} = Error -> + Error + end; +connect_opts(Socket, [{recbuf, Size} | T]) -> + case socket:setopt(Socket, {socket, rcvbuf}, Size) of + ok -> + connect_opts(Socket, T); + {error, _} = Error -> + Error + end; +connect_opts(Socket, [{sndbuf, Size} | T]) -> + case socket:setopt(Socket, {socket, sndbuf}, Size) of + ok -> + connect_opts(Socket, T); + {error, _} = Error -> + Error + end; +connect_opts(Socket, [_ | T]) -> + connect_opts(Socket, T). diff --git a/test/arithmetic_socket_client.erl b/test/arithmetic_socket_client.erl new file mode 100644 index 00000000..5f1fe0cc --- /dev/null +++ b/test/arithmetic_socket_client.erl @@ -0,0 +1,101 @@ +-module(arithmetic_socket_client). +-include("test.hrl"). + +-export([ + add/2, + start/0, + start/1, + stop/0 +]). + +-behavior(shackle_client). +-export([ + init/1, + setup/2, + handle_request/2, + handle_data/2, + terminate/1 +]). + +-record(state, { + buffer = <<>>, + request_counter = 0 +}). + +-type tiny_int() :: 0..255. + +%% public +-spec add(tiny_int(), tiny_int()) -> + pos_integer(). + +add(A, B) -> + shackle:call(?POOL_NAME, {add, A, B}, ?TIMEOUT). + +-spec start() -> + ok | {error, shackle_not_started | pool_already_started}. + +start() -> + start([ + {backlog_size, ?BACKLOG_SIZE}, + {pool_size, 1} + ]). + +-spec start(shackle_pool:options()) -> + ok | {error, shackle_not_started | pool_already_started}. + +start(PoolOptions) -> + shackle_pool:start(?POOL_NAME, ?MODULE, [ + {port, ?PORT}, + {protocol, shackle_socket}, + {reconnect, true}, + {reconnect_time_min, 1}, + {socket_options, []} + ], PoolOptions). + +-spec stop() -> + ok | {error, pool_not_started}. + +stop() -> + shackle_pool:stop(?POOL_NAME). + +%% shackle_server callbacks +init(_) -> + {ok, #state {}}. + +setup(Socket, State) -> + case socket:send(Socket, <<"INIT">>) of + ok -> + case socket:recv(Socket, 0, ?TIMEOUT) of + {ok, <<"OK">>} -> + {ok, State}; + {error, Reason} -> + {error, Reason, State} + end; + {error, Reason} -> + {error, Reason, State} + end. + +handle_data(Data, #state { + buffer = Buffer + } = State) -> + + Data2 = <>, + {Replies, Buffer2} = arithmetic_protocol:parse_replies(Data2), + + {ok, Replies, State#state { + buffer = Buffer2 + }}. + +handle_request({Operation, A, B}, #state { + request_counter = RequestCounter + } = State) -> + + RequestId = arithmetic_protocol:request_id(RequestCounter), + Data = arithmetic_protocol:request(RequestId, Operation, A, B), + + {ok, RequestId, Data, State#state { + request_counter = RequestCounter + 1 + }}. + +terminate(_State) -> + ok. From 93e29acac58e45f2e49a6e64f4e753018f2e010b Mon Sep 17 00:00:00 2001 From: Louis-Philippe Gauthier Date: Sat, 15 Aug 2026 16:26:56 -0400 Subject: [PATCH 3/3] Remove inline compiler pragmas With the JIT and module-wide type propagation these no longer have a measurable effect: end-to-end benchmarks are within noise with and without them, while the beams get larger and stack traces lose inlined frames. --- src/shackle.erl | 3 --- src/shackle_backlog.erl | 3 --- src/shackle_server.erl | 3 --- src/shackle_socket.erl | 3 --- src/shackle_status.erl | 3 --- src/shackle_telemetry.erl | 3 --- src/shackle_utils.erl | 3 --- 7 files changed, 21 deletions(-) diff --git a/src/shackle.erl b/src/shackle.erl index ad3abf51..5397e876 100644 --- a/src/shackle.erl +++ b/src/shackle.erl @@ -1,9 +1,6 @@ -module(shackle). -include("shackle_internal.hrl"). --compile(inline). --compile({inline_size, 512}). - %% public -export([ call/2, diff --git a/src/shackle_backlog.erl b/src/shackle_backlog.erl index 0e617f93..cef5db0d 100644 --- a/src/shackle_backlog.erl +++ b/src/shackle_backlog.erl @@ -1,8 +1,5 @@ -module(shackle_backlog). --compile(inline). --compile({inline_size, 512}). - %% internal -export([ check/3, diff --git a/src/shackle_server.erl b/src/shackle_server.erl index fceb5f55..f2dc934f 100644 --- a/src/shackle_server.erl +++ b/src/shackle_server.erl @@ -1,9 +1,6 @@ -module(shackle_server). -include("shackle_internal.hrl"). --compile(inline). --compile({inline_size, 512}). - -export([ start_link/2 ]). diff --git a/src/shackle_socket.erl b/src/shackle_socket.erl index 1ec8c3a5..12a33667 100644 --- a/src/shackle_socket.erl +++ b/src/shackle_socket.erl @@ -1,9 +1,6 @@ -module(shackle_socket). -include("shackle_internal.hrl"). --compile(inline). --compile({inline_size, 512}). - %% The socket specs on OTP < 28 predate {otp, select_read} and its %% recv return shapes, although both work at runtime from OTP 27.3. -dialyzer({nowarn_function, [recv/1, setopts/2]}). diff --git a/src/shackle_status.erl b/src/shackle_status.erl index ff9c20b0..cb7395b7 100644 --- a/src/shackle_status.erl +++ b/src/shackle_status.erl @@ -1,9 +1,6 @@ -module(shackle_status). -include("shackle_internal.hrl"). --compile(inline). --compile({inline_size, 512}). - -export([ active/1, delete/1, diff --git a/src/shackle_telemetry.erl b/src/shackle_telemetry.erl index bca0e0d8..76a2b1e1 100644 --- a/src/shackle_telemetry.erl +++ b/src/shackle_telemetry.erl @@ -1,8 +1,5 @@ -module(shackle_telemetry). --compile(inline). --compile({inline_size, 512}). - -export([ backlog_full/1, disabled/1, diff --git a/src/shackle_utils.erl b/src/shackle_utils.erl index 5043182c..279f07b4 100644 --- a/src/shackle_utils.erl +++ b/src/shackle_utils.erl @@ -1,8 +1,5 @@ -module(shackle_utils). --compile(inline). --compile({inline_size, 512}). - %% public -export([ ets_options/0,