Index: kern/kern_timeout.c =================================================================== --- kern/kern_timeout.c +++ kern/kern_timeout.c @@ -136,6 +136,7 @@ */ struct cc_exec { struct callout *cc_curr; + void (*cc_drain)(void *); #ifdef SMP void (*ce_migration_func)(void *); void *ce_migration_arg; @@ -170,6 +171,7 @@ #define callout_migrating(c) ((c)->c_iflags & CALLOUT_DFRMIGRATION) #define cc_exec_curr(cc, dir) cc->cc_exec_entity[dir].cc_curr +#define cc_exec_drain(cc, dir) cc->cc_exec_entity[dir].cc_drain #define cc_exec_next(cc) cc->cc_next #define cc_exec_cancel(cc, dir) cc->cc_exec_entity[dir].cc_cancel #define cc_exec_waiting(cc, dir) cc->cc_exec_entity[dir].cc_waiting @@ -679,6 +681,7 @@ cc_exec_curr(cc, direct) = c; cc_exec_cancel(cc, direct) = false; + cc_exec_drain(cc, direct) = NULL; CC_UNLOCK(cc); if (c_lock != NULL) { class->lc_lock(c_lock, lock_status); @@ -744,6 +747,15 @@ CC_LOCK(cc); KASSERT(cc_exec_curr(cc, direct) == c, ("mishandled cc_curr")); cc_exec_curr(cc, direct) = NULL; + if (cc_exec_drain(cc, direct)) { + void (*drain)(void *); + + drain = cc_exec_drain(cc, direct); + cc_exec_drain(cc, direct) = NULL; + CC_UNLOCK(cc); + drain(c_arg); + CC_LOCK(cc); + } if (cc_exec_waiting(cc, direct)) { /* * There is someone waiting for the @@ -1145,7 +1157,7 @@ } int -_callout_stop_safe(struct callout *c, int safe) +_callout_stop_safe(struct callout *c, int safe, void (*drain)(void *)) { struct callout_cpu *cc, *old_cc; struct lock_class *class; @@ -1225,20 +1237,35 @@ * stop it by other means however. */ if (!(c->c_iflags & CALLOUT_PENDING)) { - c->c_flags &= ~CALLOUT_ACTIVE; - /* * 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. */ if (cc_exec_curr(cc, direct) != c) { + int act; CTR3(KTR_CALLOUT, "failed to stop %p func %p arg %p", c, c->c_func, c->c_arg); + c->c_flags &= ~CALLOUT_ACTIVE; + act = c->c_flags & CALLOUT_ACTIVE; CC_UNLOCK(cc); + if (drain && act) { + /* + * Call the drain function, it finished. + * Though the only way I can think of we + * get in here is if the caller did not + * use proper locking and clearing ACTIVE. + */ + drain(c->c_arg); + } if (sq_locked) sleepq_release(&cc_exec_waiting(cc, direct)); return (0); } + c->c_flags &= ~CALLOUT_ACTIVE; if (safe) { /* @@ -1352,10 +1379,16 @@ CTR3(KTR_CALLOUT, "postponing stop %p func %p arg %p", c, c->c_func, c->c_arg); CC_UNLOCK(cc); + if (drain) { + cc_exec_drain(cc, direct) = drain; + } return (0); } CTR3(KTR_CALLOUT, "failed to stop %p func %p arg %p", c, c->c_func, c->c_arg); + if (drain) { + cc_exec_drain(cc, direct) = drain; + } CC_UNLOCK(cc); KASSERT(!sq_locked, ("sleepqueue chain still locked")); return (0); Index: sys/callout.h =================================================================== --- sys/callout.h +++ sys/callout.h @@ -81,7 +81,7 @@ */ #define callout_active(c) ((c)->c_flags & CALLOUT_ACTIVE) #define callout_deactivate(c) ((c)->c_flags &= ~CALLOUT_ACTIVE) -#define callout_drain(c) _callout_stop_safe(c, 1) +#define callout_drain(c) _callout_stop_safe(c, 1, NULL) void callout_init(struct callout *, int); void _callout_init_lock(struct callout *, struct lock_object *, int); #define callout_init_mtx(c, mtx, flags) \ @@ -119,10 +119,11 @@ int callout_schedule_on(struct callout *, int, int); #define callout_schedule_curcpu(c, on_tick) \ callout_schedule_on((c), (on_tick), PCPU_GET(cpuid)) -#define callout_stop(c) _callout_stop_safe(c, 0) -int _callout_stop_safe(struct callout *, int); +#define callout_stop(c) _callout_stop_safe(c, 0, NULL) +int _callout_stop_safe(struct callout *, int, void (*)(void *)); void callout_process(sbintime_t now); - +#define callout_async_drain(c, d) \ + _callout_stop_safe(c, 0, d) #endif #endif /* _SYS_CALLOUT_H_ */