blob: 77cbc6d20224d4d2676989a227e2399c201f3735 [file] [log] [blame] [edit]
// Copyright 2022 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "vm/anonymous_page_request.h"
#include <lib/fit/defer.h>
#include <kernel/lockdep.h>
#include <kernel/thread.h>
#include <vm/pmm.h>
#include <vm/scanner.h>
namespace {
// Atomic token used to ensure that only one thread performs the informational dump so that in the
// scenario of many threads all reaching the timeout at the same time there is not a rush of spam.
constinit ktl::atomic<bool> dump_info_before_panic_token = true;
} // namespace
zx::result<> AnonymousPageRequest::Allocate() {
DEBUG_ASSERT(active_);
DEBUG_ASSERT(!has_page());
// Although the pmm_wait_till_free_pages call will unblock based on bounded kernel action, and not
// some unbounded user request, the kernel might need to acquire arbitrary locks to achieve this.
// Therefore blanket require no locks here to ensure no accidental lock dependencies. This can be
// relaxed in the future if necessary.
lockdep::AssertNoLocksHeld();
// Time spent waiting is regarded as a memory stall.
ScopedMemoryStall memory_stall;
// Once we return, clear the `active_` flag.
auto defer = fit::defer([this]() { active_ = false; });
// This should only ever end up waiting momentarily until reclamation catches up. As such if we
// end up waiting for a long time then this is probably a sign of a bug in reclamation somewhere,
// so we want to make some noise here.
constexpr zx_duration_mono_t kReportWaitTime = ZX_SEC(5);
constexpr unsigned int kMaxWaits = ZX_SEC(30) / kReportWaitTime;
static_assert(kMaxWaits >= 1);
uint32_t waited = 0;
while (true) {
zx::result<vm_page_t*> page_alloc =
Pmm::Node().WaitForSinglePageAllocation(Deadline::after_mono(kReportWaitTime));
if (page_alloc.is_error()) {
// System is not making forward progress, lets try again.
if (page_alloc.error_value() == ZX_ERR_TIMED_OUT) {
waited++;
dprintf(INFO, "WARNING: Waited %" PRIi64 " seconds to retry PMM allocations\n",
(kReportWaitTime * waited) / ZX_SEC(1));
// Once one thread reaches the maximum retries, it is time to panic, only let the first
// thread to reach this point trigger the panic. All other threads can block in the event.
// This allows the `scanner_debug_dump_state_before_panic` to finish and then panic.
if (waited == kMaxWaits && dump_info_before_panic_token.exchange(false)) {
scanner_debug_dump_state_before_panic();
// If we've been waiting for a while without being able to get more memory and
// without declaring OOM, the memory watchdog is probably wedged and the system is in an
// unrecoverable state. It would be nice to get some diagnostics here but we don't want to
// risk trying to acquire any locks; just panic.
ZX_PANIC("Allocation stalled for %" PRIi64 " seconds",
(kReportWaitTime * waited) / ZX_SEC(1));
}
continue;
}
// The system reached an allocation allowing event, but we lost the race for the memory
// resources. We reset the retry count, since the system IS making forward progress.
if (page_alloc.error_value() == ZX_ERR_SHOULD_WAIT) {
waited = 0;
continue;
}
return page_alloc.take_error();
}
page_ = page_alloc.value();
return zx::ok();
}
__UNREACHABLE;
}
void AnonymousPageRequest::FreePage() { Pmm::Node().FreePage(take_page()); }