FreeBSD NFS client/Linux NFS server issue

Rick Macklem rmacklem at uoguelph.ca
Fri Jan 22 19:27:16 UTC 2010



On Fri, 22 Jan 2010, Mikolaj Golub wrote:

>
> We have nonempty nm_bufq, nm_bufqiods = 1, but actually there is no nfsiod
> thread run for this mount, which is wrong -- nm_bufq will not be emptied until
> some other process starts writing to the nfsmount and starts nfsiod thread for
> this mount.
>
> Reviewing the code how it could happen I see the following path. Could someone
> confirm or disprove me?
>
> in nfs_bio.c:nfs_asyncio() we have:
>
>   1363         mtx_lock(&nfs_iod_mtx);
> ...
>   1374         /*
>   1375          * Find a free iod to process this request.
>   1376          */
>   1377         for (iod = 0; iod < nfs_numasync; iod++)
>   1378                 if (nfs_iodwant[iod]) {
>   1379                         gotiod = TRUE;
>   1380                         break;
>   1381                 }
>   1382
>   1383         /*
>   1384          * Try to create one if none are free.
>   1385          */
>   1386         if (!gotiod) {
>   1387                 iod = nfs_nfsiodnew();
>   1388                 if (iod != -1)
>   1389                         gotiod = TRUE;
>   1390         }
>
> Let's consider situation when new nfsiod is created.
>
> nfs_nfsiod.c:nfs_nfsiodnew() before creating nfssvc_iod thread unlocks nfs_iod_mtx:
>
>    179         mtx_unlock(&nfs_iod_mtx);
>    180         error = kthread_create(nfssvc_iod, nfs_asyncdaemon + i, NULL, RFHIGHPID,
>    181             0, "nfsiod %d", newiod);
>    182         mtx_lock(&nfs_iod_mtx);
>
>
> And  nfs_nfsiod.c:nfssvc_iod() do the followin:
>
>    226         mtx_lock(&nfs_iod_mtx);
> ...
>    238                 nfs_iodwant[myiod] = curthread->td_proc;
>    239                 nfs_iodmount[myiod] = NULL;
> ...
>    244                 error = msleep(&nfs_iodwant[myiod], &nfs_iod_mtx, PWAIT | PCATCH,
>    245                     "-", timo);
>
> Let's at this moment another nfs_asyncio() request for another nfsmount has
> happened and this thread has locked nfs_iod_mtx. Then this thread will found
> nfs_iodwant[iod] in "for" loop and will use it. When the first thread actually
> has returned from nfs_nfsiodnew() it will insert buffer to nmp->nm_bufq but
> nfsiod will process other nmp.
>

Ok, good catch, I think you've found the problem (or at least a race
that might have caused it).

> It looks like the fix for this situation would be to check nfs_iodwant[iod]
> after nfs_nfsiodnew():
>
> --- nfs_bio.c.orig      2010-01-22 15:38:02.000000000 +0000
> +++ nfs_bio.c   2010-01-22 15:39:58.000000000 +0000
> @@ -1385,7 +1385,7 @@ again:
>         */
>        if (!gotiod) {
>                iod = nfs_nfsiodnew();
> -               if (iod != -1)
> +               if ((iod != -1) && (nfs_iodwant[iod] == NULL))
>                        gotiod = TRUE;
>        }
>

Unfortunately, I don't think the above fixes the problem.
If another thread that called nfs_asyncio() has "stolen" the this "iod",
it will have set nfs_iodwant[iod] == NULL (set non-NULL at #238)
and it will remain NULL until the other thread is done with it.

If you instead make it:
 		if (iod != -1 && nfs_iodwant[iod] != NULL)
 			gotiod = TRUE;
then I think it fixes your scenario above, but will break for the
case where the mtx_lock(&nfs_iod_mtx) call in nfs_nfsnewiod() (#182) wins
out over the one near the beginning of nfssvc_iod() (#226), since in that
case, nfs_iodwant[iod] will still be NULL because it hasn't yet been
set by nfssvc_iod() (#238).

There should probably be some sort of 3 way handshake between
the code in nfs_asyncio() after calling nfs_nfsnewiod() and the
code near the beginning of nfssvc_iod(), but I think the following
somewhat cheesy fix might do the trick:

 	if (!gotiod) {
 		iod = nfs_nfsiodnew();
 		if (iod != -1) {
 			if (nfs_iodwant[iod] == NULL) {
 				/*
 				 * Either another thread has acquired this
 				 * iod or I acquired the nfs_iod_mtx mutex
 				 * before the new iod thread did in
 				 * nfssvc_iod(). To be safe, go back and
 				 * try again after allowing another thread
 				 * to acquire the nfs_iod_mtx mutex.
 				 */
 				mtx_unlock(&nfs_iod_mtx);
 				/*
 				 * So long as mtx_lock() implements some
 				 * sort of fairness, nfssvc_iod() should
 				 * get nfs_iod_mtx here and set
 				 * nfs_iodwant[iod] != NULL for the case
 				 * where the iod has not been "stolen" by
 				 * another thread for a different mount
 				 * point.
 				 */
 				mtx_lock(&nfs_iod_mtx);
 				goto again;
 			}
 			gotiod = TRUE;
 		}
 	}

Does anyone else have a better solution?
(Mikolaj, could you by any chance test this? You can test yours, but I
think it breaks.)

rick



More information about the freebsd-stable mailing list