Fix dirty buf exhaustion easily triggered with msdosfs.

If truncate(2) is performed on msdosfs file, which extends the file by
system-depended large amount, fs creates corresponding amount of dirty
delayed-write buffers, which can consume all buffers.  Such buffers
cannot be flushed by the bufdaemon because the ftruncate() thread owns
the vnode lock.  So the system runs out of free buffers, and even
truncate() thread starves, which means deadlock because it owns the
vnode lock.

Fix this by doing vnode fsync in extendfile() when low memory or low
buffers condition detected, which flushes all dirty buffers belonging
to the file being extended.

Note that the more usual fallback to bawrite() does not work
acceptable in this situation, because it would only allow one buffer
to be recycled.  Other filesystems, most important UFS, do not allow
userspace to create arbitrary amount of dirty delayed-write buffers
without feedback, so bawrite() is good enough for them.

Reported and tested by:	pho
Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
This commit is contained in:
Konstantin Belousov 2019-04-09 19:55:02 +00:00
parent 1fe39413c4
commit 997febb1e7
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=346064

View File

@ -54,6 +54,7 @@
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/mount.h>
#include <sys/vmmeter.h>
#include <sys/vnode.h>
#include <fs/msdosfs/bpb.h>
@ -979,6 +980,7 @@ extendfile(struct denode *dep, u_long count, struct buf **bpp, u_long *ncp,
u_long cn, got;
struct msdosfsmount *pmp = dep->de_pmp;
struct buf *bp;
struct vop_fsync_args fsync_ap;
daddr_t blkno;
/*
@ -1086,8 +1088,16 @@ extendfile(struct denode *dep, u_long count, struct buf **bpp, u_long *ncp,
if (bpp) {
*bpp = bp;
bpp = NULL;
} else
} else {
bdwrite(bp);
}
if (vm_page_count_severe() ||
buf_dirty_count_severe()) {
fsync_ap.a_vp = DETOV(dep);
fsync_ap.a_waitfor = MNT_WAIT;
fsync_ap.a_td = curthread;
vop_stdfsync(&fsync_ap);
}
}
}
}