kernel/vm: Add better handling for busy-writing pages on cache resizes.

Previously, busy-writing pages just stayed in the cache until
I/O finished. This wasn't right, as they should've been unmapped
(and removed) immediately, as a TODO in the PageWriteWrapper noted.

Now they are, and they're moved into a queue on the cache pending
the I/O completing.

This paves the way for fixing #18390.

Change-Id: I51778ba1cfc1d26f34dd44f1ee293381be59937f
Reviewed-on: https://review.haiku-os.org/c/haiku/+/8627
Reviewed-by: waddlesplash <[email protected]>
Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
Augustin Cavalier
2026-03-30 22:11:53 +00:00
committed by waddlesplash
parent 15594f62fe
commit dcf4948bff
3 changed files with 68 additions and 50 deletions
+6
View File
@@ -115,6 +115,7 @@ public:
{ if (fPageEventWaiters != NULL) { if (fPageEventWaiters != NULL)
_NotifyPageEvents(page, events); } _NotifyPageEvents(page, events); }
inline void MarkPageUnbusy(vm_page* page); inline void MarkPageUnbusy(vm_page* page);
void FreeRemovedPage(vm_page* page);
vm_page* LookupPage(off_t offset); vm_page* LookupPage(off_t offset);
void InsertPage(vm_page* page, off_t offset); void InsertPage(vm_page* page, off_t offset);
@@ -231,12 +232,17 @@ private:
bool _FreePageRange(VMCachePagesTree::Iterator it, bool _FreePageRange(VMCachePagesTree::Iterator it,
page_num_t* toPage, page_num_t* freedPages); page_num_t* toPage, page_num_t* freedPages);
private:
typedef DoublyLinkedQueue<vm_page, DoublyLinkedListCLink<vm_page,
SplayTreeLink<vm_page>, &vm_page::cache_link> > RemovedPagesQueue;
private: private:
int32 fRefCount; int32 fRefCount;
mutex fLock; mutex fLock;
PageEventWaiter* fPageEventWaiters; PageEventWaiter* fPageEventWaiters;
void* fUserData; void* fUserData;
VMCacheRef* fCacheRef; VMCacheRef* fCacheRef;
RemovedPagesQueue fRemovedBusyPages;
page_num_t fWiredPagesCount; page_num_t fWiredPagesCount;
uint64 fFaultCount; uint64 fFaultCount;
+34 -13
View File
@@ -678,6 +678,8 @@ VMCache::Delete()
panic("cache %p to be deleted still has areas", this); panic("cache %p to be deleted still has areas", this);
if (!consumers.IsEmpty()) if (!consumers.IsEmpty())
panic("cache %p to be deleted still has consumers", this); panic("cache %p to be deleted still has consumers", this);
if (!fRemovedBusyPages.IsEmpty())
panic("cache %p to be deleted still has removed busy pages", this);
T(Delete(this)); T(Delete(this));
@@ -818,6 +820,20 @@ VMCache::InsertPage(vm_page* page, off_t offset)
} }
/*! Frees a page that was removed by _FreePageRange(), but which was busy
and couldn't be freed then.
*/
void
VMCache::FreeRemovedPage(vm_page* page)
{
AssertLocked();
NotifyPageEvents(page, PAGE_EVENT_NOT_BUSY);
fRemovedBusyPages.Remove(page);
vm_page_free(this, page);
}
/*! Removes the vm_page from this cache. Of course, the page must /*! Removes the vm_page from this cache. Of course, the page must
really be in this cache or evil things will happen. really be in this cache or evil things will happen.
The cache lock must be held. The cache lock must be held.
@@ -1103,33 +1119,38 @@ VMCache::_FreePageRange(VMCachePagesTree::Iterator it,
page = it.Next()) { page = it.Next()) {
if (page->busy) { if (page->busy) {
if (page->busy_writing) { if (!page->busy_writing) {
// We cannot wait for the page to become available
// as we might cause a deadlock this way
page->busy_writing = false;
// this will notify the writer to free the page
if (freedPages != NULL)
(*freedPages)++;
continue;
}
// wait for page to become unbusy // wait for page to become unbusy
WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true); WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
return true; return true;
} }
// remove the page and put it into the free queue // We cannot wait for the page to become available
DEBUG_PAGE_ACCESS_START(page); // as we might cause a deadlock this way
vm_remove_all_page_mappings(page); page->busy_writing = false;
// this will notify the writer to free the page
}
ASSERT(page->WiredCount() == 0); ASSERT(page->WiredCount() == 0);
// TODO: Find a real solution! If the page is wired // TODO: Find a real solution! If the page is wired
// temporarily (e.g. by lock_memory()), we actually must not // temporarily (e.g. by lock_memory()), we actually must not
// unmap it! // unmap it!
// remove the page and put it into the free queue
DEBUG_PAGE_ACCESS_START(page);
vm_remove_all_page_mappings(page);
RemovePage(page); RemovePage(page);
// Note: When iterating through a IteratableSplayTree // Note: When iterating through a IteratableSplayTree
// removing the current node is safe. // removing the current node is safe.
if (page->busy) {
fRemovedBusyPages.Add(page);
DEBUG_PAGE_ACCESS_END(page);
} else {
vm_page_free(this, page); vm_page_free(this, page);
}
if (freedPages != NULL) if (freedPages != NULL)
(*freedPages)++; (*freedPages)++;
} }
+8 -17
View File
@@ -2087,29 +2087,21 @@ PageWriteWrapper::Done(status_t result)
DEBUG_PAGE_ACCESS_START(fPage); DEBUG_PAGE_ACCESS_START(fPage);
fPage->busy = false; fPage->busy = false;
// Set unbusy and notify later by hand, since we might free the page. // Set unbusy and notify later by hand.
bool success = true; bool success = true;
if (result == B_OK) { if (!fPage->busy_writing) {
// The busy_writing flag was cleared. That means the cache tried to remove
// the page while we were trying to write it. Let the cache handle the rest.
fCache->FreeRemovedPage(fPage);
} else if (result == B_OK) {
// put it into the active/inactive queue // put it into the active/inactive queue
move_page_to_appropriate_queue(fPage); move_page_to_appropriate_queue(fPage);
fPage->busy_writing = false; fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage); DEBUG_PAGE_ACCESS_END(fPage);
} else {
// Writing the page failed. One reason would be that the cache has been
// shrunk and the page does no longer belong to the file. Otherwise the
// actual I/O failed, in which case we'll simply keep the page modified.
if (!fPage->busy_writing) { fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
// The busy_writing flag was cleared. That means the cache has been
// shrunk while we were trying to write the page and we have to free
// it now.
vm_remove_all_page_mappings(fPage);
// TODO: Unmapping should already happen when resizing the cache!
fCache->RemovePage(fPage);
free_page(fPage, false);
unreserve_pages(1);
} else { } else {
// Writing the page failed -- mark the page modified and move it to // Writing the page failed -- mark the page modified and move it to
// an appropriate queue other than the modified queue, so we don't // an appropriate queue other than the modified queue, so we don't
@@ -2130,11 +2122,10 @@ PageWriteWrapper::Done(status_t result)
fPage->busy_writing = false; fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage); DEBUG_PAGE_ACCESS_END(fPage);
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
success = false; success = false;
} }
}
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
fIsActive = false; fIsActive = false;
return success; return success;