nfs/localio: refactor iocb initialization

The goal of this commit's various refactoring is to have LOCALIO's per
IO initialization occur in process context so that we don't get into a
situation where IO fails to be issued from workqueue (e.g. due to lack
of memory, etc). Better to have LOCALIO's iocb initialization fail
early.

There isn't immediate need but this commit makes it possible for
LOCALIO to fallback to NFS pagelist code in process context to allow
for immediate retry over RPC.

Signed-off-by: Mike Snitzer <snitzer@kernel.org>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
This commit is contained in:
Mike Snitzer 2025-09-19 10:36:28 -04:00 committed by Anna Schumaker
parent 091bdcfcec
commit e43e9a3a3d
1 changed files with 55 additions and 38 deletions

View File

@ -36,6 +36,7 @@ struct nfs_local_kiocb {
struct nfs_pgio_header *hdr;
struct work_struct work;
void (*aio_complete_work)(struct work_struct *);
struct iov_iter iter ____cacheline_aligned;
struct nfsd_file *localio;
};
@ -411,13 +412,19 @@ nfs_local_pgio_done(struct nfs_pgio_header *hdr, long status)
}
}
static void
nfs_local_iocb_release(struct nfs_local_kiocb *iocb)
{
nfs_local_file_put(iocb->localio);
nfs_local_iocb_free(iocb);
}
static void
nfs_local_pgio_release(struct nfs_local_kiocb *iocb)
{
struct nfs_pgio_header *hdr = iocb->hdr;
nfs_local_file_put(iocb->localio);
nfs_local_iocb_free(iocb);
nfs_local_iocb_release(iocb);
nfs_local_hdr_release(hdr, hdr->task.tk_ops);
}
@ -483,18 +490,16 @@ static void nfs_local_call_read(struct work_struct *work)
container_of(work, struct nfs_local_kiocb, work);
struct file *filp = iocb->kiocb.ki_filp;
const struct cred *save_cred;
struct iov_iter iter;
ssize_t status;
save_cred = override_creds(filp->f_cred);
nfs_local_iter_init(&iter, iocb, ITER_DEST);
if (iocb->kiocb.ki_flags & IOCB_DIRECT) {
iocb->kiocb.ki_complete = nfs_local_read_aio_complete;
iocb->aio_complete_work = nfs_local_read_aio_complete_work;
}
status = filp->f_op->read_iter(&iocb->kiocb, &iter);
status = filp->f_op->read_iter(&iocb->kiocb, &iocb->iter);
revert_creds(save_cred);
@ -505,25 +510,14 @@ static void nfs_local_call_read(struct work_struct *work)
}
static int
nfs_do_local_read(struct nfs_pgio_header *hdr,
struct nfsd_file *localio,
nfs_local_do_read(struct nfs_local_kiocb *iocb,
const struct rpc_call_ops *call_ops)
{
struct nfs_local_kiocb *iocb;
struct file *file = nfs_to->nfsd_file_file(localio);
/* Don't support filesystems without read_iter */
if (!file->f_op->read_iter)
return -EAGAIN;
struct nfs_pgio_header *hdr = iocb->hdr;
dprintk("%s: vfs_read count=%u pos=%llu\n",
__func__, hdr->args.count, hdr->args.offset);
iocb = nfs_local_iocb_alloc(hdr, file, GFP_KERNEL);
if (iocb == NULL)
return -ENOMEM;
iocb->localio = localio;
nfs_local_pgio_init(hdr, call_ops);
hdr->res.eof = false;
@ -680,20 +674,18 @@ static void nfs_local_call_write(struct work_struct *work)
struct file *filp = iocb->kiocb.ki_filp;
unsigned long old_flags = current->flags;
const struct cred *save_cred;
struct iov_iter iter;
ssize_t status;
current->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO;
save_cred = override_creds(filp->f_cred);
nfs_local_iter_init(&iter, iocb, ITER_SOURCE);
if (iocb->kiocb.ki_flags & IOCB_DIRECT) {
iocb->kiocb.ki_complete = nfs_local_write_aio_complete;
iocb->aio_complete_work = nfs_local_write_aio_complete_work;
}
file_start_write(filp);
status = filp->f_op->write_iter(&iocb->kiocb, &iter);
status = filp->f_op->write_iter(&iocb->kiocb, &iocb->iter);
file_end_write(filp);
revert_creds(save_cred);
@ -707,26 +699,15 @@ static void nfs_local_call_write(struct work_struct *work)
}
static int
nfs_do_local_write(struct nfs_pgio_header *hdr,
struct nfsd_file *localio,
nfs_local_do_write(struct nfs_local_kiocb *iocb,
const struct rpc_call_ops *call_ops)
{
struct nfs_local_kiocb *iocb;
struct file *file = nfs_to->nfsd_file_file(localio);
/* Don't support filesystems without write_iter */
if (!file->f_op->write_iter)
return -EAGAIN;
struct nfs_pgio_header *hdr = iocb->hdr;
dprintk("%s: vfs_write count=%u pos=%llu %s\n",
__func__, hdr->args.count, hdr->args.offset,
(hdr->args.stable == NFS_UNSTABLE) ? "unstable" : "stable");
iocb = nfs_local_iocb_alloc(hdr, file, GFP_NOIO);
if (iocb == NULL)
return -ENOMEM;
iocb->localio = localio;
switch (hdr->args.stable) {
default:
break;
@ -747,32 +728,68 @@ nfs_do_local_write(struct nfs_pgio_header *hdr,
return 0;
}
static struct nfs_local_kiocb *
nfs_local_iocb_init(struct nfs_pgio_header *hdr, struct nfsd_file *localio)
{
struct file *file = nfs_to->nfsd_file_file(localio);
struct nfs_local_kiocb *iocb;
gfp_t gfp_mask;
int rw;
if (hdr->rw_mode & FMODE_READ) {
if (!file->f_op->read_iter)
return ERR_PTR(-EOPNOTSUPP);
gfp_mask = GFP_KERNEL;
rw = ITER_DEST;
} else {
if (!file->f_op->write_iter)
return ERR_PTR(-EOPNOTSUPP);
gfp_mask = GFP_NOIO;
rw = ITER_SOURCE;
}
iocb = nfs_local_iocb_alloc(hdr, file, gfp_mask);
if (iocb == NULL)
return ERR_PTR(-ENOMEM);
iocb->hdr = hdr;
iocb->localio = localio;
nfs_local_iter_init(&iocb->iter, iocb, rw);
return iocb;
}
int nfs_local_doio(struct nfs_client *clp, struct nfsd_file *localio,
struct nfs_pgio_header *hdr,
const struct rpc_call_ops *call_ops)
{
struct nfs_local_kiocb *iocb;
int status = 0;
if (!hdr->args.count)
return 0;
iocb = nfs_local_iocb_init(hdr, localio);
if (IS_ERR(iocb))
return PTR_ERR(iocb);
switch (hdr->rw_mode) {
case FMODE_READ:
status = nfs_do_local_read(hdr, localio, call_ops);
status = nfs_local_do_read(iocb, call_ops);
break;
case FMODE_WRITE:
status = nfs_do_local_write(hdr, localio, call_ops);
status = nfs_local_do_write(iocb, call_ops);
break;
default:
dprintk("%s: invalid mode: %d\n", __func__,
hdr->rw_mode);
status = -EINVAL;
status = -EOPNOTSUPP;
}
if (status != 0) {
if (status == -EAGAIN)
nfs_localio_disable_client(clp);
nfs_local_file_put(localio);
nfs_local_iocb_release(iocb);
hdr->task.tk_status = status;
nfs_local_hdr_release(hdr, call_ops);
}