Mark pages after EOF as clean after pageout.

Suppose that a file on NFS has partially filled last page, and this
page is dirty.  NFS VOP_PAGEOUT() method only marks the the page clean
up to the block of the last written byte, leaving other blocks dirty.
Also any page which erronously exists in the vnode vm_object past EOF
is also left marked as dirty.

With the introduction of the buf-cache coherent pager, each pass of
syncer over the object with such page results in creation of B_DELWRI
buffer due to VOP_WRITE() call.  This buffer is noted on next syncer
pass, which results e.g. a visible manifestation of shutdown never
finishing vnode sync.  Note that before buf-cache coherency commit, a
dirty page might left never synced to server if a partial writes
occur.

Fix this by clearing dirty bits after EOF.  Only blocks of the partial
page which are completely after EOF are marked clean, to avoid
possible user data loss.

Reported by:	mav
Reviewed by:	alc, markj
Tested by:	mav, pho
Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D11697
This commit is contained in:
Konstantin Belousov 2017-07-26 20:07:05 +00:00
parent cc2c26223b
commit 555b7bb4c8
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=321581
4 changed files with 56 additions and 9 deletions

View File

@ -336,8 +336,10 @@ ncl_putpages(struct vop_putpages_args *ap)
cred);
crfree(cred);
if (error == 0 || !nfs_keep_dirty_on_error)
vnode_pager_undirty_pages(pages, rtvals, count - uio.uio_resid);
if (error == 0 || !nfs_keep_dirty_on_error) {
vnode_pager_undirty_pages(pages, rtvals, count - uio.uio_resid,
np->n_size - offset, npages * PAGE_SIZE);
}
return (rtvals[0]);
}

View File

@ -621,9 +621,11 @@ smbfs_putpages(ap)
relpbuf(bp, &smbfs_pbuf_freecnt);
if (!error)
vnode_pager_undirty_pages(pages, rtvals, count - uio.uio_resid);
return rtvals[0];
if (error == 0) {
vnode_pager_undirty_pages(pages, rtvals, count - uio.uio_resid,
npages * PAGE_SIZE, npages * PAGE_SIZE);
}
return (rtvals[0]);
#endif /* SMBFS_RWGENERIC */
}

View File

@ -1315,13 +1315,24 @@ vnode_pager_putpages_ioflags(int pager_flags)
return (ioflags);
}
/*
* vnode_pager_undirty_pages().
*
* A helper to mark pages as clean after pageout that was possibly
* done with a short write. The lpos argument specifies the page run
* length in bytes, and the written argument specifies how many bytes
* were actually written. eof is the offset past the last valid byte
* in the vnode using the absolute file position of the first byte in
* the run as the base from which it is computed.
*/
void
vnode_pager_undirty_pages(vm_page_t *ma, int *rtvals, int written)
vnode_pager_undirty_pages(vm_page_t *ma, int *rtvals, int written, off_t eof,
int lpos)
{
vm_object_t obj;
int i, pos;
int i, pos, pos_devb;
if (written == 0)
if (written == 0 && eof >= lpos)
return;
obj = ma[0]->object;
VM_OBJECT_WLOCK(obj);
@ -1335,6 +1346,37 @@ vnode_pager_undirty_pages(vm_page_t *ma, int *rtvals, int written)
vm_page_clear_dirty(ma[i], 0, written & PAGE_MASK);
}
}
if (eof >= lpos) /* avoid truncation */
goto done;
for (pos = eof, i = OFF_TO_IDX(trunc_page(pos)); pos < lpos; i++) {
if (pos != trunc_page(pos)) {
/*
* The page contains the last valid byte in
* the vnode, mark the rest of the page as
* clean, potentially making the whole page
* clean.
*/
pos_devb = roundup2(pos & PAGE_MASK, DEV_BSIZE);
vm_page_clear_dirty(ma[i], pos_devb, PAGE_SIZE -
pos_devb);
/*
* If the page was cleaned, report the pageout
* on it as successful. msync() no longer
* needs to write out the page, endlessly
* creating write requests and dirty buffers.
*/
if (ma[i]->dirty == 0)
rtvals[i] = VM_PAGER_OK;
pos = round_page(pos);
} else {
/* vm_pageout_flush() clears dirty */
rtvals[i] = VM_PAGER_BAD;
pos += PAGE_SIZE;
}
}
done:
VM_OBJECT_WUNLOCK(obj);
}

View File

@ -50,7 +50,8 @@ int vnode_pager_local_getpages_async(struct vop_getpages_async_args *ap);
int vnode_pager_putpages_ioflags(int pager_flags);
void vnode_pager_release_writecount(vm_object_t object, vm_offset_t start,
vm_offset_t end);
void vnode_pager_undirty_pages(vm_page_t *ma, int *rtvals, int written);
void vnode_pager_undirty_pages(vm_page_t *ma, int *rtvals, int written,
off_t eof, int lpos);
void vnode_pager_update_writecount(vm_object_t object, vm_offset_t start,
vm_offset_t end);