Transitioning if_addr_lock to an rwlock
John Baldwin
jhb at freebsd.org
Thu Dec 29 16:41:02 UTC 2011
On Monday, December 26, 2011 11:17:28 pm Gleb Smirnoff wrote:
> On Thu, Dec 22, 2011 at 11:30:01AM -0500, John Baldwin wrote:
> J> You can find the patch for 8.x at
> J> http://www.freebsd.org/~jhb/patches/if_addr_rwlock.patch
>
> Just my two pennies: for head/ patching if ip_carp.c should
> be straightforward:
>
> 1) Using W in carp_alloc_if() and carp_free_if().
> 2) Using R everywhere else.
Yes, this is what I did, but with an extra XXX:
@@ -1512,10 +1512,11 @@ carp_alloc_if(struct ifnet *ifp)
cif->cif_ifp = ifp;
TAILQ_INIT(&cif->cif_vrs);
- IF_ADDR_LOCK(ifp);
+ /* XXX: Race, shouldn't this be checking for concurrent calls? */
+ IF_ADDR_WLOCK(ifp);
ifp->if_carp = cif;
if_ref(ifp);
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
return (cif);
@@ -1534,10 +1535,10 @@ carp_free_if(struct carp_if *cif)
KASSERT(TAILQ_EMPTY(&cif->cif_vrs), ("%s: softc list not empty",
__func__));
- IF_ADDR_LOCK(ifp);
+ IF_ADDR_WLOCK(ifp);
ifp->if_carp = NULL;
if_rele(ifp);
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
CIF_LOCK_DESTROY(cif);
Specifically, if two threads both call carp_alloc() at the same time and both
see a value of NULL for ifp->if_carp (and I really do not like side effects
like assignments in conditional expressions of if() and while()), then both
threads can call carp_if_alloc(). Instead, carp_if_alloc() should be doing
something like this:
IF_ADDR_LOCK(ifp);
if (ifp->if_carp != NULL) {
CIF_LOCK_DESTROY(cif);
free(cif, M_CARP);
cif = ifp->if_carp;
} else
ifp->if_carp = cif;
IF_ADDR_UNLOCK(ifp);
return (cif);
Similarly, you have a race in the SIOCSVH ioctl handling code. You check
for a duplicate carp device for a specific (ifnet, vhid), tuple, but
carp_alloc() doesn't do a recheck when adding the new softc to the cif_vrs
list. Rather, what it should do is move that loop into carp_alloc() while
holding the CIF_LOCK() and free the already-created softc and fail
carp_alloc() if it finds a duplicate.
You also have a race between concurrent carp_alloc() and carp_destroy().
Specifically, carp_alloc() might find a cif and be in the process of building
a new carp softc when a carp_destroy() tears down the cif. The right way to
fix this is to add a reference count to the cif and have carp_alloc_if()
always bump the reference count. carp_destroy() would need to drop the
reference count, but under IF_ADDR_LOCK() and only do a carp_free_if() if the
count drops to zero. You'd have to grab IF_ADDR_LOCK() in the caller and let
carp_free_if() unlock it internally.
--
John Baldwin
More information about the freebsd-net
mailing list