diff --git a/sys/kern/kern_lock.c b/sys/kern/kern_lock.c index dc8adfe..8fafff8 100644 --- a/sys/kern/kern_lock.c +++ b/sys/kern/kern_lock.c @@ -874,15 +874,27 @@ lockmgr_release(struct lock *lkp, u_int flags) } else { /* * Multiple exclusive counts, drop by 1. - * Since we are the holder and there is more - * than one count, we can just decrement it. + * + * Use a fcmpset loop instead of a blind + * fetchadd. A blind decrement can race a + * concurrent release (lk_lockholder == + * LK_KERNTHREAD allows any cpu to release, + * e.g. the buffer-cache biodone handoff) + * and drive the exclusive count to zero + * without handling the grant/wakeup/ + * EXREQ2/CANCEL cases that the single-count + * releases above deal with, leaving blocked + * requesters sleeping on a free lock + * (lost wakeup). */ - count = - atomic_fetchadd_long(&lkp->lk_count, -1); - /* count = count - 1 NOT NEEDED */ - if (lkp->lk_lockholder != LK_KERNTHREAD) - COUNT(td, -1); - break; + ncount = count - 1; + if (atomic_fcmpset_64(&lkp->lk_count, + &count, ncount)) { + if (lkp->lk_lockholder != LK_KERNTHREAD) + COUNT(td, -1); + break; + } + /* fcmpset failed, count reloaded: retry */ } /* retry */ } else {