Skip to content

Instantly share code, notes, and snippets.

@nickva
Created February 14, 2026 20:16
Show Gist options
  • Select an option

  • Save nickva/d03cbf3c6456c5125ca71a1e79197b1c to your computer and use it in GitHub Desktop.

Select an option

Save nickva/d03cbf3c6456c5125ca71a1e79197b1c to your computer and use it in GitHub Desktop.
Erlang/OTP segfault
-module(cwb_bench2).
-export([run/5, status/1, stop/1]).
-export([init/1, terminate/2]).
-export([handle_call/3, handle_cast/2, handle_info/2]).
-behaviour(gen_server).
-define(HIBERNATE_DEFAULT, true).
-record(q, {
queue = queue:new(),
blocked = [],
max_size,
max_items,
items = 0,
size = 0,
worker = undefined,
close_on_dequeue = false,
hibernate = ?HIBERNATE_DEFAULT
}).
new(Options) ->
gen_server:start_link(?MODULE, Options, []).
queue(Wq, Item) when is_binary(Item) ->
gen_server:call(Wq, {queue, Item, byte_size(Item)}, infinity);
queue(Wq, Item) ->
gen_server:call(Wq, {queue, Item, erlang:external_size(Item)}, infinity).
dequeue(Wq) ->
dequeue(Wq, all).
dequeue(Wq, MaxItems) ->
try
gen_server:call(Wq, {dequeue, MaxItems}, infinity)
catch
_:_ -> closed
end.
close(Wq) ->
gen_server:cast(Wq, close).
init(Options) ->
Q = #q{
max_size = get_value(max_size, Options, nil),
max_items = get_value(max_items, Options, nil),
hibernate = get_value(hibernate, Options, ?HIBERNATE_DEFAULT)
},
{ok, Q}.
terminate(_Reason, #q{worker = undefined}) ->
ok;
terminate(_Reason, #q{worker = {W, _}}) ->
gen_server:reply(W, closed),
ok.
handle_call({queue, Item, Size}, From, #q{worker = undefined} = Q0) ->
Q = Q0#q{
size = Q0#q.size + Size,
items = Q0#q.items + 1,
queue = queue:in({Item, Size}, Q0#q.queue)
},
IsFull = (Q#q.size >= Q#q.max_size) orelse (Q#q.items >= Q#q.max_items),
case {IsFull, Q#q.hibernate} of
{true, true} -> {noreply, Q#q{blocked = [From | Q#q.blocked]}, hibernate};
{true, false} -> {noreply, Q#q{blocked = [From | Q#q.blocked]}};
{false, true} -> {reply, ok, Q, hibernate};
{false, false} -> {reply, ok, Q}
end;
handle_call({queue, Item, _}, _From, #q{worker = {W, _Max}} = Q) ->
gen_server:reply(W, {ok, [Item]}),
case Q#q.hibernate of
true -> {reply, ok, Q#q{worker = undefined}, hibernate};
false -> {reply, ok, Q#q{worker = undefined}}
end;
handle_call({dequeue, _Max}, _From, #q{worker = {_, _}}) ->
% Something went wrong - the same or a different worker is
% trying to dequeue an item. We only allow one worker to wait
% for work at a time, so we exit with an error.
exit(multiple_workers_error);
handle_call({dequeue, Max}, From, #q{worker = undefined, items = Count} = Q) ->
case Count of
0 ->
{noreply, Q#q{worker = {From, Max}}};
C when C > 0 ->
deliver_queue_items(Max, Q)
end;
handle_call(item_count, _From, Q) ->
{reply, Q#q.items, Q};
handle_call(size, _From, Q) ->
{reply, Q#q.size, Q}.
deliver_queue_items(Max, Q) ->
#q{
queue = Queue,
items = Count,
size = Size,
close_on_dequeue = Close,
blocked = Blocked
} = Q,
case (Max =:= all) orelse (Max >= Count) of
false ->
{Items, Size2, Queue2, Blocked2} = dequeue_items(
Max, Size, Queue, Blocked, []
),
Q2 = Q#q{
items = Count - Max, size = Size2, blocked = Blocked2, queue = Queue2
},
{reply, {ok, Items}, Q2};
true ->
lists:foreach(fun(F) -> gen_server:reply(F, ok) end, Blocked),
Q2 = Q#q{items = 0, size = 0, blocked = [], queue = queue:new()},
Items = [Item || {Item, _} <- queue:to_list(Queue)],
case Close of
false ->
{reply, {ok, Items}, Q2};
true ->
{stop, normal, {ok, Items}, Q2}
end
end.
dequeue_items(0, Size, Queue, Blocked, DequeuedAcc) ->
{lists:reverse(DequeuedAcc), Size, Queue, Blocked};
dequeue_items(NumItems, Size, Queue, Blocked, DequeuedAcc) ->
{{value, {Item, ItemSize}}, Queue2} = queue:out(Queue),
case Blocked of
[] ->
Blocked2 = Blocked;
[From | Blocked2] ->
gen_server:reply(From, ok)
end,
dequeue_items(
NumItems - 1, Size - ItemSize, Queue2, Blocked2, [Item | DequeuedAcc]
).
handle_cast(close, #q{items = 0} = Q) ->
{stop, normal, Q};
handle_cast(close, Q) ->
{noreply, Q#q{close_on_dequeue = true}}.
handle_info(X, Q) ->
{stop, X, Q}.
run(BatchSize, BinarySize, MaxQueueItems, MaxQueueBytes, Hibernate) ->
io:format("Batch Size (List Length) : ~p~n", [BatchSize]),
io:format("Binary Size : ~p bytes~n", [BinarySize]),
io:format("Max Queue Items : ~p~n", [MaxQueueItems]),
io:format("Max Queue Size : ~p bytes~n", [MaxQueueBytes]),
io:format("Hibernate : ~p ~n", [Hibernate]),
{ok, Q} = new([
{max_items, MaxQueueItems},
{max_size, MaxQueueBytes},
{hibernate, Hibernate}
]),
rand:seed(default, {1, 2, 3}),
Consumer = spawn_link(fun() ->
rand:seed(default, {1, 2, 3}),
consumer(Q)
end),
Producer = spawn_link(fun() ->
rand:seed(default, {1, 2, 3}),
producer(Q, BinarySize, BatchSize)
end),
spawn_link(fun() ->
rand:seed(default, {1, 2, 3}),
checker(10, Producer, Consumer, 0, 0)
end).
status(Pid) ->
case is_process_alive(Pid) of
false ->
dead;
true ->
Pid ! {status, self()},
receive
{Pid, Produced, Consumed} ->
{Produced, Consumed}
end
end.
stop(Pid) ->
case is_process_alive(Pid) of
false ->
dead;
true ->
Pid ! stop
end.
checker(MaxN, Producer, Consumer, Produced, Produced) ->
N = rand:uniform(MaxN),
Producer ! {produce, N, self()},
checker(MaxN, Producer, Consumer, Produced + N, Produced);
checker(MaxN, Producer, Consumer, Produced, Consumed) ->
receive
{consumed, N} ->
checker(MaxN, Producer, Consumer, Produced, Consumed + N);
{status, From} ->
From ! {self(), Produced, Consumed},
checker(MaxN, Producer, Consumer, Produced, Consumed);
stop ->
Producer ! close
end.
payload(BatchSize, BinarySize) ->
{[
{rand:bytes(rand:uniform(10)), rand:bytes(rand:uniform(BinarySize))}
|| _ <- lists:seq(0, BatchSize)]}.
producer(Q, BinarySize, BatchSize) ->
receive
{produce, N, NotifyPid} ->
lists:foreach(fun(_) ->
Payload = payload(BatchSize, BinarySize),
queue(Q, {Payload, NotifyPid}),
case rand:uniform() < 0.2 of
true -> timer:sleep(round(rand:uniform()*20));
false -> ok
end
end, lists:seq(1, N)),
producer(Q, BinarySize, BatchSize);
close ->
close(Q)
end.
consumer(Q) ->
case dequeue(Q) of
{ok, Items} ->
[{_Payload, NotifyPid}|_]= Items,
NotifyPid ! {consumed, length(Items)},
case rand:uniform() < 0.2 of
true -> timer:sleep(round(rand:uniform()*20));
false -> ok
end,
consumer(Q);
closed ->
close(Q)
end.
get_value(Key, List, Default) ->
case lists:keysearch(Key, 1, List) of
{value, {K, Value}} when K =:= Key ->
Value;
false ->
Default
end.
@nickva
Copy link
Author

nickva commented Feb 14, 2026

 erl +MBacul 0 +MBas aobf
Erlang/OTP 27 [erts-15.2.7.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Eshell V15.2.7.4 (press Ctrl+G to abort, type help(). for help)
1> f(Pids), Pids =[cwb_bench2:run(5000, 500, 5000, 1000000, true) || P <- lists:seq(1, 50)].
Batch Size (List Length) : 5000
...


rp([cwb_bench2:status(P) || P <- Pids]).
[{118,115},
 {87,85},
 {131,130},
 {129,123},
 {118,113},
 {87,78},
 {129,121},
 {118,115},
 {87,82},
 {113,109},
 {129,125},
 {87,80},
 {129,122},
 {87,83},
 {91,89},
 {91,87},
 {87,79},
 {91,90},
 {100,95},
 {87,78},
 {87,85},
 {78,75},
 {109,101},
 {87,86},
 {87,85},
 {109,105},
 {87,84},
 {109,104},
 {78,75},
 {87,78},
 {78,72},
 {78,76},
 {100,95},
 {78,76},
 {78,75},
 {100,97},
 {109,101},
 {146,144},
 {100,97},
 {159,154},
 {100,98},
 {87,86},
 {93,92},
 {100,99},
 {93,91},
 {87,81},
 {118,114},
 {78,72},
 {93,91},
 {100,97}]
...
32> Segmentation fault (core dumped)

In other terminal

gdb -p $pid
c (continue)
...

Thread 28 "erts_sched_2" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7228b6afb6c0 (LWP 23271)]
aobf_link_free_block (allctr=0x5ddd4f129300, block=0x72281383ccd0) at beam/erl_bestfit_alloc.c:612
warning: 612	beam/erl_bestfit_alloc.c: No such file or directory
(gdb) bt
#0  aobf_link_free_block (allctr=0x5ddd4f129300, block=0x72281383ccd0) at beam/erl_bestfit_alloc.c:612
#1  0x00005ddd2b519e79 in mbc_free (allctr=0x5ddd4f129300, type=<optimized out>, p=<optimized out>, busy_pcrr_pp=<optimized out>)
    at beam/erl_alloc_util.c:2793
#2  0x00005ddd2b51cbfd in dealloc_block (fix=0x0, ptr=0x72281383ccd8, flags=<optimized out>, type=<optimized out>, allctr=<optimized out>)
    at beam/erl_alloc_util.c:2340
#3  handle_delayed_dealloc (allctr=allctr@entry=0x5ddd4f129300, need_more_work=0x0, thr_prgr_p=0x0, need_thr_progress=0x0, ops_limit=2, 
    use_limit=1, allctr_locked=1) at beam/erl_alloc_util.c:2199
#4  0x00005ddd2b51d75c in realloc_thr_pref (type=27946, pref_allctr=0x5ddd4f129300, p=0x722813b6eaa0, size=397, force_move=0)
    at beam/erl_alloc_util.c:6560
#5  0x00005ddd2b6436ea in erts_realloc_fnf (size=397, ptr=0x722813b6eaa0, type=27946) at beam/erl_alloc.h:302
#6  erts_bin_realloc_fnf (size=365, bp=0x722813b6eaa0) at beam/erl_binary.h:338
#7  erts_bin_realloc (size=365, bp=0x722813b6eaa0) at beam/erl_binary.h:351
#8  erts_pin_writable_binary (br=0x72282a689638, sb=0x72282a684af0) at beam/erl_bits.c:1851
#9  erts_pin_writable_binary (sb=sb@entry=0x72282a684af0, br=br@entry=0x72282a689638) at beam/erl_bits.c:1831
#10 0x00005ddd2b5c0a8f in encode_size_struct_int (ctx=ctx@entry=0x0, acmp=acmp@entry=0x0, obj=<optimized out>, dflags=dflags@entry=67584, 
    reds=reds@entry=0x0, res=res@entry=0x7228b6af6cd0) at beam/external.c:5588
#11 0x00005ddd2b5ce611 in erts_encode_ext_size_2 (szp=0x7228b6af6cd0, dflags=67584, term=<optimized out>) at beam/external.c:735
#12 erts_encode_ext_size (szp=0x7228b6af6cd0, term=<optimized out>) at beam/external.c:742
#13 external_size_1 (A__p=0x5ddd4f294508, BIF__ARGS=<optimized out>, A__I=<optimized out>) at beam/external.c:2195
#14 0x00007228babff63c in ?? ()
#15 0x0000000000000000 in ?? ()

@nickva
Copy link
Author

nickva commented Feb 14, 2026

A run without the extra allocator args segfaults but in another area:

$ erl
Erlang/OTP 27 [erts-15.2.7.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Eshell V15.2.7.4 (press Ctrl+G to abort, type help(). for help)
1> f(Pids), Pids =[cwb_bench2:run(5000, 500, 5000, 1000000, true) || P <- lists:seq(1, 50)].
Batch Size (List Length) : 5000
Binary Size              : 500 bytes
....

> f(Pids), Pids =[cwb_bench2:run(5000, 500, 5000, 1000000, true) || P <- lists:seq(1, 50)].

 rp([cwb_bench2:status(P) || P <- Pids]).
[{36,30},
 {78,71},
 {36,30},
 {36,34},
 {69,61},
 {36,30},
 {61,57},
 {36,30},
 {36,33},
 {36,29},
 {36,28},
 {40,36},
 {36,32},
 {36,28},
 {36,30},
 {36,35},
 {36,30},
 {36,32},
 {36,31},
 {36,29},
 {36,33},
 {36,30},
 {36,31},
 {27,25},
 {36,28},
 {36,28},
 {36,30},
 {36,28},
 {27,24},
 {27,24},
 {36,28},
 {36,30},
 {27,24},
 {36,29},
 {36,29},
 {27,23},
 {23,22},
 {27,25},
 {27,26},
 {52,43},
 {22,21},
 {61,60},
 {27,26},
 {36,27},
 {22,21},
 {52,48},
 {27,26},
 {22,20},
 {36,30},
 {27,26}]

gdb

0x00007cd227526d07 in select () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) c
Continuing.
JITed symbol file is not an object file, ignoring it.
JITed symbol file is not an object file, ignoring it.

Thread 26 "erts_sched_4" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7cd1db4f56c0 (LWP 23894)]
sweep_off_heap (p=p@entry=0x7cd1d9f51660, fullsweep=fullsweep@entry=1) at beam/erl_gc.c:3174
warning: 3174	beam/erl_gc.c: No such file or directory
(gdb) bt
#0  sweep_off_heap (p=p@entry=0x7cd1d9f51660, fullsweep=fullsweep@entry=1) at beam/erl_gc.c:3174
#1  0x00006291c14372db in full_sweep_heaps (p=p@entry=0x7cd1d9f51660, live_hf_end=live_hf_end@entry=0xfffffffffffffff8, n_heap=n_heap@entry=0x7cd167c10028, 
    n_htop=0x7cd167c58590, n_htop@entry=0x7cd167c10028, objv=objv@entry=0x7cd1db4f0d40, nobj=nobj@entry=11, oh_size=<optimized out>, oh=<optimized out>, hibernate=0)
    at beam/erl_gc.c:2060
#2  0x00006291c143af9f in major_collection (recl=<synthetic pointer>, ygen_usage=<optimized out>, nobj=11, objv=0x7cd1db4f0d40, need=<optimized out>, 
    live_hf_end=0xfffffffffffffff8, p=0x7cd1d9f51660) at beam/erl_gc.c:1924
#3  garbage_collect (p=p@entry=0x7cd1d9f51660, live_hf_end=live_hf_end@entry=0xfffffffffffffff8, need=need@entry=5, objv=objv@entry=0x7cd1db4f0d40, nobj=nobj@entry=11, 
    fcalls=3243, max_young_gen_usage=0) at beam/erl_gc.c:828
#4  0x00006291c143cd1c in erts_garbage_collect (p=p@entry=0x7cd1d9f51660, need=5, objv=objv@entry=0x7cd1db4f0d40, nobj=nobj@entry=11) at beam/erl_gc.c:959
#5  0x00006291c1442bbf in erts_bs_append_checked (c_p=0x7cd1d9f51660, reg=0x7cd1db4f0d40, live=10, build_size_in_bits=<optimized out>, extra_words=<optimized out>, 
    unit=<optimized out>) at beam/erl_bits.c:1600
#6  0x00007cd1dfb42463 in ?? ()
#7  0x0000000000000000 in ?? ()

@nickva
Copy link
Author

nickva commented Feb 16, 2026

Unfortunately I can't reproduce it any longer. Perhaps it was a temporary hardware issue (overheating) or there was something else consuming memory in the background at that time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment