mirror of
https://github.com/RhapsodiOS/RhapsodiOS.git
synced 2026-11-03 22:01:40 -07:00
[GH-ISSUE #87] kernel: msync returns EIO when another process is writing the same file (vnode_pageout gives up on the vnode lock) #81
Labels
No labels
bug
documentation
enhancement
enhancement
good first issue
help wanted
help wanted
pull-request
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
github-starred/RhapsodiOS#81
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @raynorpat on GitHub (Oct 31, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/87
Originally assigned to: @raynorpat on GitHub.
Found while testing #81 (pre-existing; reproduced on the stock kernel).
Symptom
vm/work/issue-81/msx.c: one process loopsmsyncon a dirty 64K shared mapping of a UFS file while a second processwrite()s 1 MB chunks to the same file, for 60 s.3d017f064): 246 of 297 and 249 of 281 msync calls failed with EIO.The stress suite's
mmap synchit it once asmsync: EIOunder load (runs/i81).Cause
vnode_pageout(vm/vnode_pager.c~915-930 on ffs-perf23c1dd5f6) takes the vnode lock with LK_NOWAIT, withVNODE_LOCK_RETRY_COUNT 1andVNODE_LOCK_RETRY_TICKS 2. If a writer holds the lock for longer than about 2 ticks, the pageout returns PAGER_ERROR, andmsync(vm_synchronize/vm_msync) reports EIO.The no-wait lock is probably there so the pageout daemon never blocks on a vnode, which can deadlock (for example against a thread holding the vnode lock and waiting for free pages).
Expected
A synchronous
msync(MS_SYNC) on a healthy file never fails because another process is writing it: it waits for the lock, or retries until it gets it. The pageout daemon may keep its no-wait behaviour. The fix must tell the two callers apart (for example a flag on the pageout request, or a caller-context check) and must not bring back the pageout-daemon deadlock.@raynorpat commented on GitHub (Oct 31, 2026):
Fixed by
f3202b49e, merged into ffs-perf in1f8535d41.Why the lock is no-wait.
vnode_pageout(vm/vnode_pager.c:916-939) has taken it with LK_NOWAIT since the Darwin 0.3 import. Making it wait would deadlock two ways:vm_pageout.c:291) is the only thread that frees memory, and the vnode-lock holder can be waiting for free pages.uiomove,mapfs.c:1514, viavm_fault.c:269-282;vmp_push_range;vno_flush) waits on the busy bit and never releases the lock.So a "wait" flag on the request would deadlock the reproducer itself.
Fix: by lock ownership.
vnode_pageoutstays LK_NOWAIT|LK_CANRECURSE. A caller that may sleep takes the vnode lock itself before any page is busy, so the pageout's lock is a recursion and can't fail (lockmgr checks the thread,kern_lock.c:341).vm/vm_synchronize.cmap_push_range): for a file-backed object (not device, not swap file) it takes an object reference, drops the map read lock, takes the vnode lock (LK_RETRY|LK_CANRECURSE), pushes, unlocks, relocks the map and goes on after the range. The vnode lock is taken before the map lock and before any busy page, the same order a writer uses.vm_pageout_pageskips pages being paged in (nfspagereq), whose pagein may wait for the lock the pusher holds.vfs_syscalls.c:2018) runsmapfs_fsyncunder the vnode lock it already takes. Its contended pushes used to fail silently.The pageout daemon,
vm_object_terminate,mapfs_syncand the VXLOCK paths don't pre-lock, so they keep no-wait.Verified on the kernel built from
f3202b49e(cksum 32422712):stress-mmap sync5/5. The rp, rp2, s4x and mx recipes: everyumount -freturns 0, no hang.check-proc,check-mmap,check-vm,check-fspass on UFS and NFS.proc ptyEIO, the known failure from the final retests.stress-vm pressure: passes at-m 192mon 128 MB. At 640m it hit the time limit with the guest responsive, the known #89 limit.Left open:
mapfs_sync(update) andvm_object_terminatestill give up on a busy vnode, andmapfs_syncstops its scan on the first error.mapfs_fsync's return value.