freebsd_network: Refactor callout_stop and functions which invoke it.

* Create an internal variant which accepts a "bool locked" parameter.
   Use this from callout_reset instead of invoking it before locking,
   and then relocking afterwards. Eliminates some possible (though,
   so far as I know, benign) races.

 * mtx_assert always, even if the callout is not active. (Requirement
   notated in the comment.)

 * In callout_reset, do not invoke callout_stop at all unless we are
   cancelling; in cases of mere reschedules, simply change c_due
   and notify the callout thread.

 * While at it, use list_init_etc in init_callout; no-op change,
   but keeps things clean.

This should fix #18338 (specifically the change to never invoke
callout_stop when merely rescheduling, not cancelling.)
This commit is contained in:
Augustin Cavalier
2023-04-01 00:51:22 -04:00
parent f863f473b5
commit 70b4d59f18
+70 -46
View File
@@ -120,7 +120,7 @@ callout_thread(void* /*data*/)
status_t status_t
init_callout(void) init_callout(void)
{ {
list_init(&sTimers); list_init_etc(&sTimers, offsetof(struct callout, c_link));
sTimeout = B_INFINITE_TIMEOUT; sTimeout = B_INFINITE_TIMEOUT;
status_t status = B_OK; status_t status = B_OK;
@@ -186,64 +186,34 @@ callout_init_mtx(struct callout *c, struct mtx *mtx, int flags)
} }
int static int
callout_reset(struct callout *c, int _ticks, void (*func)(void *), void *arg) _callout_stop(struct callout *c, bool drain, bool locked = false)
{ {
int cancelled = callout_stop(c); TRACE("_callout_stop %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
MutexLocker locker(sLock); MutexLocker locker;
if (!locked)
locker.SetTo(sLock, false);
c->c_func = func; if (!drain && c->c_mtx != NULL) {
c->c_arg = arg; // The documentation for callout_stop() confirms any associated locks
// must be held when invoking it. We depend on this behavior for
TRACE("callout_reset %p, func %p, arg %p\n", c, c->c_func, c->c_arg); // synchronization with the callout thread, which can modify c_due
// with only the callout's lock held.
if (_ticks >= 0) { mtx_assert(c->c_mtx, MA_OWNED);
// reschedule or add this timer
if (c->c_due <= 0)
list_add_item(&sTimers, c);
c->c_due = system_time() + TICKS_2_USEC(_ticks);
// notify timer about the change if necessary
if (sTimeout > c->c_due)
release_sem(sWaitSem);
} }
return (cancelled == -1) ? 0 : 1;
}
int
callout_schedule(struct callout *callout, int _ticks)
{
return callout_reset(callout, _ticks, callout->c_func, callout->c_arg);
}
int
_callout_stop_safe(struct callout *c, int safe)
{
if (c == NULL)
return -1;
TRACE("_callout_stop_safe %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
MutexLocker locker(sLock);
int ret = -1; int ret = -1;
if (callout_active(c)) { if (callout_active(c)) {
ret = 0; ret = 0;
if (!safe && c->c_mtx != NULL && c->c_due == 0) { if (!drain && c->c_mtx != NULL && c->c_due == 0) {
mtx_assert(c->c_mtx, MA_OWNED);
// The callout is active, but c_due == 0 and we hold the locks: this // The callout is active, but c_due == 0 and we hold the locks: this
// means the callout thread has dequeued it and is waiting for c_mtx. // means the callout thread has dequeued it and is waiting for c_mtx.
// Clear c_due to signal the callout thread. // Clear c_due to signal the callout thread.
c->c_due = -1; c->c_due = -1;
ret = 1; ret = 1;
} }
if (safe) { if (drain) {
locker.Unlock(); locker.Unlock();
while (callout_active(c)) while (callout_active(c))
snooze(100); snooze(100);
@@ -256,11 +226,65 @@ _callout_stop_safe(struct callout *c, int safe)
// this timer is scheduled, cancel it // this timer is scheduled, cancel it
list_remove_item(&sTimers, c); list_remove_item(&sTimers, c);
c->c_due = 0; c->c_due = -1;
return (ret == -1) ? 1 : ret; return (ret == -1) ? 1 : ret;
} }
int
callout_reset(struct callout *c, int _ticks, void (*func)(void *), void *arg)
{
MutexLocker locker(sLock);
TRACE("callout_reset %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
c->c_func = func;
c->c_arg = arg;
if (_ticks < 0) {
int stopped = -1;
if (c->c_due > 0)
stopped = _callout_stop(c, 0, true);
return (stopped == -1) ? 0 : 1;
}
int rescheduled = 0;
if (_ticks >= 0) {
// reschedule or add this timer
if (c->c_due <= 0) {
list_add_item(&sTimers, c);
} else {
rescheduled = 1;
}
c->c_due = system_time() + TICKS_2_USEC(_ticks);
// notify timer about the change if necessary
if (sTimeout > c->c_due)
release_sem(sWaitSem);
}
return rescheduled;
}
int
_callout_stop_safe(struct callout *c, int safe)
{
if (c == NULL)
return -1;
return _callout_stop(c, safe);
}
int
callout_schedule(struct callout *callout, int _ticks)
{
return callout_reset(callout, _ticks, callout->c_func, callout->c_arg);
}
int int
callout_pending(struct callout *c) callout_pending(struct callout *c)
{ {