Fix livelock in ufsdirhash_create().

When more than one thread enters ufsdirhash_create() for the same
directory and the inode dirhash is instantiated, but the dirhash' hash
is not, all of them lock the dirhash shared and then try to upgrade.
Since there are several threads owning the lock shared, upgrade fails
and the same attempt is repeated, ad infinitum.

To break the lockstep, lock the dirhash in exclusive mode after the
failed try-upgrade.

Reported and tested by:	pho
Sponsored by:	Mellanox Technologies
MFC after:	1 week
This commit is contained in:
Konstantin Belousov 2017-12-07 09:05:34 +00:00
parent fb3cc1c37d
commit b6721e4a5c
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=326657

View File

@ -192,9 +192,11 @@ ufsdirhash_create(struct inode *ip)
struct dirhash *ndh;
struct dirhash *dh;
struct vnode *vp;
bool excl;
ndh = dh = NULL;
vp = ip->i_vnode;
excl = false;
for (;;) {
/* Racy check for i_dirhash to prefetch a dirhash structure. */
if (ip->i_dirhash == NULL && ndh == NULL) {
@ -231,8 +233,11 @@ ufsdirhash_create(struct inode *ip)
ufsdirhash_hold(dh);
VI_UNLOCK(vp);
/* Acquire a shared lock on existing hashes. */
sx_slock(&dh->dh_lock);
/* Acquire a lock on existing hashes. */
if (excl)
sx_xlock(&dh->dh_lock);
else
sx_slock(&dh->dh_lock);
/* The hash could've been recycled while we were waiting. */
VI_LOCK(vp);
@ -253,9 +258,10 @@ ufsdirhash_create(struct inode *ip)
* so we can recreate it. If we fail the upgrade, drop our
* lock and try again.
*/
if (sx_try_upgrade(&dh->dh_lock))
if (excl || sx_try_upgrade(&dh->dh_lock))
break;
sx_sunlock(&dh->dh_lock);
excl = true;
}
/* Free the preallocated structure if it was not necessary. */
if (ndh) {