Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 17 Aug 2005 17:02:48 -0700
From:      Luigi Rizzo <rizzo@icir.org>
To:        Max Laier <max@love2party.net>
Cc:        arch@freebsd.org, net@freebsd.org
Subject:   Re: duplicate  read/write locks in net/pfil.c and netinet/ip_fw2.c
Message-ID:  <20050817170248.A70991@xorpc.icir.org>
In-Reply-To: <200508170435.34688.max@love2party.net>; from max@love2party.net on Wed, Aug 17, 2005 at 04:35:19AM %2B0200
References:  <20050816170519.A74422@xorpc.icir.org> <200508170435.34688.max@love2party.net>

next in thread | previous in thread | raw e-mail | index | archive | help
On Wed, Aug 17, 2005 at 04:35:19AM +0200, Max Laier wrote:
> On Wednesday 17 August 2005 02:05, Luigi Rizzo wrote:
> > [apologies for the cross post but it belongs both to arch and net.]
> >
> > I notice that net/pfil.c and netinet/ip_fw2.c have two copies of
> > aisimilar but slightly different implementation of
> > multiple-reader/single-writer locks, which brings up the question(s):
> >
> > 1. should we rather put this code in the generic kernel code so that other
> >    subsystems could make use of it ? E.g. the routing table is certainly
> >    a candidate,
> 
> I have asked this several time on -arch and IRC, but never found anyone 
> willing to pursue it.  However, the problem is ...
> 
> > and especially
> >
> > 2. should we implement it right ?
> >
> >    Both implementations are subject to starvation for the writers
> >    (which is indeed a problem here, because we might want to modify
> >    a ruleset and be prevented from doing it because of incoming traffic
> >    that keeps readers active).
> >    Also the PFIL_TRY_WLOCK will in fact be blocking if a writer
> >    is already in - i have no idea how problematic is this in the
> >    way it is actually used.
> 
>... really this.  I didn't find a clean way out of the starvation issue.  What 
> I do for pfil is that I set a flag and simply stop serving[2] shared requests 
> once a writer waits for the lock.  If a writer can't sleep[1] then we return 
> EBUSY and don't.  However, for pfil it's almost ever safe to assume that a 
> write may sleep (as it is for most instances of this kind of sx-lock where 
> you have BIGNUMxreads:1xwrite).

could you guys look at the following code and see if it makes sense,
or tell me where i am wrong ?

It should solve the starvation and blocking trylock problems,
because the active reader does not hold the mutex in the critical
section anymore. The lock could well be a spinlock.

	cheers
	luigi

/*
 * Implementation of multiple reader-single writer that prevents starvation.
 * Luigi Rizzo 2005.08.19
 *
 * The mutex m only protects accesses to the struct rwlock.
 * We can have the following states:
 * IDLE: readers = 0, writers = 0, br = 0;
 *      any request will be granted immediately.
 *
 * READ: readers > 0, writers = 0, br = 0. Read in progress.
 *      Grant read requests immediately, queue write requests and
 *      move to READ1.
 *      When last reader terminates, move to IDLE.
 *
 * READ1: readers > 0, writers > 0, br >= 0.
 *      Read in progress, but writers are queued.
 *      Queue read and write requests to qr and wr, respectively.
 *      When the last reader terminates, wakeup the next queued writer
 *      and move to WRITE
 *
 * WRITE: readers = 0, writers > 0, br >= 0.
 *      Write in progress, possibly queued readers/writers.
 *      Queue read and write requests to qr and wr, respectively.
 *      When the writer terminates, wake up all readers if any,
 *      otherwise wake up the next writer if any.
 *      Move to READ, READ1, IDLE accordingly.
 */

struct rwlock {
        mtx     m;      /* protects access to the rwlock */
        int     readers;        /* active readers */
        int     br;     /* blocked readers */
        int     writers;        /* active + blocked writers */
        cv      qr;     /* queued readers */
        cv      qw;     /* queued writers */
}

int 
RLOCK(struct rwlock *rwl, int try)
{
        if (!try)
                mtx_lock(&rwl->m);
        else if (!mtx_trylock(&rwl->m))
                return EBUSY;
        if (rwl->writers == 0)  /* no writer, pass */
                rwl->readers++;
        else {
                rwl->br++;
                cv_wait(&rwl->qr, &rwl->m);
        }
        mtx_unlock(&rwl->m);
        return 0;
}

int 
WLOCK(struct rwlock *rwl, int try)
{
        if (!try)
                mtx_lock(&rwl->m);
        else if (!mtx_trylock(&rwl->m))
                return EBUSY;
        rwl->writers++;
        if (rwl->readers > 0)   /* have readers, must wait */
                cv_wait(&rwl->qw, &rwl->m);
        mtx_unlock(&rwl->m);
        return 0;
}

void
RUNLOCK(struct rwlock *rwl)
{
        mtx_lock(&rwl->m);
        rwl->readers--;
        if (rwl->readers == 0 && rwl->writers > 0)
                cv_signal(&rwl->qw);
        mtx_unlock(&rwl->m);
}

void
WUNLOCK(struct rwlock *rwl)
{
        mtx_lock(&rwl->m);
        rwl->writers--;
        if (rwl->br > 0) {      /* priority to readers */
                rwl->readers = rwl->br;
                rwl->br = 0;
                cv_broadcast(&rwl->qr);
        } else if (rwl->writers > 0)
                cv_signal(&rwl->qw);
        mtx_unlock(&rwl->m);
}




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