Re: callout_drain either broken or man page needs updating

From: Hans Petter Selasky <hps_at_selasky.org>
Date: Fri, 15 Jul 2016 06:25:41 +0200
On 07/15/16 05:45, Matthew Macy wrote:
> glebius last commit needs some further re-work.

Hi,

Glebius commit needs to be backed out, at least the API change that 
changes the return value when calling callout_stop() when the callout is 
scheduled and being serviced. Simply because there is code out there, 
like Mattew and others have discovered that is "refcounting" on the 
callout_reset() and expecting that a subsequent callout_stop() will 
return 1 to "unref".

If you consider this impossible, maybe a fourth return value is needed 
for CANCELLED and DRAINING .

Further, getting the callouts straight in the TCP stack is a matter of 
doing the locking correctly, which some has called "my magic bullet" and 
not the return values. I've proposed in the following revision 
https://svnweb.freebsd.org/changeset/base/302768 to add a new callout 
API that accepts a locking function so that the callout code can run its 
cancelled checks at the right place for situations where more than one 
lock is needed.

Consider this case:

> void
> tcp_timer_2msl(void *xtp)
> {
>         struct tcpcb *tp = xtp;
>         struct inpcb *inp;
>         CURVNET_SET(tp->t_vnet);
> #ifdef TCPDEBUG
>         int ostate;
>
>         ostate = tp->t_state;
> #endif
>         INP_INFO_RLOCK(&V_tcbinfo);
>         inp = tp->t_inpcb;
>         KASSERT(inp != NULL, ("%s: tp %p tp->t_inpcb == NULL", __func__, tp));
>         INP_WLOCK(inp);
>         tcp_free_sackholes(tp);
>         if (callout_pending(&tp->t_timers->tt_2msl) ||
>             !callout_active(&tp->t_timers->tt_2msl)) {

Here we have custom in-house race check that doesn't affect the return 
value of callout_reset() nor callout_stop().

>                 INP_WUNLOCK(tp->t_inpcb);
>                 INP_INFO_RUNLOCK(&V_tcbinfo);
>                 CURVNET_RESTORE();
>                 return;


I propose the following solution:

>
> static void
> tcp_timer_2msl_lock(void *xtp, int do_lock)
> {
> 	struct tcpcb *tp = xtp;
> 	struct inpcb *inp;
>
> 	inp = tp->t_inpcb;
>
> 	if (do_lock) {
> 		CURVNET_SET(tp->t_vnet);
> 		INP_INFO_RLOCK(&V_tcbinfo);
> 		INP_WLOCK(inp);
> 	} else {
> 		INP_WUNLOCK(inp);
> 		INP_INFO_RUNLOCK(&V_tcbinfo);
> 		CURVNET_RESTORE();
> 	}
> }
>

callout_init_lock_function(&callout, &tcp_timer_2msl_lock, 
CALLOUT_RETURNUNLOCKED);

Then in softclock_call_cc() it will look like this:

>
> 	CC_UNLOCK(cc);
> 	if (c_lock != NULL) {
> 		if (have locking function)
> 			tcp_timer_2msl_lock(c_arg, 1);
> 		else
> 			class->lc_lock(c_lock, lock_status);
> 		/*
> 		 * The callout may have been cancelled
> 		 * while we switched locks.
> 		 */

Actually "CC_LOCK(cc)" should be in-front of cc_exec_cancel() to avoid 
races testing, setting and clearing this variable, like done in hps_head.

> 		if (cc_exec_cancel(cc, direct)) {
> 			if (have locking function)
> 				tcp_timer_2msl_lock(c_arg, 0);
> 			else
> 				class->lc_unlock(c_lock);
> 			goto skip;
>                }
 >                cc_exec_cancel(cc, direct) = true;
>
> ....
>
> skip:
> 	if ((c_iflags & CALLOUT_RETURNUNLOCKED) == 0) {
> 		if (have locking function)
> 			...
> 		else
> 			class->lc_unlock(c_lock);
> 	}

The whole point about this is to make the the cancelled check atomic.

1) Lock TCP
2) Lock CC_LOCK()
3) change callout state

--HPS
Received on Fri Jul 15 2016 - 02:21:50 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:41:06 UTC