Subject: Re: timedwork
To: Iain Hibbert <plunky@rya-online.net>
From: Andrew Doran <ad@netbsd.org>
List: tech-kern
Date: 01/20/2007 20:40:33
Hi Iain,

On Fri, Jan 19, 2007 at 10:16:11AM +0000, Iain Hibbert wrote:

> Here is my implementation, seems stable. Points to note:
> 
> - callout locking is changed to use splclock() as per documentation
> 
> - uses callout_setflag(c, f) rather than extend callout_init()
> - deprecates CALLOUT_INITIALIZER_SETFUNC

I don't like the idea of callout_setflag(), but until the guts of "struct
callout" are opaque to the consumers then it can't really be claimed to be
a stable API, so no argument from me.

>  #define	CALLOUT_LOCK(s)							\
>  do {									\
> -	s = splsched();							\
> +	s = splclock();							\
>  	simple_lock(&callout_slock);					\
>  } while (/*CONSTCOND*/0)

I'm not sure about this change. splsched() seems more appropriate to me as
it's a symbolic level.

> +callout_thread(void *arg)
...
> +		/*
> +		 * loop here forever, snoozing when there is no work
> +		 */
> +		if (CIRCQ_EMPTY(q)) {
> +			error = ltsleep(q, PUSER - 1, "callout", 0,
> +							    &callout_slock);
> +			if (error)
> +				panic("%s: ltsleep error=%d", __func__, error);
> +		} else {
> +			c = CIRCQ_FIRST(q);
> +			CIRCQ_REMOVE(&c->c_list);
> +
> +			c->c_flags &= ~CALLOUT_PENDING;
> +			c->c_flags |= (CALLOUT_FIRED|CALLOUT_INVOKING);
> +
> +			func = c->c_func;
> +			arg = c->c_arg;
> +
> +			CALLOUT_UNLOCK(s);
> +			(*func)(arg);
> +			CALLOUT_LOCK(s);
> +		}
> +	}

There's no real need to check for an error from ltsleep() here. KNFed it
would be:

			error = ltsleep(q, PUSER - 1, "callout", 0,
			    &callout_slock);

Actually, thinking about it this could lead to deadlock. Since the lock
order is sched_lock -> callout_slock, entering tsleep() at this point
would acquire sched_lock while we already hold callout_slock.

> softclock()
...
> -			c->c_flags = (c->c_flags  & ~CALLOUT_PENDING) |
> -			    (CALLOUT_FIRED|CALLOUT_INVOKING);
> -
> -			func = c->c_func;
> -			arg = c->c_arg;
> -
> -			CALLOUT_UNLOCK(s);
> -			(*func)(arg);
> -			CALLOUT_LOCK(s);
> +			if (c->c_flags & CALLOUT_THREAD) {
> +				CIRCQ_INSERT(&c->c_list, &timeout_work);
> +				wakeup(&timeout_work);
> +			} else {

There is the same lock order reversal here - callout_slock needs to be
dropped before calling wakeup().

Thanks,
Andrew