%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1998-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(timer_bif_SUITE). -export([all/0, suite/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2,end_per_testcase/2]). -export([start_timer_1/1, send_after_1/1, send_after_2/1, send_after_3/1, cancel_timer_1/1, cancel_timer_sync/1, start_timer_big/1, send_after_big/1, start_timer_e/1, send_after_e/1, cancel_timer_e/1, read_timer_trivial/1, read_timer/1, read_timer_async/1, cleanup/1, evil_timers/1, registered_process/1, same_time_yielding/1, same_time_yielding_with_cancel/1, same_time_yielding_with_cancel_other/1, % same_time_yielding_with_cancel_other_accessor/1, auto_cancel_yielding/1, suspended_scheduler_timeout/1, multizero_timeout_in_timeout/1]). -include_lib("common_test/include/ct.hrl"). -define(SHORT_TIMEOUT, 5000). %% Bif timers as short as this may be pre-allocated -define(TIMEOUT_YIELD_LIMIT, 100). -define(AUTO_CANCEL_YIELD_LIMIT, 100). init_per_testcase(_Case, Config) -> case catch erts_debug:get_internal_state(available_internal_state) of true -> ok; _ -> erts_debug:set_internal_state(available_internal_state, true) end, Config. end_per_testcase(_Case, Config) -> erts_test_utils:ept_check_leaked_nodes(Config). init_per_suite(Config) -> erts_debug:set_internal_state(available_internal_state, true), Config. end_per_suite(_Config) -> catch erts_debug:set_internal_state(available_internal_state, false). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 5}}]. all() -> [start_timer_1, send_after_1, send_after_2, cancel_timer_1, cancel_timer_sync, start_timer_e, send_after_e, cancel_timer_e, start_timer_big, send_after_big, read_timer_trivial, read_timer, read_timer_async, cleanup, evil_timers, registered_process, same_time_yielding, same_time_yielding_with_cancel, same_time_yielding_with_cancel_other, % same_time_yielding_with_cancel_other_accessor, auto_cancel_yielding, suspended_scheduler_timeout, multizero_timeout_in_timeout]. %% Basic start_timer/3 functionality start_timer_1(Config) when is_list(Config) -> Ref1 = erlang:start_timer(1000, self(), plopp), ok = get(1400, {timeout, Ref1, plopp}), false = erlang:read_timer(Ref1), false = erlang:cancel_timer(Ref1), false = erlang:read_timer(Ref1), Ref2 = erlang:start_timer(1000, self(), plapp), Left2 = erlang:cancel_timer(Ref2), UpperLimit = 1000, true = (Left2 > 600) and (Left2 =< UpperLimit), empty = get_msg(), false = erlang:cancel_timer(Ref2), Ref3 = erlang:start_timer(1000, self(), plopp), no_message = get(600, {timeout, Ref3, plopp}), ok. %% Basic send_after/3 functionality send_after_1(Config) when is_list(Config) -> Ref3 = erlang:send_after(1000, self(), plipp), ok = get(1500, plipp), false = erlang:read_timer(Ref3), ok. %% Big timeouts for start_timer/3 start_timer_big(Config) when is_list(Config) -> Big = 1 bsl 31, R = erlang:start_timer(Big, self(), hej), timer:sleep(200), Left = erlang:cancel_timer(R), case Big - Left of Diff when Diff >= 200, Diff < 10000 -> ok; _Diff -> ct:fail({big, Big, Left}) end, ok. %% Big timeouts for send_after/3 send_after_big(Config) when is_list(Config) -> Big = 1 bsl 31, R = erlang:send_after(Big, self(), hej), timer:sleep(200), Left = erlang:cancel_timer(R), case Big - Left of Diff when Diff >= 200, Diff < 10000 -> ok; _Diff -> ct:fail({big, Big, Left}) end, ok. %% send_after/3: messages in the right order, kind version send_after_2(Config) when is_list(Config) -> _ = erlang:send_after(5000, self(), last), _ = erlang:send_after(0, self(), a0), _ = erlang:send_after(200, self(), a2), _ = erlang:send_after(100, self(), a1), _ = erlang:send_after(500, self(), a5), _ = erlang:send_after(300, self(), a3), _ = erlang:send_after(400, self(), a4), [a0,a1,a2,a3,a4,a5,last] = collect(last), ok. %% send_after/3: messages in the right order, worse than send_after_2 send_after_3(Config) when is_list(Config) -> _ = erlang:send_after(100, self(), b1), _ = erlang:send_after(101, self(), b2), _ = erlang:send_after(102, self(), b3), _ = erlang:send_after(103, self(), last), [b1, b2, b3, last] = collect(last), % This behaviour is not guaranteed: % _ = erlang:send_after(100, self(), c1), % _ = erlang:send_after(100, self(), c2), % _ = erlang:send_after(100, self(), c3), % _ = erlang:send_after(100, self(), last), % [c1, c2, c3, last] = collect(last), ok. %% Check trivial cancel_timer/1 behaviour cancel_timer_1(Config) when is_list(Config) -> false = erlang:cancel_timer(make_ref()), ok. cancel_timer_sync(Config) when is_list(Config) -> cancel_timer_sync_test(true), cancel_timer_sync_test(false). cancel_timer_sync_test(SameSched) -> process_flag(scheduler, 1), R1 = erlang:send_after(1000, self(), cling), R2 = erlang:send_after(1000, self(), clong), R3 = erlang:send_after(1000, self(), clang), TsSet = erlang:monotonic_time(), R4 = erlang:send_after(0, self(), pling), R5 = erlang:send_after(0, self(), plong), R6 = erlang:send_after(0, self(), plang), R7 = make_ref(), R8 = make_ref(), R9 = make_ref(), case SameSched of true -> %% Cancel from the same scheduler... ok; false-> %% Cancel from different scheduler... process_flag(scheduler, erlang:system_info(schedulers_online)) end, C1 = erlang:cancel_timer(R1), true = is_integer(C1), C2 = erlang:cancel_timer(R2, [{info, true}]), true = is_integer(C2), ok = erlang:cancel_timer(R3, [{info, false}]), receive pling -> ok end, receive plong -> ok end, receive plang -> ok end, false = erlang:cancel_timer(R4), false = erlang:cancel_timer(R5, [{info, true}]), ok = erlang:cancel_timer(R6, [{info, false}]), false = erlang:cancel_timer(R7), false = erlang:cancel_timer(R8, [{info, true}]), ok = erlang:cancel_timer(R9, [{info, false}]), Wait = 1500 - erlang:convert_time_unit(erlang:monotonic_time() - TsSet, native, millisecond), receive TMO when TMO == cling; TMO == clang; TMO == clong -> ct:fail({unexpected_timeout, TMO}) after Wait -> ok end. %% Error cases for start_timer/3 start_timer_e(Config) when is_list(Config) -> {'EXIT', _} = (catch erlang:start_timer(-4, self(), hej)), {'EXIT', _} = (catch erlang:start_timer(1 bsl 64, self(), hej)), {'EXIT', _} = (catch erlang:start_timer(4.5, self(), hej)), {'EXIT', _} = (catch erlang:start_timer(a, self(), hej)), {ok, Peer, Node} = ?CT_PEER(), Pid = spawn(Node, timer, sleep, [10000]), {'EXIT', _} = (catch erlang:start_timer(1000, Pid, hej)), peer:stop(Peer). %% Error cases for send_after/3 send_after_e(Config) when is_list(Config) -> {'EXIT', _} = (catch erlang:send_after(-4, self(), hej)), {'EXIT', _} = (catch erlang:send_after(1 bsl 64, self(), hej)), {'EXIT', _} = (catch erlang:send_after(4.5, self(), hej)), {'EXIT', _} = (catch erlang:send_after(a, self(), hej)), {ok, Peer, Node} = ?CT_PEER(), Pid = spawn(Node, timer, sleep, [10000]), {'EXIT', _} = (catch erlang:send_after(1000, Pid, hej)), peer:stop(Peer). %% Error cases for cancel_timer/1 cancel_timer_e(Config) when is_list(Config) -> {'EXIT', _} = (catch erlang:cancel_timer(1)), {'EXIT', _} = (catch erlang:cancel_timer(self())), {'EXIT', _} = (catch erlang:cancel_timer(a)), ok. %% Trivial and error test cases for read_timer/1. read_timer_trivial(Config) when is_list(Config) -> false = erlang:read_timer(make_ref()), {'EXIT', _} = (catch erlang:read_timer(42)), {'EXIT', _} = (catch erlang:read_timer(423497834744444444457667444444)), {'EXIT', _} = (catch erlang:read_timer(self())), {'EXIT', _} = (catch erlang:read_timer(ab)), ok. %% Test that read_timer/1 seems to return the correct values. read_timer(Config) when is_list(Config) -> process_flag(scheduler, 1), Big = 1 bsl 31, R = erlang:send_after(Big, self(), hej_hopp), receive after 200 -> ok end, % Delay and clear reductions. Left = erlang:read_timer(R), Left2 = erlang:cancel_timer(R), case Left == Left2 of true -> ok; false -> Left = Left2 + 1 end, false = erlang:read_timer(R), case Big - Left of Diff when Diff >= 200, Diff < 10000 -> ok; _Diff -> ct:fail({big, Big, Left}) end, process_flag(scheduler, 0), ok. %% Test that read_timer/1 seems to return the correct values. read_timer_async(Config) when is_list(Config) -> process_flag(scheduler, 1), Big = 1 bsl 33, R = erlang:send_after(Big, self(), hej_hopp), %% Access from another scheduler process_flag(scheduler, erlang:system_info(schedulers_online)), receive after 200 -> ok end, % Delay and clear reductions. ok = erlang:read_timer(R, [{async, true}]), ok = erlang:cancel_timer(R, [{async, true}, {info, true}]), ok = erlang:read_timer(R, [{async, true}]), {read_timer, R, Left} = receive_one(), {cancel_timer, R, Left2} = receive_one(), case Left == Left2 of true -> ok; false -> Left = Left2 + 1 end, {read_timer, R, false} = receive_one(), case Big - Left of Diff when Diff >= 200, Diff < 10000 -> ok; _Diff -> ct:fail({big, Big, Left}) end, process_flag(scheduler, 0), ok. cleanup(Config) when is_list(Config) -> Mem = mem(), %% Timer on dead process P1 = spawn(fun () -> ok end), wait_until(fun () -> process_is_cleaned_up(P1) end), T1 = erlang:start_timer(?SHORT_TIMEOUT*2, P1, "hej"), T2 = erlang:send_after(?SHORT_TIMEOUT*2, P1, "hej"), receive after 1000 -> ok end, Mem = mem(), false = erlang:read_timer(T1), false = erlang:read_timer(T2), Mem = mem(), %% Process dies before timeout P2 = spawn(fun () -> receive after (?SHORT_TIMEOUT div 10) -> ok end end), T3 = erlang:start_timer(?SHORT_TIMEOUT*2, P2, "hej"), T4 = erlang:send_after(?SHORT_TIMEOUT*2, P2, "hej"), true = mem_larger_than(Mem), true = is_integer(erlang:read_timer(T3)), true = is_integer(erlang:read_timer(T4)), wait_until(fun () -> process_is_cleaned_up(P2) end), receive after 1000 -> ok end, false = erlang:read_timer(T3), false = erlang:read_timer(T4), Mem = mem(), %% Cancel timer P3 = spawn(fun () -> receive after ?SHORT_TIMEOUT*4 -> ok end end), T5 = erlang:start_timer(?SHORT_TIMEOUT*2, P3, "hej"), T6 = erlang:send_after(?SHORT_TIMEOUT*2, P3, "hej"), true = mem_larger_than(Mem), true = is_integer(erlang:cancel_timer(T5)), true = is_integer(erlang:cancel_timer(T6)), false = erlang:read_timer(T5), false = erlang:read_timer(T6), exit(P3, kill), wait_until(fun () -> process_is_cleaned_up(P3) end), Mem = mem(), %% Timeout Ref = make_ref(), T7 = erlang:start_timer(?SHORT_TIMEOUT+1, self(), Ref), T8 = erlang:send_after(?SHORT_TIMEOUT+1, self(), Ref), true = mem_larger_than(Mem), true = is_integer(erlang:read_timer(T7)), true = is_integer(erlang:read_timer(T8)), receive {timeout, T7, Ref} -> ok end, receive Ref -> ok end, Mem = mem(), ok. evil_timers(Config) when is_list(Config) -> %% Create a composite term consisting of at least: %% * externals (remote pids, ports, and refs) %% * large (off heap) binaries %% * small (heap) binaries %% * funs %% * bignums %% * tuples %% * lists %% since data of these types have to be adjusted if moved %% in memory Self = self(), R1 = make_ref(), {ok, Peer, Node} = ?CT_PEER(), spawn_link(Node, fun () -> Self ! {R1, [lists:sublist(erlang:ports(), 3), [make_ref(), make_ref(), make_ref()], lists:sublist(processes(), 3), [fun () -> gurka end, fun (A) -> A + 1 end, fun (A, B) -> A + B end]]} end), ExtList = receive {R1, L} -> L end, peer:stop(Peer), BinList = [<<"bla">>, <<"blipp">>, <<"blupp">>, list_to_binary(lists:duplicate(1000000,$a)), list_to_binary(lists:duplicate(1000000,$b)), list_to_binary(lists:duplicate(1000000,$c))], FunList = [fun () -> gurka end, fun (A) -> A + 1 end, fun (A, B) -> A + B end], PidList = lists:sublist(processes(), 3), PortList = lists:sublist(erlang:ports(), 3), RefList = [make_ref(), make_ref(), make_ref()], BigList = [111111111111, 22222222222222, 333333333333333333], Msg = {BinList,[FunList,{RefList,ExtList,PidList,PortList,BigList}]}, %% io:format("Msg=~p~n",[Msg]), Prio = process_flag(priority, max), %% %% In the smp case there are four major cases we want to test: %% %% 1. A timer started with erlang:start_timer(Time, Receiver, Msg), %% where Msg is a composite term, expires, and the receivers main %% lock *cannot* be acquired immediately (typically when the %% receiver *is* running). %% %% The wrap tuple ({timeout, TRef, Msg}) will in this case %% be allocated in the previously allocated message buffer along %% with Msg, i.e. the previously allocated message buffer will be %% reallocated and potentially moved. TimeOutMsgs0 = evil_setup_timers(200, Self, Msg), RecvTimeOutMsgs0 = evil_recv_timeouts(200), %% 2. A timer started with erlang:start_timer(Time, Receiver, Msg), %% where Msg is an immediate term, expires, and the receivers main %% lock *cannot* be acquired immediately (typically when the %% receiver *is* running). %% %% The wrap tuple will in this case be allocated in a new %% message buffer. TimeOutMsgs1 = evil_setup_timers(200, Self, immediate), RecvTimeOutMsgs1 = evil_recv_timeouts(200), %% 3. A timer started with erlang:start_timer(Time, Receiver, Msg), %% where Msg is a composite term, expires, and the receivers main %% lock *can* be acquired immediately (typically when the receiver %% *is not* running). %% %% The wrap tuple will in this case be allocated on the receivers %% heap, and Msg is passed in the previously allocated message %% buffer. R2 = make_ref(), spawn_link(fun () -> Self ! {R2, evil_setup_timers(200, Self, Msg)} end), receive after 1000 -> ok end, TimeOutMsgs2 = receive {R2, TOM2} -> TOM2 end, RecvTimeOutMsgs2 = evil_recv_timeouts(200), %% 4. A timer started with erlang:start_timer(Time, Receiver, Msg), %% where Msg is an immediate term, expires, and the Receivers main %% lock *can* be acquired immediately (typically when the receiver %% *is not* running). %% %% The wrap tuple will in this case be allocated on the receivers %% heap. R3 = make_ref(), spawn_link(fun () -> Self ! {R3, evil_setup_timers(200,Self,immediate)} end), receive after 1000 -> ok end, TimeOutMsgs3 = receive {R3, TOM3} -> TOM3 end, RecvTimeOutMsgs3 = evil_recv_timeouts(200), %% Garge collection will hopefully crash the emulator if something %% is wrong... garbage_collect(), garbage_collect(), garbage_collect(), %% Make sure we got the timeouts we expected %% %% Note timeouts are *not* guaranteed to be delivered in order ok = match(lists:sort(RecvTimeOutMsgs0), lists:sort(TimeOutMsgs0)), ok = match(lists:sort(RecvTimeOutMsgs1), lists:sort(TimeOutMsgs1)), ok = match(lists:sort(RecvTimeOutMsgs2), lists:sort(TimeOutMsgs2)), ok = match(lists:sort(RecvTimeOutMsgs3), lists:sort(TimeOutMsgs3)), process_flag(priority, Prio), ok. evil_setup_timers(N, Receiver, Msg) -> evil_setup_timers(0, N, Receiver, Msg, []). evil_setup_timers(N, N, _Receiver, _Msg, TOs) -> TOs; evil_setup_timers(N, Max, Receiver, Msg, TOs) -> TRef = erlang:start_timer(N, Receiver, Msg), evil_setup_timers(N+1, Max, Receiver, Msg, [{timeout,TRef,Msg}|TOs]). evil_recv_timeouts(M) -> evil_recv_timeouts([], 0, M). evil_recv_timeouts(TOs, N, N) -> TOs; evil_recv_timeouts(TOs, N, M) -> receive {timeout, _, _} = TO -> evil_recv_timeouts([TO|TOs], N+1, M) after 0 -> evil_recv_timeouts(TOs, N, M) end. registered_process(Config) when is_list(Config) -> Mem = mem(), %% Cancel T1 = erlang:start_timer(?SHORT_TIMEOUT+1, ?MODULE, "hej"), T2 = erlang:send_after(?SHORT_TIMEOUT+1, ?MODULE, "hej"), undefined = whereis(?MODULE), true = mem_larger_than(Mem), true = is_integer(erlang:cancel_timer(T1)), true = is_integer(erlang:cancel_timer(T2)), false = erlang:read_timer(T1), false = erlang:read_timer(T2), Mem = mem(), %% Timeout register after start Ref1 = make_ref(), T3 = erlang:start_timer(?SHORT_TIMEOUT+1, ?MODULE, Ref1), T4 = erlang:send_after(?SHORT_TIMEOUT+1, ?MODULE, Ref1), undefined = whereis(?MODULE), true = mem_larger_than(Mem), true = is_integer(erlang:read_timer(T3)), true = is_integer(erlang:read_timer(T4)), true = register(?MODULE, self()), receive {timeout, T3, Ref1} -> ok end, receive Ref1 -> ok end, Mem = mem(), %% Timeout register before start Ref2 = make_ref(), T5 = erlang:start_timer(?SHORT_TIMEOUT+1, ?MODULE, Ref2), T6 = erlang:send_after(?SHORT_TIMEOUT+1, ?MODULE, Ref2), true = mem_larger_than(Mem), true = is_integer(erlang:read_timer(T5)), true = is_integer(erlang:read_timer(T6)), receive {timeout, T5, Ref2} -> ok end, receive Ref2 -> ok end, Mem = mem(), true = unregister(?MODULE), ok. same_time_yielding(Config) when is_list(Config) -> Mem = mem(), Ref = make_ref(), SchdlrsOnline = erlang:system_info(schedulers_online), SchdlrsToUse = erlang:max(1, erlang:min(8, SchdlrsOnline) - 1), StartTime = erlang:monotonic_time(millisecond), Tmo = StartTime + 20000, NrOfTimeouts = (?TIMEOUT_YIELD_LIMIT*3+1) * SchdlrsToUse, NrOfTimeoutsPerScheduler = NrOfTimeouts div SchdlrsToUse, Tmrs = lists:map(fun (I) -> SchedulerId = case SchdlrsOnline of 1 -> 1; _ -> ((I div NrOfTimeoutsPerScheduler) rem SchdlrsToUse) + 2 end, process_flag(scheduler, SchedulerId), erlang:start_timer(Tmo, self(), Ref, [{abs, true}]), SchedulerId end, lists:seq(1, NrOfTimeouts)), process_flag(scheduler, 1), AllTimersStartedTime = erlang:monotonic_time(millisecond), true = mem_larger_than(Mem), {TimeToReceive, TimeUntilFirst} = receive_all_timeouts(length(Tmrs), Ref), Done = erlang:monotonic_time(millisecond), true = Done >= Tmo, MsAfterTmo = Done - Tmo, io:format("Done ~p ms after Tmo\nInfo: ~p\n", [MsAfterTmo, {'TimeToStartAllTimers', AllTimersStartedTime - StartTime, 'TimeInReceiveUntilFirst',TimeUntilFirst, 'TimeToReceive', TimeToReceive}]), case erlang:system_info(build_type) of opt -> true = MsAfterTmo < 200; _ -> true = MsAfterTmo < 1000 end, Mem = mem(), ok. %% Read out all timeouts in receive queue order. This is efficient %% even if there are very many messages. receive_all_timeouts(N, Ref) -> receive_all_timeouts(N, Ref, not_set). receive_all_timeouts(0, _Ref, {FirstTime, TimeUntilFirst}) -> {erlang:monotonic_time(millisecond) - FirstTime, TimeUntilFirst}; receive_all_timeouts(N, Ref, Timings) -> StartTime = case Timings of not_set -> erlang:monotonic_time(millisecond); _ -> ok end, receive {timeout, _Tmr, Ref} -> NewTimings = case Timings of not_set -> FirstMessageAtTime = erlang:monotonic_time(millisecond), {FirstMessageAtTime, FirstMessageAtTime - StartTime}; _ -> Timings end, receive_all_timeouts(N-1, Ref, NewTimings) end. same_time_yielding_with_cancel(Config) when is_list(Config) -> same_time_yielding_with_cancel_test(false, false). same_time_yielding_with_cancel_other(Config) when is_list(Config) -> same_time_yielding_with_cancel_test(true, false). %same_time_yielding_with_cancel_other_accessor(Config) when is_list(Config) -> % same_time_yielding_with_cancel_test(true, true). do_cancel_tmrs(Tmo, Tmrs, Tester) -> BeginCancel = erlang:convert_time_unit(Tmo, millisecond, microsecond) - 500, busy_wait_until(fun () -> erlang:monotonic_time(microsecond) >= BeginCancel end), lists:foreach(fun (Tmr) -> erlang:cancel_timer(Tmr, [{async, true}, {info, true}]) end, Tmrs), case Tester == self() of true -> ok; false -> forward_msgs(Tester) end. same_time_yielding_with_cancel_test(Other, Accessor) -> Mem = mem(), SchdlrsOnline = erlang:system_info(schedulers_online), SchdlrsToUse = erlang:max(1, erlang:min(8, SchdlrsOnline) - 1), NrOfTimeouts = (?TIMEOUT_YIELD_LIMIT*3+1) * SchdlrsToUse, NrOfTimeoutsPerScheduler = NrOfTimeouts div SchdlrsToUse, Tmo = erlang:monotonic_time(millisecond) + 6000, Tester = self(), Cancelor = case Other of false -> Tester; true -> spawn(fun () -> receive {timers, Tmrs} -> do_cancel_tmrs(Tmo, Tmrs, Tester) end end) end, Opts = case Accessor of false -> [{abs, true}]; true -> [{accessor, Cancelor}, {abs, true}] end, Tmrs = lists:map(fun (I) -> SchedulerId = case SchdlrsOnline of 1 -> 1; _ -> ((I div NrOfTimeoutsPerScheduler) rem SchdlrsToUse) + 2 end, process_flag(scheduler, SchedulerId), erlang:start_timer(Tmo, self(), hej, Opts) end, lists:seq(1, NrOfTimeouts)), process_flag(scheduler, 1), true = mem_larger_than(Mem), case Other of false -> do_cancel_tmrs(Tmo, Tmrs, Tester); true -> Cancelor ! {timers, Tmrs} end, {Tmos, Cncls} = lists:foldl(fun (Tmr, {T, C}) -> receive {timeout, Tmr, hej} -> receive {cancel_timer, Tmr, Info} -> false = Info, {T+1, C} end; {cancel_timer, Tmr, false} -> receive {timeout, Tmr, hej} -> {T+1, C} end; {cancel_timer, Tmr, TimeLeft} -> true = is_integer(TimeLeft), {T, C+1} end end, {0, 0}, Tmrs), io:format("Timeouts: ~p Cancels: ~p~n", [Tmos, Cncls]), Mem = mem(), case Other of true -> exit(Cancelor, bang); false -> ok end, {comment, "Timeouts: " ++ integer_to_list(Tmos) ++ " Cancels: " ++ integer_to_list(Cncls)}. auto_cancel_yielding(Config) when is_list(Config) -> Mem = mem(), SchdlrsOnln = erlang:system_info(schedulers_online), P = spawn(fun () -> lists:foreach( fun (I) -> process_flag(scheduler, (I rem SchdlrsOnln)+1), erlang:start_timer((1 bsl 28)+I*10, self(), hej) end, lists:seq(1, ((?AUTO_CANCEL_YIELD_LIMIT*3+1) *SchdlrsOnln))), receive after infinity -> ok end end), true = mem_larger_than(Mem), exit(P, bang), wait_until(fun () -> process_is_cleaned_up(P) end), Mem = mem(), ok. suspended_scheduler_timeout(Config) when is_list(Config) -> Ref = make_ref(), SchdlrsOnln = erlang:system_info(schedulers_online), lists:foreach(fun (Sched) -> process_flag(scheduler, Sched), erlang:send_after(1000, self(), {Ref, Sched}) end, lists:seq(1, SchdlrsOnln)), process_flag(scheduler, 0), erlang:system_flag(schedulers_online, 1), try lists:foreach(fun (Sched) -> receive {Ref, Sched} -> ok after 2000 -> ct:fail({missing_timeout, Sched}) end end, lists:seq(1, SchdlrsOnln)) after erlang:system_flag(schedulers_online, SchdlrsOnln) end, ok. multizero_timeout_in_timeout(Config) when is_list(Config) -> Timeout = 500, MaxTimeoutDiff = 1000, %% We want to operate on the same timer wheel all the time... process_flag(scheduler, erlang:system_info(schedulers_online)), erlang:send_after(5*(Timeout+MaxTimeoutDiff), self(), pling), erlang:yield(), Start = erlang:monotonic_time(), erts_debug:set_internal_state(multizero_timeout_in_timeout, Timeout), receive multizero_timeout_in_timeout_done -> ok end, End = erlang:monotonic_time(), Time = erlang:convert_time_unit(End-Start, native, millisecond), io:format("Time=~p~n", [Time]), true = Time < Timeout + MaxTimeoutDiff, ok. process_is_cleaned_up(P) when is_pid(P) -> undefined == erts_debug:get_internal_state({process_status, P}). wait_until(Pred) when is_function(Pred) -> case catch Pred() of true -> ok; _ -> receive after 50 -> ok end, wait_until(Pred) end. busy_wait_until(Pred) when is_function(Pred) -> case catch Pred() of true -> ok; _ -> busy_wait_until(Pred) end. forward_msgs(To) -> receive Msg -> To ! Msg end, forward_msgs(To). get(Time, Msg) -> receive Msg -> ok after Time -> no_message end. get_msg() -> receive Msg -> {ok, Msg} after 0 -> empty end. collect(Last) -> collect(Last, []). receive_one() -> receive Msg -> Msg end. collect(Last, Msgs0) -> Msg = receive_one(), Msgs = Msgs0 ++ [Msg], case Msg of Last -> Msgs; _ -> collect(Last, Msgs) end. match(X, X) -> %erlang:display({match, X}), ok; match(X, Y) -> %erlang:display({mismatch, X, Y}), match_aux(X, Y). match_aux(X, X) -> unexpected_error; match_aux(X, Y) when is_list(X), is_list(Y), length(X) =/= length(Y) -> %% erlang:display({mismatch, X, Y}), {list_length_mismatch, length(X), length(Y)}; match_aux([X|Xs], [X|Ys]) -> match_aux(Xs, Ys); match_aux([X|_], [Y|_]) -> match_aux(X, Y); match_aux(X, Y) when is_tuple(X), is_tuple(Y), size(X) =/= size(Y) -> %% erlang:display({mismatch, X, Y}), {tuple_size_mismatch, size(X), size(Y)}; match_aux(X, Y) when is_tuple(X), is_tuple(Y) -> match_aux(tuple_to_list(X), tuple_to_list(Y)); match_aux(X, Y) -> %% erlang:display({mismatch, X, Y}), {mismatch, type(X), type(Y)}. type(X) when is_list(X) -> list; type(X) when is_tuple(X) -> tuple; type(X) when is_float(X) -> float; type(X) when is_integer(X) -> integer; type(X) when is_pid(X) -> {pid, node(X)}; type(X) when is_reference(X) -> {reference, node(X)}; type(X) when is_port(X) -> {port, node(X)}; type(X) when is_binary(X) -> binary; type(X) when is_atom(X) -> atom; type(_) -> unknown. mem_larger_than(no_fix_alloc) -> true; mem_larger_than(Mem) -> mem() > Mem. mem() -> erts_debug:set_internal_state(wait, timer_cancellations), erts_debug:set_internal_state(wait, deallocations), case mem_get() of {-1, -1} -> no_fix_alloc; {A, U} -> io:format("mem = ~p ~p~n", [A, U]), U end. mem_get() -> % Bif timer memory Ref = make_ref(), erlang:system_info({memory_internal, Ref, [fix_alloc]}), mem_recv(erts_internal:no_aux_work_threads()-1, Ref, {0, 0}). mem_recv(0, _Ref, AU) -> AU; mem_recv(N, Ref, AU) -> receive {Ref, _, IL} -> mem_recv(N-1, Ref, mem_parse_ilists(IL, AU)) end. mem_parse_ilists([], AU) -> AU; mem_parse_ilists([I|Is], AU) -> mem_parse_ilists(Is, mem_parse_ilist(I, AU)). mem_parse_ilist({fix_alloc, false}, _) -> {-1, -1}; mem_parse_ilist({fix_alloc, _, IDL}, {A, U}) -> case lists:keyfind(fix_types, 1, IDL) of {fix_types, TL} -> {ThisA, ThisU} = mem_get_btm_aus(TL, 0, 0), {ThisA + A, ThisU + U}; {fix_types, Mask, TL} -> {ThisA, ThisU} = mem_get_btm_aus(TL, 0, 0), {(ThisA + A) band Mask , (ThisU + U) band Mask} end. mem_get_btm_aus([], A, U) -> {A, U}; mem_get_btm_aus([{BtmType, BtmA, BtmU} | Types], A, U) when BtmType == bif_timer; BtmType == accessor_bif_timer -> mem_get_btm_aus(Types, BtmA+A, BtmU+U); mem_get_btm_aus([_|Types], A, U) -> mem_get_btm_aus(Types, A, U).