Skip to content

Commit b2d87da

Browse files
RobinMorissetrickard-greenjcpetruzza
committed
erts: Pause and resume BIF timers during suspend_process
When a process is suspended via erlang:suspend_process/2, its proc timer was already paused, but BIF timers (erlang:start_timer/3, erlang:send_after/3) were not. This meant timers could fire and deliver messages to a suspended process, which is problematic for debuggers that need a consistent frozen state. Add infrastructure to pause all BIF timers targeting a suspended process and recreate them on resume: - New types ErtsPausedBifTimer and ErtsPausedBifTimers to hold paused timer state including remaining time and message. - New Process field paused_bif_timers, initialized to NULL. - erts_pause_bif_timers(): iterates the process BIF timer tree, snapshots each timer into a paused record, and cancels the original. Handles multiple suspends via a reference count. - erts_resume_paused_bif_timers(): recreates all paused timers with their saved remaining time when the last suspender resumes. - erts_destroy_paused_bif_timers(): cleans up on process exit. - Handle BIF timers created after suspension: setup_bif_timer checks paused_bif_timers and directly creates a paused record instead of inserting into the timer tree. - Pass correct lock info through cancel_bif_timer/access_btm to avoid unnecessary lock/unlock when BTM lock is already held. Integration points: - erts_internal_suspend_process_2: acquire ERTS_PROC_LOCK_BTM, call erts_pause_bif_timers after pausing proc timer. - activate_suspend_monitor: call erts_pause_bif_timers via signal. - Signal handling (resume): call erts_resume_paused_bif_timers. - Process termination: call erts_destroy_paused_bif_timers. - Process creation: initialize paused_bif_timers to NULL. Co-authored-by: Rickard Green <rickard@erlang.org> Co-authored-by: Daniel Gorin <danielgo@meta.com>
1 parent f99fe47 commit b2d87da

6 files changed

Lines changed: 423 additions & 23 deletions

File tree

erts/emulator/beam/erl_hl_timer.c

Lines changed: 201 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,17 @@ typedef struct {
214214
Sint count;
215215
} ErtsPausedProcTimer;
216216

217+
typedef struct ErtsPausedBifTimer_ {
218+
ErtsBifTimer tmr;
219+
Sint64 time_left_in_msec;
220+
struct ErtsPausedBifTimer_ *next;
221+
} ErtsPausedBifTimer;
222+
223+
struct ErtsPausedBifTimers_ {
224+
ErtsPausedBifTimer *list;
225+
Sint count;
226+
};
227+
217228
typedef ErtsTimer *(*ErtsCreateTimerFunc)(ErtsSchedulerData *esdp,
218229
ErtsMonotonicTime timeout_pos,
219230
int short_time, ErtsTmrType type,
@@ -966,7 +977,7 @@ create_tw_timer(ErtsSchedulerData *esdp,
966977
}
967978

968979
/*
969-
* Paused proc timers
980+
* Paused bif timers
970981
*/
971982

972983
static ERTS_INLINE Sint64
@@ -980,6 +991,31 @@ time_left_for_timer_in_msec(ErtsTimer* tmr, ErtsSchedulerData *esdp)
980991
return get_time_left(esdp, timeout_pos);
981992
}
982993

994+
static ERTS_INLINE void
995+
create_paused_bif_timer(ErtsBifTimer *tmr, Process *c_p, ErtsSchedulerData *esdp)
996+
{
997+
ErtsPausedBifTimer *pbtmr = erts_alloc(ERTS_ALC_T_PAUSED_TIMER,
998+
sizeof(ErtsPausedBifTimer));
999+
1000+
ASSERT(c_p->paused_bif_timers->count > 0);
1001+
1002+
ASSERT(!(tmr->type.head.roflgs & ERTS_TMR_ROFLG_PAUSED));
1003+
1004+
init_btm_message(&pbtmr->tmr, tmr->btm.message);
1005+
1006+
pbtmr->tmr.type.head.roflgs = tmr->type.head.roflgs | ERTS_TMR_ROFLG_PAUSED;
1007+
erts_atomic32_init_nob(&pbtmr->tmr.type.head.refc, 1);
1008+
pbtmr->tmr.type.head.receiver.proc = tmr->type.head.receiver.proc;
1009+
1010+
pbtmr->time_left_in_msec = time_left_for_timer_in_msec((ErtsTimer *) tmr, esdp);
1011+
1012+
pbtmr->next = c_p->paused_bif_timers->list;
1013+
c_p->paused_bif_timers->list = pbtmr;
1014+
}
1015+
1016+
/*
1017+
* Paused proc timers
1018+
*/
9831019
static ERTS_INLINE ErtsPausedProcTimer *
9841020
create_paused_proc_timer(Process *c_p)
9851021
{
@@ -1739,7 +1775,7 @@ continue_cancel_ptimer(ErtsSchedulerData *esdp, ErtsTimer *tmr)
17391775
return;
17401776
}
17411777

1742-
if (esdp->no != sid)
1778+
if (esdp->no != sid)
17431779
queue_canceled_timer(esdp, sid, tmr);
17441780
else
17451781
cleanup_sched_local_canceled_timer(esdp, tmr);
@@ -1789,7 +1825,16 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
17891825
Process *proc = erts_pid2proc_opt(c_p, ERTS_PROC_LOCK_MAIN,
17901826
rcvr, ERTS_PROC_LOCK_BTM,
17911827
ERTS_P2P_FLG_INC_REFC);
1792-
if (!proc) {
1828+
if (proc) {
1829+
tmr->type.head.receiver.proc = proc;
1830+
if (proc->paused_bif_timers) {
1831+
create_paused_bif_timer(tmr, proc, esdp);
1832+
} else {
1833+
proc_btm_rbt_insert(&proc->bif_timers, tmr);
1834+
}
1835+
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
1836+
}
1837+
if (!proc || proc->paused_bif_timers) {
17931838
if (tmr->btm.tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
17941839
btm_rbt_delete(&esdp->timer_service->btm_tree, tmr);
17951840
tmr->btm.tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
@@ -1802,11 +1847,6 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
18021847
hlt_delete_timer(esdp, &tmr->type.hlt);
18031848
timer_destroy((ErtsTimer *) tmr, twheel, 1);
18041849
}
1805-
else {
1806-
proc_btm_rbt_insert(&proc->bif_timers, tmr);
1807-
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
1808-
tmr->type.head.receiver.proc = proc;
1809-
}
18101850
}
18111851

18121852
ERTS_BIF_PREP_RET(ret, ref);
@@ -1819,11 +1859,12 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
18191859
}
18201860

18211861
static int
1822-
cancel_bif_timer(ErtsBifTimer *tmr)
1862+
cancel_bif_timer(ErtsBifTimer *tmr, ErtsProcLocks c_p_locks)
18231863
{
18241864
erts_aint_t state;
18251865
Uint32 roflgs;
18261866
int res;
1867+
int proc_lock_btm_held = c_p_locks & ERTS_PROC_LOCK_BTM;
18271868

18281869
state = erts_atomic32_cmpxchg_acqb(&tmr->btm.state,
18291870
ERTS_TMR_STATE_CANCELED,
@@ -1843,7 +1884,9 @@ cancel_bif_timer(ErtsBifTimer *tmr)
18431884
proc = tmr->type.head.receiver.proc;
18441885
ERTS_HLT_ASSERT(!(tmr->type.head.roflgs & ERTS_TMR_ROFLG_REG_NAME));
18451886

1846-
erts_proc_lock(proc, ERTS_PROC_LOCK_BTM);
1887+
if (!proc_lock_btm_held) {
1888+
erts_proc_lock(proc, ERTS_PROC_LOCK_BTM);
1889+
}
18471890
/*
18481891
* If process is exiting, let it clean up
18491892
* the btm tree by itself (it may be in
@@ -1855,14 +1898,16 @@ cancel_bif_timer(ErtsBifTimer *tmr)
18551898
tmr->btm.proc_tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
18561899
res = 1;
18571900
}
1858-
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
1901+
if (!proc_lock_btm_held) {
1902+
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
1903+
}
18591904
}
18601905

18611906
return res;
18621907
}
18631908

18641909
static ERTS_INLINE Sint64
1865-
access_btm(ErtsBifTimer *tmr, Uint32 sid, ErtsSchedulerData *esdp, int cancel)
1910+
access_btm(ErtsBifTimer *tmr, Uint32 sid, ErtsSchedulerData *esdp, int cancel, ErtsProcLocks c_p_locks)
18661911
{
18671912
int cncl_res;
18681913
Sint64 time_left;
@@ -1884,7 +1929,7 @@ access_btm(ErtsBifTimer *tmr, Uint32 sid, ErtsSchedulerData *esdp, int cancel)
18841929
return -1;
18851930
}
18861931

1887-
cncl_res = cancel_bif_timer(tmr);
1932+
cncl_res = cancel_bif_timer(tmr, c_p_locks);
18881933
if (!cncl_res)
18891934
return -1;
18901935

@@ -2048,11 +2093,6 @@ access_sched_local_btm(Process *c_p, Eterm pid,
20482093

20492094
tmr = btm_rbt_lookup(srv->btm_tree, trefn);
20502095

2051-
time_left = access_btm(tmr, (Uint32) esdp->no, esdp, cancel);
2052-
2053-
if (async && !info)
2054-
return am_ok;
2055-
20562096
if (c_p) {
20572097
proc = c_p;
20582098
proc_locks = ERTS_PROC_LOCK_MAIN;
@@ -2062,6 +2102,11 @@ access_sched_local_btm(Process *c_p, Eterm pid,
20622102
proc_locks = 0;
20632103
}
20642104

2105+
time_left = access_btm(tmr, (Uint32) esdp->no, esdp, cancel, proc_locks);
2106+
2107+
if (async && !info)
2108+
return am_ok;
2109+
20652110
if (!async) {
20662111
if (c_p) {
20672112
if (!info)
@@ -2135,7 +2180,7 @@ try_access_sched_remote_btm(ErtsSchedulerData *esdp,
21352180
if (!tmr)
21362181
return 0;
21372182

2138-
time_left = access_btm(tmr, sid, esdp, cancel);
2183+
time_left = access_btm(tmr, sid, esdp, cancel, ERTS_PROC_LOCK_MAIN);
21392184

21402185
if (!info)
21412186
*resp = am_ok;
@@ -2805,6 +2850,59 @@ erts_pause_proc_timer(Process *c_p)
28052850
erts_atomic_set_nob(&c_p->common.timer, (erts_aint_t) pptmr);
28062851
}
28072852

2853+
static ERTS_INLINE int
2854+
add_bif_timer_to_worklist(ErtsBifTimer *tmr, void *arg, Sint reds)
2855+
{
2856+
ErtsWStack *s = (ErtsWStack *) arg;
2857+
WSTACK_PUSH((*s), (UWord) tmr);
2858+
return 1;
2859+
}
2860+
2861+
void
2862+
erts_pause_bif_timers(Process *c_p, ErtsProcLocks c_p_locks)
2863+
{
2864+
ErtsSchedulerData *esdp = erts_proc_sched_data(c_p);
2865+
int must_release_btm_lock = 0;
2866+
2867+
ASSERT(c_p_locks & ERTS_PROC_LOCK_MAIN);
2868+
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(c_p));
2869+
2870+
if (!(c_p_locks & ERTS_PROC_LOCK_BTM)) {
2871+
erts_proc_lock(c_p, ERTS_PROC_LOCK_BTM);
2872+
c_p_locks |= ERTS_PROC_LOCK_BTM;
2873+
must_release_btm_lock = 1;
2874+
}
2875+
ERTS_LC_ASSERT(ERTS_PROC_LOCK_BTM & erts_proc_lc_my_proc_locks(c_p));
2876+
2877+
if (c_p->paused_bif_timers) {
2878+
ASSERT(c_p->paused_bif_timers->count > 0);
2879+
c_p->paused_bif_timers->count++;
2880+
}
2881+
else {
2882+
WSTACK_DECLARE(bif_timers_worklist);
2883+
c_p->paused_bif_timers = erts_alloc(ERTS_ALC_T_PAUSED_TIMER,
2884+
sizeof(ErtsPausedBifTimers));
2885+
c_p->paused_bif_timers->list = NULL;
2886+
c_p->paused_bif_timers->count = 1;
2887+
/*
2888+
* It would be better in theory to use a yielding version of foreach here,
2889+
* but it would be a lot more complex, and since this function is only
2890+
* used by the debugger potential loss in responsiveness is acceptable.
2891+
*/
2892+
proc_btm_rbt_foreach(c_p->bif_timers, add_bif_timer_to_worklist, &bif_timers_worklist);
2893+
while (!WSTACK_ISEMPTY(bif_timers_worklist)) {
2894+
ErtsBifTimer *tmr = (ErtsBifTimer *) WSTACK_POP(bif_timers_worklist);
2895+
Uint32 sid = (tmr->type.head.roflgs & ERTS_TMR_ROFLG_SID_MASK);
2896+
create_paused_bif_timer(tmr, c_p, esdp);
2897+
access_btm(tmr, sid, esdp, /* cancel = */ 1, c_p_locks);
2898+
}
2899+
WSTACK_DESTROY(bif_timers_worklist);
2900+
}
2901+
if (must_release_btm_lock) {
2902+
erts_proc_unlock(c_p, ERTS_PROC_LOCK_BTM);
2903+
}
2904+
}
2905+
28082906
void
28092907
erts_resume_paused_proc_timer(Process *c_p)
28102908
{
@@ -2835,6 +2933,90 @@ erts_resume_paused_proc_timer(Process *c_p)
28352933
}
28362934
}
28372935

2936+
void
2937+
erts_resume_paused_bif_timers(Process *c_p)
2938+
{
2939+
ErtsSchedulerData *esdp = erts_proc_sched_data(c_p);
2940+
ErtsPausedBifTimer *paused_bif_timer;
2941+
2942+
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(c_p));
2943+
ERTS_LC_ASSERT(!(ERTS_PROC_LOCK_BTM & erts_proc_lc_my_proc_locks(c_p)));
2944+
erts_proc_lock(c_p, ERTS_PROC_LOCK_BTM);
2945+
2946+
ASSERT(c_p->paused_bif_timers);
2947+
ASSERT(c_p->paused_bif_timers->count > 0);
2948+
2949+
if (--c_p->paused_bif_timers->count > 0) {
2950+
/* Other suspends still active; leave them paused... */
2951+
erts_proc_unlock(c_p, ERTS_PROC_LOCK_BTM);
2952+
return;
2953+
}
2954+
2955+
paused_bif_timer = c_p->paused_bif_timers->list;
2956+
2957+
while (paused_bif_timer != NULL) {
2958+
ErtsPausedBifTimer *old_timer = paused_bif_timer;
2959+
ErtsMonotonicTime timeout_pos;
2960+
ErtsBifTimer *tmr;
2961+
Eterm ref;
2962+
UWord tmo;
2963+
ErtsCreateTimerFunc create_timer;
2964+
void *hp;
2965+
2966+
tmo = (UWord) paused_bif_timer->time_left_in_msec;
2967+
timeout_pos = get_timeout_pos(erts_get_monotonic_time(esdp), (ErtsMonotonicTime) tmo);
2968+
2969+
/* Lifted from setup_bif_timer */
2970+
hp = HAlloc(c_p, ERTS_REF_THING_SIZE);
2971+
ref = erts_sched_make_ref_in_buffer(esdp, hp);
2972+
create_timer = (tmo < ERTS_TIMER_WHEEL_MSEC
2973+
? create_tw_timer
2974+
: create_hl_timer);
2975+
tmr = (ErtsBifTimer *) create_timer(esdp, timeout_pos,
2976+
tmo < ERTS_BIF_TIMER_SHORT_TIME, ERTS_TMR_BIF,
2977+
NULL, c_p->common.id, paused_bif_timer->tmr.btm.message,
2978+
internal_ordinary_ref_numbers(ref),
2979+
NULL, NULL);
2980+
proc_btm_rbt_insert(&c_p->bif_timers, tmr);
2981+
tmr->type.head.receiver.proc = c_p;
2982+
2983+
check_canceled_queue(esdp, esdp->timer_service);
2984+
2985+
paused_bif_timer = paused_bif_timer->next;
2986+
erts_free(ERTS_ALC_T_PAUSED_TIMER, old_timer);
2987+
2988+
/* We correctly decrement the refc in the pausing of the bif timer, but for some reason recreating it does not increment it */
2989+
/* So we do it manually here */
2990+
erts_proc_inc_refc(c_p);
2991+
}
2992+
2993+
erts_free(ERTS_ALC_T_PAUSED_TIMER, c_p->paused_bif_timers);
2994+
c_p->paused_bif_timers = NULL;
2995+
2996+
erts_proc_unlock(c_p, ERTS_PROC_LOCK_BTM);
2997+
}
2998+
2999+
void
3000+
erts_destroy_paused_bif_timers(Process *c_p)
3001+
{
3002+
ErtsPausedBifTimer *ptmr, *free_ptmr;
3003+
3004+
erts_proc_lock(c_p, ERTS_PROC_LOCK_BTM);
3005+
3006+
ptmr = c_p->paused_bif_timers->list;
3007+
3008+
while (ptmr) {
3009+
free_ptmr = ptmr;
3010+
ptmr = ptmr->next;
3011+
erts_free(ERTS_ALC_T_PAUSED_TIMER, free_ptmr);
3012+
}
3013+
3014+
erts_free(ERTS_ALC_T_PAUSED_TIMER, c_p->paused_bif_timers);
3015+
3016+
c_p->paused_bif_timers = NULL;
3017+
erts_proc_unlock(c_p, ERTS_PROC_LOCK_BTM);
3018+
}
3019+
28383020
void
28393021
erts_set_port_timer(Port *c_prt, Sint64 tmo)
28403022
{

erts/emulator/beam/erl_hl_timer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#define ERL_HL_TIMER_H__
2525

2626
typedef struct ErtsBifTimer_ ErtsBifTimers;
27+
typedef struct ErtsPausedBifTimers_ ErtsPausedBifTimers;
2728
typedef struct ErtsHLTimerService_ ErtsHLTimerService;
2829

2930
#include "sys.h"
@@ -57,6 +58,9 @@ void erts_set_proc_timer_uword(Process *, UWord);
5758
void erts_cancel_proc_timer(Process *);
5859
void erts_pause_proc_timer(Process *);
5960
void erts_resume_paused_proc_timer(Process *);
61+
void erts_pause_bif_timers(Process *, ErtsProcLocks);
62+
void erts_resume_paused_bif_timers(Process *);
63+
void erts_destroy_paused_bif_timers(Process *c_p);
6064
void erts_set_port_timer(Port *, Sint64);
6165
void erts_cancel_port_timer(Port *);
6266
Sint64 erts_read_port_timer(Port *);

erts/emulator/beam/erl_proc_sig_queue.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5438,6 +5438,7 @@ activate_suspend_monitor(Process *c_p, ErtsMonitorSuspend *msp)
54385438
erts_aint_t mstate;
54395439

54405440
erts_pause_proc_timer(c_p);
5441+
erts_pause_bif_timers(c_p, ERTS_PROC_LOCK_MAIN);
54415442
mstate = erts_atomic_read_bor_acqb(&msp->state,
54425443
ERTS_MSUSPEND_STATE_FLG_ACTIVE);
54435444
ASSERT(!(mstate & ERTS_MSUSPEND_STATE_FLG_ACTIVE)); (void) mstate;
@@ -6610,6 +6611,7 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
66106611
if (mstate & ERTS_MSUSPEND_STATE_FLG_ACTIVE) {
66116612
erts_resume(c_p, ERTS_PROC_LOCK_MAIN);
66126613
erts_resume_paused_proc_timer(c_p);
6614+
erts_resume_paused_bif_timers(c_p);
66136615
}
66146616
break;
66156617
}

0 commit comments

Comments
 (0)