Skip to content

Instantly share code, notes, and snippets.

@nickva
Last active August 29, 2024 12:24
Show Gist options
  • Save nickva/9762b8dca22af82e12203b71cc254bcc to your computer and use it in GitHub Desktop.
Save nickva/9762b8dca22af82e12203b71cc254bcc to your computer and use it in GitHub Desktop.
Erlang 24 and 25 fix_alloc memory leak
%
% Run on Erlang 24+
%
% (MFatags true is to enable instrumentation for fix_alloc)
%
% $ erl -name [email protected] +MFatags true
% ([email protected])>
%
% $ erl -name [email protected] +MFatags true
% ([email protected])2> c(fixalloc), fixalloc:go('[email protected]').
%
% Leak appears on 24 and 25:
%
% > c(fixalloc), fixalloc:go('[email protected]').
% PidRefHist:{1,0,0,0} AllocSize(MB):0
% PidRefHist:{44658,0,0,0} AllocSize(MB):51
% PidRefHist:{85391,0,0,0} AllocSize(MB):81
% ...
% PidRefHist:{2089865,0,0,0} AllocSize(MB):627
% PidRefHist:{2096789,0,0,0} AllocSize(MB):635
%
% Leak doesn't appear on 26
%
% Eshell V14.0 (press Ctrl+G to abort, type help(). for help)
% ([email protected])1> c(fixalloc), fixalloc:go('[email protected]').
% PidRefHist:unavailable AllocSize(MB):0
% PidRefHist:unavailable AllocSize(MB):81
% PidRefHist:unavailable AllocSize(MB):81
% ...
% PidRefHist:unavailable AllocSize(MB):81
% PidRefHist:unavailable AllocSize(MB):36
% PidRefHist:unavailable AllocSize(MB):78
%
% Script adapted from original version by Lukas Larsson from
% https://groups.google.com/g/erlang-programming/c/sCrWDju0N8c
%
-module(fixalloc).
-export([go/1, go/2, go/3, stop/0, gc/0, stats/1]).
-export([hist/0, fix_alloc_size/0]).
go(RemoteNode) ->
go(RemoteNode, 100).
go(RemoteNode, Cycles) ->
go(RemoteNode, Cycles, 80000).
go(_RemoteNode, 0, _PidCount) ->
stop(),
ok;
go(RemoteNode, Cycles, PidCount) ->
cycle(RemoteNode, PidCount),
timer:sleep(1000),
gc(),
stop(),
go(RemoteNode, Cycles - 1, PidCount).
cycle(RemoteNode, N) when is_atom(RemoteNode), is_integer(N) ->
Payload = <<"x">>,
StatsPid = spawn_link(?MODULE, stats, [RemoteNode]),
Pids = [spawn(RemoteNode, fun
F() ->
receive {From, Msg} ->
From ! {reply, From, erlang:iolist_size(Msg)},
F()
end
end
) || _<- lists:seq(1, N)],
Pinger = fun
F(Pid) ->
Alias = alias([reply]),
Msg = {Alias, Payload},
SendRes = erlang:send(Pid, Msg, [nosuspend]),
case SendRes of
ok ->
receive
{reply, Alias, _IOListSize} -> F(Pid)
end;
nosuspend ->
erlang:send(Pid, Msg),
receive
{reply, Alias, _IOListSize} -> F(Pid)
end
end
end,
Ctx = {StatsPid, [{Pid, spawn(fun() -> Pinger(Pid) end)} || Pid <- Pids]},
put(fixalloc, Ctx),
started.
stop() ->
case erase(fixalloc) of
undefined ->
ok;
{StatsPid, Pids} ->
[begin exit(Pid1, kill), exit(Pid2, kill) end || {Pid1, Pid2} <- Pids],
unlink(StatsPid),
exit(StatsPid, kill)
end.
gc() ->
[erlang:garbage_collect(Pid) || Pid<-processes()],
ok.
stats(RemoteNode) ->
PidRefHist = hist(),
CarrierTot = fix_alloc_size() bsr 20,
io:format("PidRefHist:~p AllocSize(MB):~p~n",[PidRefHist, CarrierTot]),
timer:sleep(10000),
stats(RemoteNode).
fix_alloc_size() ->
Opts = #{allocator_types => [fix_alloc]},
case instrument:carriers(Opts) of
{ok, {_, Carriers}} ->
lists:sum([TSize || {fix_alloc, _, TSize, _, _, _} <- Carriers]);
_Other ->
0
end.
hist() ->
Opts = #{histogram_width => 4},
case instrument:allocations(Opts) of
{ok, {_, _, #{system := #{nsched_pid_ref_entry := H}}}} -> H;
_Other -> unavailable
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment