blob: 5ee69c516a8a034ef33ee719b7e315e9b33a7c39 [file] [log] [blame]
// Copyright 2013 The Flutter 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 "flutter/fml/platform/fuchsia/message_loop_fuchsia.h"
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include "flutter/fml/platform/fuchsia/task_observers.h"
namespace fml {
namespace {
// See comment on `ExecuteAfterTaskObservers` for explanation.
static void LoopEpilogue(async_loop_t*, void*) {
ExecuteAfterTaskObservers();
}
constexpr async_loop_config_t kLoopConfig = {
.make_default_for_current_thread = false,
.epilogue = &LoopEpilogue,
};
} // namespace
MessageLoopFuchsia::MessageLoopFuchsia() : loop_(&kLoopConfig) {
async_set_default_dispatcher(loop_.dispatcher());
zx_status_t timer_status =
zx::timer::create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &timer_);
FML_CHECK(timer_status == ZX_OK)
<< "MessageLoopFuchsia failed to create timer; status="
<< zx_status_get_string(timer_status);
}
MessageLoopFuchsia::~MessageLoopFuchsia() {
// It is only safe to unset the current thread's default dispatcher if it is
// already pointing to this loop.
if (async_get_default_dispatcher() == loop_.dispatcher()) {
async_set_default_dispatcher(nullptr);
}
}
void MessageLoopFuchsia::Run() {
timer_wait_ = std::make_unique<async::Wait>(
timer_.get(), ZX_TIMER_SIGNALED, 0,
[this](async_dispatcher_t* dispatcher, async::Wait* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status == ZX_ERR_CANCELED) {
return;
}
FML_CHECK(signal->observed & ZX_TIMER_SIGNALED);
// Cancel the timer now, because `RunExpiredTasksNow` might not re-arm
// the timer. That would leave the timer in a signalled state and it
// would trigger the async::Wait again immediately, creating a busy
// loop.
//
// NOTE: It is not neccesary to synchronize this with the timer_.set()
// call below, even though WakeUp() can be called from any thread and
// thus timer_.set() can run in parallel with this timer_.cancel().
//
// Zircon will synchronize the 2 syscalls internally, and the Wait loop
// here is resilient to cancel() and set() being called in any order.
timer_.cancel();
// Run the tasks, which may or may not re-arm the timer for the future.
RunExpiredTasksNow();
// Kick off the next iteration of the timer wait loop.
zx_status_t wait_status = wait->Begin(loop_.dispatcher());
FML_CHECK(wait_status == ZX_OK)
<< "MessageLoopFuchsia::WakeUp failed to wait for timer; status="
<< zx_status_get_string(wait_status);
});
// Kick off the first iteration of the timer wait loop.
zx_status_t wait_status = timer_wait_->Begin(loop_.dispatcher());
FML_CHECK(wait_status == ZX_OK)
<< "MessageLoopFuchsia::WakeUp failed to wait for timer; status="
<< zx_status_get_string(wait_status);
// Kick off the underlying async loop that services the timer wait in addition
// to other tasks and waits queued on its `async_dispatcher_t`.
loop_.Run();
// Ensure any pending waits on the timer are properly canceled.
if (timer_wait_->is_pending()) {
timer_wait_->Cancel();
timer_.cancel();
}
}
void MessageLoopFuchsia::Terminate() {
loop_.Quit();
}
void MessageLoopFuchsia::WakeUp(fml::TimePoint time_point) {
constexpr zx::duration kZeroSlack(0);
zx::time due_time(time_point.ToEpochDelta().ToNanoseconds());
zx_status_t timer_status = timer_.set(due_time, kZeroSlack);
FML_CHECK(timer_status == ZX_OK)
<< "MessageLoopFuchsia::WakeUp failed to set timer; status="
<< zx_status_get_string(timer_status);
}
} // namespace fml