Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 10 Jul 2002 11:15:13 -0700 (PDT)
From:      Archie Cobbs <archie@dellroad.org>
To:        John Baldwin <jhb@FreeBSD.org>
Cc:        davidx@viasoft.com.cn, freebsd-arch@FreeBSD.org, julian@elischer.org
Subject:   Re: Timeout and SMP race
Message-ID:  <200207101815.g6AIFDm28655@arch20m.dellroad.org>
In-Reply-To: <XFMail.20020710132809.jhb@FreeBSD.org> "from John Baldwin at Jul 10, 2002 01:28:09 pm"

next in thread | previous in thread | raw e-mail | index | archive | help

[ NOTE: I'm moving this discussion to freebsd-arch@freebsd.org ]

John Baldwin writes:
> > What do you think of the idea of letting the timer code (optionally)
> > handle all the locking and race conditions?
> 
> I'm not sure it can in a clean fashion since of the few cases I've known
> so far each client needs a customized solution.  I am open to ideas though.
> I'm also open to some redesign of how callouts work to begin with (maybe
> using other threads than the one running softclock() to actually execute
> callout handlers, etc.).

FWIW, here is an API I've used before. This handles all race
conditions and the 'other thread' question.

struct timer_event;				/* opaque structure */

typedef struct timer_event *timer_handle_t;	/* caller's timer "handle" */

typedef void timer_func_t(void *arg);		/* timeout function type */

/* flags for timer_start() */
#define TIMER_RECURRING		0x0001		/* timer is recurring */
#define TIMER_OWN_THREAD	0x0002		/* handle in separate thread */

extern int	timer_start(timer_handle_t *handlep, mutex_t *mutexp,
			timer_func_t tfunc, void *arg, u_int delay,
			int flags);
extern void	timer_cancel(timer_handle_t *handlep);
extern int	timer_remaining(timer_handle_t handle, u_int *delayp);

static inline int
timer_isrunning(timer_handle_t handle)
{
	return (handle != NULL);
}

Semantics:

  1. The caller supplies a pointer to the 'handle', which must initially
     be NULL. The handle != NULL if and only if the timer is running.
  2. timer_cancel() guarantees that tfunc() will not be called subsequently
  3. *handlep is set to NULL by timer_cancel() and by the timer expiring.
     So when *handlep is NULL when tfunc() is invoked (unless TIMER_RECURRING).
  4. Calling timer_start() or timer_stop() from within tfunc() is OK.
  5. If TIMER_RECURRING, timer started again before calling tfunc()
  6. If TIMER_OWN_THREAD, timer runs in a newly created thread (rather
     than the timer service thread), which means that tfunc() may sleep
     or be canceled. If tfunc() sleeps or the thread is canceled but
     TIMER_OWN_THREAD was not set -> panic.
  7. If mutexp != NULL, *mutexp is acquired before calling tfunc() and
     released after it returns.

Items 1, and 2 are guaranteed only if mutexp != NULL and the caller
acquires *mutexp before any calls to timer_start() or timer_cancel()
(you would normally be doing this anyway).

Errors:

  - timer_start() returns EBUSY if *handlep != NULL
  - timer_remaining() returns ESRCH if handle != NULL

The model is: you have some object that has an associated lock and
one or more associated timers. The object is to be locked whenever
you muck with it (including when you start, stop, or handle a timer):

    struct foobar {
	struct lock	mutex;
	timer_handle_t	timer1;
	timer_handle_t	timer2;
	...
    };

Then all calls to the timer_* routines are "well behaved" and the
timeout thread caling tfunc() never races with any other thread
that may be stopping or starting the timer, or destroying the object.
E.g., to destroy the object, the following suffices:

    void
    foobar_destroy(struct foobar *f)
    {
	mutex_lock(&f->mutex);
	timer_cancel(&f->timer1);
	timer_cancel(&f->timer2);
	mutex_unlock(&f->mutex);
	free(f);
    }

The only remaining complexity for the caller is that if you have
any TIMER_OWN_THREAD handlers which unlock the object (e.g., in order
to go to sleep), then you need to reference count the object and
have a FOOBAR_INVALID flag.

If you are working under a different model then this API may not
be appropriate, but at least in my multi-threading experience this
model is very typical.

-Archie

__________________________________________________________________________
Archie Cobbs     *     Packet Design     *     http://www.packetdesign.com

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200207101815.g6AIFDm28655>