Index: share/man/man9/timeout.9 =================================================================== --- share/man/man9/timeout.9 +++ share/man/man9/timeout.9 @@ -29,7 +29,7 @@ .\" .\" $FreeBSD$ .\" -.Dd September 14, 2015 +.Dd July 4, 2016 .Dt TIMEOUT 9 .Os .Sh NAME @@ -247,6 +247,10 @@ negative one is returned. If the callout is currently being serviced and cannot be stopped, then zero will be returned. +If the callout is currently being serviced and cannot be stopped, and at the +same time a next invocation of the same callout is also scheduled, then +.Fn callout_stop +unschedules the next run and returns zero. If the callout has an associated lock, then that lock must be held when this function is called. .Pp @@ -814,7 +818,7 @@ .Fn callout_drain functions return a value of one if the callout was still pending when it was called, a zero if the callout could not be stopped and a negative one is it -was either not running or haas already completed. +was either not running or has already completed. The .Fn timeout function returns a Index: sys/kern/kern_timeout.c =================================================================== --- sys/kern/kern_timeout.c +++ sys/kern/kern_timeout.c @@ -681,7 +681,7 @@ if (c->c_iflags & CALLOUT_LOCAL_ALLOC) c->c_iflags = CALLOUT_LOCAL_ALLOC; else - c->c_iflags &= ~CALLOUT_PENDING; + c->c_iflags &= ~(CALLOUT_PENDING | CALLOUT_PROCESSED); cc_exec_curr(cc, direct) = c; cc_exec_cancel(cc, direct) = false; @@ -1088,6 +1088,7 @@ LIST_REMOVE(c, c_links.le); } else { TAILQ_REMOVE(&cc->cc_expireq, c, c_links.tqe); + c->c_iflags &= ~CALLOUT_PROCESSED; } cancelled = 1; c->c_iflags &= ~ CALLOUT_PENDING; @@ -1166,7 +1167,7 @@ struct callout_cpu *cc, *old_cc; struct lock_class *class; int direct, sq_locked, use_lock; - int not_on_a_list; + int cancelled, not_on_a_list; if ((flags & CS_DRAIN) != 0) WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, c->c_lock, @@ -1236,28 +1237,14 @@ } /* - * If the callout isn't pending, it's not on the queue, so - * don't attempt to remove it from the queue. We can try to - * stop it by other means however. + * If the callout is running, try to stop it or drain it. */ - if (!(c->c_iflags & CALLOUT_PENDING)) { + if (cc_exec_curr(cc, direct) == c) { /* - * If it wasn't on the queue and it isn't the current - * callout, then we can't stop it, so just bail. - * It probably has already been run (if locking - * is properly done). You could get here if the caller - * calls stop twice in a row for example. The second - * call would fall here without CALLOUT_ACTIVE set. + * Succeed we to stop it or not, we must clear the + * active flag - this is what API users expect. */ c->c_flags &= ~CALLOUT_ACTIVE; - if (cc_exec_curr(cc, direct) != c) { - CTR3(KTR_CALLOUT, "failed to stop %p func %p arg %p", - c, c->c_func, c->c_arg); - CC_UNLOCK(cc); - if (sq_locked) - sleepq_release(&cc_exec_waiting(cc, direct)); - return (-1); - } if ((flags & CS_DRAIN) != 0) { /* @@ -1383,13 +1370,21 @@ if (drain) { cc_exec_drain(cc, direct) = drain; } - CC_UNLOCK(cc); KASSERT(!sq_locked, ("sleepqueue chain still locked")); - return (0); - } + cancelled = 0; + } else + cancelled = 1; + if (sq_locked) sleepq_release(&cc_exec_waiting(cc, direct)); + if ((c->c_iflags & CALLOUT_PENDING) == 0) { + CTR3(KTR_CALLOUT, "failed to stop %p func %p arg %p", + c, c->c_func, c->c_arg); + CC_UNLOCK(cc); + return (cancelled); + } + c->c_iflags &= ~CALLOUT_PENDING; c->c_flags &= ~CALLOUT_ACTIVE; @@ -1402,11 +1397,12 @@ LIST_REMOVE(c, c_links.le); } else { TAILQ_REMOVE(&cc->cc_expireq, c, c_links.tqe); + c->c_iflags &= ~CALLOUT_PROCESSED; } } callout_cc_del(c, cc); CC_UNLOCK(cc); - return (1); + return (cancelled); } void