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)
_NotifyPageEvents(page, events); }
inline void MarkPageUnbusy(vm_page* page);
void FreeRemovedPage(vm_page* page);
vm_page* LookupPage(off_t offset);
void InsertPage(vm_page* page, off_t offset);
@@ -231,12 +232,17 @@ private:
bool _FreePageRange(VMCachePagesTree::Iterator it,
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:
int32 fRefCount;
mutex fLock;
PageEventWaiter* fPageEventWaiters;
void* fUserData;
VMCacheRef* fCacheRef;
RemovedPagesQueue fRemovedBusyPages;
page_num_t fWiredPagesCount;
uint64 fFaultCount;
+36 -15
View File
@@ -678,6 +678,8 @@ VMCache::Delete()
panic("cache %p to be deleted still has areas", this);
if (!consumers.IsEmpty())
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));
@@ -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
really be in this cache or evil things will happen.
The cache lock must be held.
@@ -1103,33 +1119,38 @@ VMCache::_FreePageRange(VMCachePagesTree::Iterator it,
page = it.Next()) {
if (page->busy) {
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;
if (!page->busy_writing) {
// wait for page to become unbusy
WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
return true;
}
// wait for page to become unbusy
WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
return true;
// 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
}
// remove the page and put it into the free queue
DEBUG_PAGE_ACCESS_START(page);
vm_remove_all_page_mappings(page);
ASSERT(page->WiredCount() == 0);
// TODO: Find a real solution! If the page is wired
// temporarily (e.g. by lock_memory()), we actually must not
// 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);
// Note: When iterating through a IteratableSplayTree
// removing the current node is safe.
vm_page_free(this, page);
if (page->busy) {
fRemovedBusyPages.Add(page);
DEBUG_PAGE_ACCESS_END(page);
} else {
vm_page_free(this, page);
}
if (freedPages != NULL)
(*freedPages)++;
}
+26 -35
View File
@@ -2087,54 +2087,45 @@ PageWriteWrapper::Done(status_t result)
DEBUG_PAGE_ACCESS_START(fPage);
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;
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
move_page_to_appropriate_queue(fPage);
fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage);
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
} 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.
// Writing the page failed -- mark the page modified and move it to
// an appropriate queue other than the modified queue, so we don't
// keep trying to write it over and over again. We keep
// non-temporary pages in the modified queue, though, so they don't
// get lost in the inactive queue.
dprintf("PageWriteWrapper: Failed to write page %p: %s\n", fPage,
strerror(result));
if (!fPage->busy_writing) {
// 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 {
// Writing the page failed -- mark the page modified and move it to
// an appropriate queue other than the modified queue, so we don't
// keep trying to write it over and over again. We keep
// non-temporary pages in the modified queue, though, so they don't
// get lost in the inactive queue.
dprintf("PageWriteWrapper: Failed to write page %p: %s\n", fPage,
strerror(result));
fPage->modified = true;
if (!fCache->temporary)
set_page_state(fPage, PAGE_STATE_MODIFIED);
else if (fPage->IsMapped())
set_page_state(fPage, PAGE_STATE_ACTIVE);
else
set_page_state(fPage, PAGE_STATE_INACTIVE);
fPage->modified = true;
if (!fCache->temporary)
set_page_state(fPage, PAGE_STATE_MODIFIED);
else if (fPage->IsMapped())
set_page_state(fPage, PAGE_STATE_ACTIVE);
else
set_page_state(fPage, PAGE_STATE_INACTIVE);
fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage);
fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage);
success = false;
}
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
success = false;
}
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
fIsActive = false;
return success;