blob: a60de7662800ca0ba914031d7063574aa6b8f179 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/memory/monitor/pressure.h"
#include <fuchsia/boot/c/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/job.h>
#include <sys/stat.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include "src/lib/fxl/logging.h"
namespace monitor {
// Called from the main dispatcher thread, which is also the provider_dispatcher_ thread.
// Sets up another thread "memory-pressure-loop", which waits on memory pressure level changes from
// the kernel. If a change is observed, this thread posts tasks to the main thread i.e. the
// provider_dispatcher_ thread (which also handles registration and deletion of watchers).
Pressure::Pressure(bool watch_for_changes, sys::ComponentContext* context,
async_dispatcher_t* dispatcher)
: provider_dispatcher_(dispatcher) {
if (InitMemPressureEvents() != ZX_OK) {
return;
}
if (context) {
context->outgoing()->AddPublicService(bindings_.GetHandler(this));
}
if (watch_for_changes) {
loop_.StartThread("memory-pressure-loop");
watch_task_.Post(loop_.dispatcher());
}
}
// Called from the main dispatcher thread.
zx_status_t Pressure::InitMemPressureEvents() {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx::channel::create returned " << zx_status_get_string(status);
return status;
}
const char* root_job_svc = "/svc/fuchsia.boot.RootJobForInspect";
status = fdio_service_connect(root_job_svc, remote.release());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "fdio_service_connect returned " << zx_status_get_string(status);
return status;
}
zx::job root_job;
status = fuchsia_boot_RootJobForInspectGet(local.get(), root_job.reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "fuchsia_boot_RootJobForInspectGet returned " << zx_status_get_string(status);
return status;
}
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_MEMORY_PRESSURE_CRITICAL,
events_[Level::kCritical].reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_system_get_event [CRITICAL] returned " << zx_status_get_string(status);
return status;
}
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_MEMORY_PRESSURE_WARNING,
events_[Level::kWarning].reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_system_get_event [WARNING] returned " << zx_status_get_string(status);
return status;
}
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_MEMORY_PRESSURE_NORMAL,
events_[Level::kNormal].reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_system_get_event [NORMAL] returned " << zx_status_get_string(status);
return status;
}
for (size_t i = 0; i < Level::kNumLevels; i++) {
wait_items_[i].handle = events_[i].get();
wait_items_[i].waitfor = ZX_EVENT_SIGNALED;
wait_items_[i].pending = 0;
}
return ZX_OK;
}
// Called from the memory-pressure-loop thread.
void Pressure::WatchForChanges() {
WaitOnLevelChange();
watch_task_.Post(loop_.dispatcher());
}
// Called from the memory-pressure-loop thread.
void Pressure::WaitOnLevelChange() {
// Wait on all events the first time around.
size_t num_wait_items = (level_ == Level::kNumLevels) ? Level::kNumLevels : Level::kNumLevels - 1;
zx_status_t status = zx_object_wait_many(wait_items_.data(), num_wait_items, ZX_TIME_INFINITE);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_object_wait_many returned " << zx_status_get_string(status);
return;
}
for (size_t i = 0; i < Level::kNumLevels; i++) {
if (wait_items_[i].pending) {
wait_items_[i].pending = 0;
OnLevelChanged(wait_items_[i].handle);
// Move the event currently asserted to the end of the array.
// Wait on only the first |kNumLevels| - 1 items next time around.
std::swap(wait_items_[i].handle, wait_items_[Level::kNumLevels - 1].handle);
break;
}
}
}
// Called from the memory-pressure-loop thread.
void Pressure::OnLevelChanged(zx_handle_t handle) {
Level old_level = level_;
for (size_t i = 0; i < Level::kNumLevels; i++) {
if (events_[i].get() == handle) {
level_ = Level(i);
break;
}
}
FX_LOGS(INFO) << "Memory pressure level changed from " << kLevelNames[old_level] << " to "
<< kLevelNames[level_];
if (provider_dispatcher_) {
post_task_.Post(provider_dispatcher_);
}
}
// Called from the provider_dispatcher_ thread.
void Pressure::PostLevelChange() {
Level level_to_send = level_;
// TODO(rashaeqbal): Throttle notifications to prevent thrashing.
for (auto& watcher : watchers_) {
// Notify the watcher only if we received a response for the previous level change, i.e. there
// is no pending callback.
if (!watcher->pending_callback) {
watcher->pending_callback = true;
NotifyWatcher(watcher.get(), level_to_send);
}
}
}
// Called from the provider_dispatcher_ thread.
void Pressure::NotifyWatcher(WatcherState* watcher, Level level) {
// We should already have set |pending_callback| when the notification (call to NotifyWatcher())
// was posted, to prevent removing |WatcherState| from |watchers_| in the error handler.
ZX_DEBUG_ASSERT(watcher->pending_callback);
// We should not be notifying a watcher if |needs_free| is set - indicating that a delayed free is
// required. This can only happen if there was a pending callback when we tried to release the
// watcher. No new notifications can be sent out while there is a pending callback. And when the
// callback is invoked, the |WatcherState| is removed from the |watchers_| vector, so we won't
// post any new notifications after that.
ZX_DEBUG_ASSERT(!watcher->needs_free);
watcher->level_sent = level;
watcher->proxy->OnLevelChanged(ConvertLevel(level),
[watcher, this]() { OnLevelChangedCallback(watcher); });
}
// Called from the provider_dispatcher_ thread.
void Pressure::OnLevelChangedCallback(WatcherState* watcher) {
watcher->pending_callback = false;
// The error handler invoked ReleaseWatcher(), but we could not free the |WatcherState| because of
// this outstanding callback. It is safe to free the watcher now. There are no more outstanding
// callbacks, and no new notifications (since a new notification is posted only if there is no
// pending callback).
if (watcher->needs_free) {
ReleaseWatcher(watcher->proxy.get());
return;
}
Level current_level = level_;
// The watcher might have missed a level change if it occurred before this callback. If the
// level has changed, notify the watcher.
if (watcher->level_sent != current_level) {
// Set |pending_callback| to true here before posting the NotifyWatcher() call. This ensures
// that if ReleaseWatcher() is called (via the error handler) after we post the call, but before
// we dispatch it, we don't access a freed |WatcherState*| in the NotifyWatcher() call.
// ReleaseWatcher() will find |pending_callback| set, hence delay freeing the watcher and set
// |needs_free| to true. NotifyWatcher() will operate on a valid |WatcherState*|, the next
// callback will find |needs_free| set and free the watcher.
watcher->pending_callback = true;
async::PostTask(provider_dispatcher_,
[watcher, current_level, this]() { NotifyWatcher(watcher, current_level); });
}
}
// Called from the provider_dispatcher_ thread.
void Pressure::RegisterWatcher(fidl::InterfaceHandle<fuchsia::memorypressure::Watcher> watcher) {
fuchsia::memorypressure::WatcherPtr watcher_proxy = watcher.Bind();
fuchsia::memorypressure::Watcher* proxy_raw_ptr = watcher_proxy.get();
watcher_proxy.set_error_handler(
[this, proxy_raw_ptr](zx_status_t status) { ReleaseWatcher(proxy_raw_ptr); });
Level current_level = level_;
watchers_.emplace_back(std::make_unique<WatcherState>(
WatcherState{std::move(watcher_proxy), current_level, false, false}));
// Set |pending_callback| and notify the current level.
watchers_.back()->pending_callback = true;
NotifyWatcher(watchers_.back().get(), current_level);
}
// Called from the provider_dispatcher_ thread.
void Pressure::ReleaseWatcher(fuchsia::memorypressure::Watcher* watcher) {
auto predicate = [watcher](const auto& target) { return target->proxy.get() == watcher; };
auto watcher_to_free = std::find_if(watchers_.begin(), watchers_.end(), predicate);
if (watcher_to_free == watchers_.end()) {
// Not found.
return;
}
// There is a pending callback, which also means that the Watcher (client) holds a reference to
// the |WatcherState| unique pointer (the callback captures a raw pointer - |WatcherState*|).
// Freeing it now can lead to a use-after-free. Set |needs_free| to indicate that we need a
// delayed free, when the pending callback is executed.
//
// NOTE: It is possible that a Watcher exits (closes its connection) and never invokes the
// callback. In that case, we will never be able to free the corresponding |WatcherState|, which
// is fine, since this is the only way we can safeguard against a use-after-free.
if ((*watcher_to_free)->pending_callback) {
(*watcher_to_free)->needs_free = true;
} else {
watchers_.erase(watcher_to_free);
}
}
// Helper function. Has no thread affinity.
fuchsia::memorypressure::Level Pressure::ConvertLevel(Level level) {
switch (level) {
case Level::kCritical:
return fuchsia::memorypressure::Level::CRITICAL;
case Level::kWarning:
return fuchsia::memorypressure::Level::WARNING;
case Level::kNormal:
default:
return fuchsia::memorypressure::Level::NORMAL;
}
}
} // namespace monitor