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