ram_disk: Do not use cache_get_pages in Trim().

cache_get_pages locks and unlocks the cache to wait for busy pages,
as well as after it's done, which creates a race where some other
thread may start waiting on a page that we have marked busy. But
as we are about to free those pages, that wait will deadlock.

Just loop through the cache's pages directly, skipping any busy ones
and freeing the others directly.

Fixes a deadlock observed with BFS on a ramdisk.
This commit is contained in:
Augustin Cavalier
2025-03-08 10:16:02 -05:00
parent 876171f588
commit 906623697e
@@ -575,36 +575,23 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> {
TRACE("ramdisk: trim %" B_PRIu64 " bytes from %" B_PRIu64 "\n",
length, offset);
ASSERT(offset % B_PAGE_SIZE == 0);
ASSERT(length % B_PAGE_SIZE == 0);
BStackOrHeapArray<vm_page*, 16> pages(length / B_PAGE_SIZE);
if (!pages.IsValid()) {
result = B_NO_MEMORY;
break;
}
cache_get_pages(fCache, (off_t)offset, (off_t)length, false, pages);
AutoLocker<VMCache> locker(fCache);
for (uint64 j = 0; j < length / B_PAGE_SIZE; j++) {
// If we run out of pages (some may already be trimmed), stop.
if (pages[j] == NULL)
break;
vm_page* page = fCache->LookupPage(offset + j * B_PAGE_SIZE);
if (page == NULL || page->busy)
continue;
TRACE("free range %" B_PRIu32 ", page %" B_PRIu64 ", offset %"
B_PRIu64 "\n", i, j, offset);
DEBUG_PAGE_ACCESS_START(pages[j]);
DEBUG_PAGE_ACCESS_START(page);
if (pages[j]->Cache() != NULL)
fCache->RemovePage(pages[j]);
vm_page_free(NULL, pages[j]);
fCache->RemovePage(page);
vm_page_free(NULL, page);
trimmedSize += B_PAGE_SIZE;
}
}
trimData->trimmed_size = trimmedSize;
return result;
}