321 lines
10 KiB
C++
321 lines
10 KiB
C++
// Copyright 2017 The Chromium 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 "base/message_loop/message_pump_fuchsia.h"
|
|
|
|
#include <lib/async-loop/cpp/loop.h>
|
|
#include <lib/async-loop/default.h>
|
|
#include <lib/fdio/io.h>
|
|
#include <lib/fdio/unsafe.h>
|
|
#include <lib/zx/time.h>
|
|
#include <zircon/status.h>
|
|
#include <zircon/syscalls.h>
|
|
|
|
#include "base/auto_reset.h"
|
|
#include "base/fuchsia/fuchsia_logging.h"
|
|
#include "base/logging.h"
|
|
#include "base/trace_event/trace_event.h"
|
|
|
|
namespace base {
|
|
|
|
MessagePumpFuchsia::ZxHandleWatchController::ZxHandleWatchController(
|
|
const Location& from_here)
|
|
: async_wait_t({}), created_from_location_(from_here) {}
|
|
|
|
MessagePumpFuchsia::ZxHandleWatchController::~ZxHandleWatchController() {
|
|
if (!StopWatchingZxHandle())
|
|
NOTREACHED();
|
|
}
|
|
|
|
bool MessagePumpFuchsia::ZxHandleWatchController::WaitBegin() {
|
|
DCHECK(!handler);
|
|
async_wait_t::handler = &HandleSignal;
|
|
|
|
zx_status_t status =
|
|
async_begin_wait(weak_pump_->async_loop_->dispatcher(), this);
|
|
if (status != ZX_OK) {
|
|
ZX_DLOG(ERROR, status) << "async_begin_wait():"
|
|
<< created_from_location_.ToString();
|
|
async_wait_t::handler = nullptr;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MessagePumpFuchsia::ZxHandleWatchController::StopWatchingZxHandle() {
|
|
if (was_stopped_) {
|
|
DCHECK(!*was_stopped_);
|
|
*was_stopped_ = true;
|
|
|
|
// |was_stopped_| points at a value stored on the stack, which will go out
|
|
// of scope. MessagePumpFuchsia::Run() will reset it only if the value is
|
|
// false. So we need to reset this pointer here as well, to make sure it's
|
|
// not used again.
|
|
was_stopped_ = nullptr;
|
|
}
|
|
|
|
// If the pump is gone then there is nothing to cancel.
|
|
if (!weak_pump_)
|
|
return true;
|
|
|
|
// |handler| is set when waiting for a signal.
|
|
if (!handler)
|
|
return true;
|
|
|
|
async_wait_t::handler = nullptr;
|
|
|
|
zx_status_t result =
|
|
async_cancel_wait(weak_pump_->async_loop_->dispatcher(), this);
|
|
ZX_DLOG_IF(ERROR, result != ZX_OK, result)
|
|
<< "async_cancel_wait(): " << created_from_location_.ToString();
|
|
return result == ZX_OK;
|
|
}
|
|
|
|
// static
|
|
void MessagePumpFuchsia::ZxHandleWatchController::HandleSignal(
|
|
async_dispatcher_t* async,
|
|
async_wait_t* wait,
|
|
zx_status_t status,
|
|
const zx_packet_signal_t* signal) {
|
|
TRACE_EVENT0("toplevel", "ZxHandleSignal");
|
|
|
|
ZxHandleWatchController* controller =
|
|
static_cast<ZxHandleWatchController*>(wait);
|
|
DCHECK_EQ(controller->handler, &HandleSignal);
|
|
|
|
if (status != ZX_OK) {
|
|
ZX_DLOG(WARNING, status) << "async wait failed: "
|
|
<< controller->created_from_location_.ToString();
|
|
return;
|
|
}
|
|
|
|
controller->handler = nullptr;
|
|
|
|
// In the case of a persistent Watch, the Watch may be stopped and
|
|
// potentially deleted by the caller within the callback, in which case
|
|
// |controller| should not be accessed again, and we mustn't continue the
|
|
// watch. We check for this with a bool on the stack, which the Watch
|
|
// receives a pointer to.
|
|
bool was_stopped = false;
|
|
controller->was_stopped_ = &was_stopped;
|
|
|
|
controller->watcher_->OnZxHandleSignalled(wait->object, signal->observed);
|
|
|
|
if (was_stopped)
|
|
return;
|
|
|
|
controller->was_stopped_ = nullptr;
|
|
|
|
if (controller->persistent_)
|
|
controller->WaitBegin();
|
|
}
|
|
|
|
void MessagePumpFuchsia::FdWatchController::OnZxHandleSignalled(
|
|
zx_handle_t handle,
|
|
zx_signals_t signals) {
|
|
uint32_t events;
|
|
fdio_unsafe_wait_end(io_, signals, &events);
|
|
|
|
// |events| can include other spurious things, in particular, that an fd
|
|
// is writable, when we only asked to know when it was readable. In that
|
|
// case, we don't want to call both the CanWrite and CanRead callback,
|
|
// when the caller asked for only, for example, readable callbacks. So,
|
|
// mask with the events that we actually wanted to know about.
|
|
events &= desired_events_;
|
|
DCHECK_NE(0u, events);
|
|
|
|
// Each |watcher_| callback we invoke may stop or delete |this|. The pump has
|
|
// set |was_stopped_| to point to a safe location on the calling stack, so we
|
|
// can use that to detect being stopped mid-callback and avoid doing further
|
|
// work that would touch |this|.
|
|
bool* was_stopped = was_stopped_;
|
|
if (events & FDIO_EVT_WRITABLE)
|
|
watcher_->OnFileCanWriteWithoutBlocking(fd_);
|
|
if (!*was_stopped && (events & FDIO_EVT_READABLE))
|
|
watcher_->OnFileCanReadWithoutBlocking(fd_);
|
|
|
|
// Don't add additional work here without checking |*was_stopped_| again.
|
|
}
|
|
|
|
MessagePumpFuchsia::FdWatchController::FdWatchController(
|
|
const Location& from_here)
|
|
: FdWatchControllerInterface(from_here),
|
|
ZxHandleWatchController(from_here) {}
|
|
|
|
MessagePumpFuchsia::FdWatchController::~FdWatchController() {
|
|
if (!StopWatchingFileDescriptor())
|
|
NOTREACHED();
|
|
}
|
|
|
|
bool MessagePumpFuchsia::FdWatchController::WaitBegin() {
|
|
// Refresh the |handle_| and |desired_signals_| from the mxio for the fd.
|
|
// Some types of fdio map read/write events to different signals depending on
|
|
// their current state, so we must do this every time we begin to wait.
|
|
fdio_unsafe_wait_begin(io_, desired_events_, &object, &trigger);
|
|
if (async_wait_t::object == ZX_HANDLE_INVALID) {
|
|
DLOG(ERROR) << "fdio_wait_begin failed: "
|
|
<< ZxHandleWatchController::created_from_location_.ToString();
|
|
return false;
|
|
}
|
|
|
|
return MessagePumpFuchsia::ZxHandleWatchController::WaitBegin();
|
|
}
|
|
|
|
bool MessagePumpFuchsia::FdWatchController::StopWatchingFileDescriptor() {
|
|
bool success = StopWatchingZxHandle();
|
|
if (io_) {
|
|
fdio_unsafe_release(io_);
|
|
io_ = nullptr;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
MessagePumpFuchsia::MessagePumpFuchsia()
|
|
: async_loop_(new async::Loop(&kAsyncLoopConfigAttachToCurrentThread)),
|
|
weak_factory_(this) {}
|
|
MessagePumpFuchsia::~MessagePumpFuchsia() = default;
|
|
|
|
bool MessagePumpFuchsia::WatchFileDescriptor(int fd,
|
|
bool persistent,
|
|
int mode,
|
|
FdWatchController* controller,
|
|
FdWatcher* delegate) {
|
|
DCHECK_GE(fd, 0);
|
|
DCHECK(controller);
|
|
DCHECK(delegate);
|
|
|
|
if (!controller->StopWatchingFileDescriptor())
|
|
NOTREACHED();
|
|
|
|
controller->fd_ = fd;
|
|
controller->watcher_ = delegate;
|
|
|
|
DCHECK(!controller->io_);
|
|
controller->io_ = fdio_unsafe_fd_to_io(fd);
|
|
if (!controller->io_) {
|
|
DLOG(ERROR) << "Failed to get IO for FD";
|
|
return false;
|
|
}
|
|
|
|
switch (mode) {
|
|
case WATCH_READ:
|
|
controller->desired_events_ = FDIO_EVT_READABLE;
|
|
break;
|
|
case WATCH_WRITE:
|
|
controller->desired_events_ = FDIO_EVT_WRITABLE;
|
|
break;
|
|
case WATCH_READ_WRITE:
|
|
controller->desired_events_ = FDIO_EVT_READABLE | FDIO_EVT_WRITABLE;
|
|
break;
|
|
default:
|
|
NOTREACHED() << "unexpected mode: " << mode;
|
|
return false;
|
|
}
|
|
|
|
// Pass dummy |handle| and |signals| values to WatchZxHandle(). The real
|
|
// values will be populated by FdWatchController::WaitBegin(), before actually
|
|
// starting the wait operation.
|
|
return WatchZxHandle(ZX_HANDLE_INVALID, persistent, 1, controller,
|
|
controller);
|
|
}
|
|
|
|
bool MessagePumpFuchsia::WatchZxHandle(zx_handle_t handle,
|
|
bool persistent,
|
|
zx_signals_t signals,
|
|
ZxHandleWatchController* controller,
|
|
ZxHandleWatcher* delegate) {
|
|
DCHECK_NE(0u, signals);
|
|
DCHECK(controller);
|
|
DCHECK(delegate);
|
|
DCHECK(handle == ZX_HANDLE_INVALID ||
|
|
controller->async_wait_t::object == ZX_HANDLE_INVALID ||
|
|
handle == controller->async_wait_t::object);
|
|
|
|
if (!controller->StopWatchingZxHandle())
|
|
NOTREACHED();
|
|
|
|
controller->async_wait_t::object = handle;
|
|
controller->persistent_ = persistent;
|
|
controller->async_wait_t::trigger = signals;
|
|
controller->watcher_ = delegate;
|
|
|
|
controller->weak_pump_ = weak_factory_.GetWeakPtr();
|
|
|
|
return controller->WaitBegin();
|
|
}
|
|
|
|
bool MessagePumpFuchsia::HandleIoEventsUntil(zx_time_t deadline) {
|
|
zx_status_t status = async_loop_->Run(zx::time(deadline), /*once=*/true);
|
|
switch (status) {
|
|
// Return true if some tasks or events were dispatched or if the dispatcher
|
|
// was stopped by ScheduleWork().
|
|
case ZX_OK:
|
|
return true;
|
|
|
|
case ZX_ERR_CANCELED:
|
|
async_loop_->ResetQuit();
|
|
return true;
|
|
|
|
case ZX_ERR_TIMED_OUT:
|
|
return false;
|
|
|
|
default:
|
|
ZX_DLOG(DCHECK, status) << "unexpected wait status";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void MessagePumpFuchsia::Run(Delegate* delegate) {
|
|
AutoReset<bool> auto_reset_keep_running(&keep_running_, true);
|
|
|
|
for (;;) {
|
|
const Delegate::NextWorkInfo next_work_info = delegate->DoSomeWork();
|
|
if (!keep_running_)
|
|
break;
|
|
|
|
const bool did_handle_io_event = HandleIoEventsUntil(/*deadline=*/0);
|
|
if (!keep_running_)
|
|
break;
|
|
|
|
bool attempt_more_work =
|
|
next_work_info.is_immediate() || did_handle_io_event;
|
|
if (attempt_more_work)
|
|
continue;
|
|
|
|
attempt_more_work = delegate->DoIdleWork();
|
|
if (!keep_running_)
|
|
break;
|
|
|
|
if (attempt_more_work)
|
|
continue;
|
|
|
|
zx_time_t deadline = next_work_info.delayed_run_time.is_max()
|
|
? ZX_TIME_INFINITE
|
|
: next_work_info.delayed_run_time.ToZxTime();
|
|
|
|
HandleIoEventsUntil(deadline);
|
|
}
|
|
}
|
|
|
|
void MessagePumpFuchsia::Quit() {
|
|
keep_running_ = false;
|
|
}
|
|
|
|
void MessagePumpFuchsia::ScheduleWork() {
|
|
// Stop async_loop to let MessagePumpFuchsia::Run() handle message loop tasks.
|
|
async_loop_->Quit();
|
|
}
|
|
|
|
void MessagePumpFuchsia::ScheduleDelayedWork(
|
|
const TimeTicks& delayed_work_time) {
|
|
// Since this is always called from the same thread as Run(), there is nothing
|
|
// to do as the loop is already running. It will wait in Run() with the
|
|
// correct timeout when it's out of immediate tasks.
|
|
// TODO(https://crbug.com/885371): Consider removing ScheduleDelayedWork()
|
|
// when all pumps function this way (bit.ly/merge-message-pump-do-work).
|
|
}
|
|
|
|
} // namespace base
|