Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 15 Jul 2002 10:20:06 -0700 (PDT)
From:      Scott Hess <scott@avantgo.com>
To:        Bosko Milekic <bmilekic@unixdaemons.com>
Cc:        Jon Mini <baka@elvis.mu.org>, <freebsd-net@FreeBSD.ORG>
Subject:   Re: mbuf external buffer reference counters
Message-ID:  <Pine.LNX.4.44.0207150944330.32100-100000@river.avantgo.com>
In-Reply-To: <20020712074507.B75547@unixdaemons.com>

next in thread | previous in thread | raw e-mail | index | archive | help
On Fri, 12 Jul 2002, Bosko Milekic wrote:
> On Fri, Jul 12, 2002 at 04:26:53AM -0700, Jon Mini wrote:
> > I'm probably speaking out of turn here (I have no idea what structure 
> > you all are talking about), but a monodirectional ring can be safely 
> > modified with a compare-and-exchange atomic operation.
> 
>  The jist of the problem is that when you want to say, remove yourself
>  from the list, you have to:
> 
>  1) your "next"'s back pointer to your "back" pointer
>  2) your "Prev"'s next pointer to your "next" pointer
> 
>  So that's two operations but for all you know your "next" or your
>  "back" may be doing the same thing to you at the same time.  As far as
>  I know, you can't (intuitively) figure out a way to do both of these
>  atomically. i.e., maybe you'll set your next's back pointer to whatever
>  you have in `back' but then your `back' guy will set your back pointer
>  to whatever he has in `back' and then your next guy's back pointer will
>  be invalid, for example.

<offtopic>You threw out a logic problem</offtopic>

The following came to mind, though I've not quite been able to fully think
through all possibilities, yet (C psuedo-code, because I am not up-to-date
on my assembly.  Comments assume a list A<->B<->C<-^, B being deleted):

    r1=prev;
    // If prev!=r1, A was deleted during the race.
    prev=0 iff prev==r1;   // atomic test-and-set.
    r2=next;
    // If next!=r2, C was deleted during the race.
    next=0 iff next==r2;
    // If r2->prev!=self, then the C is in delete.
    r2->prev=r1 iff r2->prev==self;
    // At this point, if C attempted a delete, the delete code would 
    // notice that C->prev->next != C (it is still B).
    // If r1->next!=self, then the A is in delete.
    r1->next=r2 iff r1->next==self;

Storing the 0's locks against deletes from the neighbors. The failure
cases for the first three exchanges (X=Y iff X==Z) are pretty
straightforward, and I think can be safely unwound.

The fourth failure case is interesting, though, because I _think_ that if
you correctly code the failure cases, you can try to run 'r1->next=r2 iff
r1->next==0;' to salvage it.  It's possible that there's a way of coding
things which would always allow one of two conflicting threads to proceed,
so that spinning on failure is sensible.  I can't quite see it, though.

I have no idea what the performance implications of this are.  AFAICT, you
would require 3 locks to accomplish this correctly (self, next, prev *),
which implies 6 total exchanges.  Also, I haven't considered the insert 
case, though I'm not sure why it wouldn't simply invert the 
ordering of the delete.

(*)  Hmm.  I thought you could do two (Lock self, lock next,
prev->next=next, next->prev=prev, unlock next, delete self), but then
realized that there was a race between finding next and applying the lock.  
So you need all three.

Later,
scott


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




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.LNX.4.44.0207150944330.32100-100000>