Skip to content

Commit 16228ef

Browse files
committed
itimer: resolve timer disarm deadlocks
Lockdep reports: <4>[ 180.883141] git/435 is trying to acquire lock: <5>[ 180.883538] 0xffffd0010366c010 (&s->signal_lock#2){+.-.}-{2:2}, at: kernel_raise_signal+0x6f/0x19f <4>[ 180.883538] but task is already holding lock: <5>[ 180.883982] 0xffff800507931e40 (&ev#2){+.-.}-{0:0}, at: _Z19timer_handle_eventsP5timer+0x160/0x2fa <4>[ 180.883982] <5>[ 180.900180] Possible unsafe locking scenario: <5>[ 180.900180] CPU0 CPU1 <5>[ 180.900180] ---- ---- <5>[ 180.900180] lock(&ev#2); <5>[ 180.900180] lock(&it.lock); <5>[ 180.900181] lock(&ev#2); <5>[ 180.900181] lock(&s->signal_lock#2); <5>[ 180.900181] *** DEADLOCK *** This happens because itimers firing require the signal lock (to send a signal). Having the signal_lock (or something in its chain of dependencies) while trying to cancel the timer results in a deadlock, and a loud complaint from lockdep. Fix it by finally adding a proper timer_cancel_try() which fails if the timer is running. itimer_disarm() requires a back-out loop now, which in exit's case needs to unlock+lock signal_lock. Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com>
1 parent 617c750 commit 16228ef

4 files changed

Lines changed: 52 additions & 6 deletions

File tree

‎kernel/include/onyx/itimer.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ struct process;
3636
__BEGIN_CDECLS
3737

3838
void itimer_init(struct process *p);
39-
void itimer_disarm(struct itimer *it);
39+
int itimer_disarm(struct itimer *it);
4040

4141
__END_CDECLS
4242
#endif

‎kernel/include/onyx/timer.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ struct timer;
3030
struct clockevent;
3131

3232
void timer_cancel_event(struct clockevent *ev);
33+
34+
/**
35+
* @brief Try to cancel a clockevent
36+
*
37+
* @param ev Event to cancel
38+
* @retval true if still cancelled
39+
* @return false if running
40+
*/
41+
bool timer_cancel_try(struct clockevent *ev);
3342
void timer_mod(struct clockevent *ev, hrtime_t future);
3443
struct clockevent
3544
{

‎kernel/kernel/exit.c‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,21 @@ static void exit_signal(struct process *task)
814814
}
815815

816816
for (int i = 0; i < ITIMER_COUNT; i++)
817-
itimer_disarm(&sig->timers[i]);
817+
{
818+
int err;
819+
again:
820+
err = itimer_disarm(&sig->timers[i]);
821+
if (err)
822+
{
823+
/* itimer_disarm found the timer running. This is problematic: the timer can try to
824+
* send a signal to ourselves, which grabs the signal_lock. To avoid this,
825+
* spin_unlock+relock and retry disarming. */
826+
WARN_ON(err != -EAGAIN);
827+
spin_unlock(&sighand->signal_lock);
828+
spin_lock(&sighand->signal_lock);
829+
goto again;
830+
}
831+
}
818832
}
819833

820834
/* Remove ourselves from every list we've been apart of. Sibblings, tasklist, pids, threads */

‎kernel/kernel/timer.cpp‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,18 @@ static void timer_spin_pending(struct timer *timer, struct clockevent *ev)
232232
cpu_relax();
233233
}
234234

235+
/**
236+
* @brief Try to cancel a clockevent
237+
*
238+
* @param ev Event to cancel
239+
* @retval true if still cancelled
240+
* @return false if running
241+
*/
242+
bool timer_cancel_try(struct clockevent *ev)
243+
{
244+
return timer_cancel_event_try(ev) == nullptr;
245+
}
246+
235247
void timer_cancel_event(struct clockevent *ev)
236248
{
237249
struct timer *timer;
@@ -419,13 +431,21 @@ int itimer::disarm()
419431
scoped_lock g{lock};
420432

421433
if (armed)
422-
timer_cancel_event(&ev);
434+
{
435+
if (!timer_cancel_try(&ev))
436+
{
437+
/* We can't form a dependency loop between signal_lock, itimer::lock and timer
438+
* cancelling (waiting). Thus, back out and try again later. */
439+
return -EAGAIN;
440+
}
441+
}
442+
423443
return 0;
424444
}
425445

426-
void itimer_disarm(struct itimer *it)
446+
int itimer_disarm(struct itimer *it)
427447
{
428-
it->disarm();
448+
return it->disarm();
429449
}
430450

431451
int sys_setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
@@ -460,7 +480,10 @@ int sys_setitimer(int which, const struct itimerval *new_value, struct itimerval
460480
auto &timer = current->sig->timers[which];
461481

462482
if (!initial_ns)
463-
st = timer.disarm();
483+
{
484+
while ((st = timer.disarm()) == -EAGAIN)
485+
cpu_relax();
486+
}
464487
else
465488
st = timer.arm(interval_ns, initial_ns);
466489

0 commit comments

Comments
 (0)