blob: e1cda047404f4c39d2e2d72157605c88482a82ba [file] [log] [blame]
// 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 <zircon/status.h>
#include <zircon/syscalls.h>
#include "base/auto_reset.h"
#include "base/logging.h"
namespace base {
MessagePumpFuchsia::ZxHandleWatchController::ZxHandleWatchController(
const Location& from_here)
: created_from_location_(from_here) {}
MessagePumpFuchsia::ZxHandleWatchController::~ZxHandleWatchController() {
if (!StopWatchingZxHandle())
NOTREACHED();
}
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 (!has_begun_)
return true;
has_begun_ = false;
// If the pump is gone then there is nothing to cancel.
if (!weak_pump_)
return true;
int result = zx_port_cancel(weak_pump_->port_.get(), handle_, wait_key());
DLOG_IF(ERROR, result != ZX_OK)
<< "zx_port_cancel(handle=" << handle_
<< ") failed: " << zx_status_get_string(result);
return result == ZX_OK;
}
void MessagePumpFuchsia::FdWatchController::OnZxHandleSignalled(
zx_handle_t handle,
zx_signals_t signals) {
uint32_t events;
__fdio_wait_end(io_, signals, &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)
: ZxHandleWatchController(from_here) {}
MessagePumpFuchsia::FdWatchController::~FdWatchController() {
if (!StopWatchingFileDescriptor())
NOTREACHED();
}
bool MessagePumpFuchsia::FdWatchController::StopWatchingFileDescriptor() {
bool success = StopWatchingZxHandle();
if (io_) {
__fdio_release(io_);
io_ = nullptr;
}
return success;
}
MessagePumpFuchsia::MessagePumpFuchsia() : weak_factory_(this) {
CHECK_EQ(ZX_OK, zx_port_create(0, port_.receive()));
}
MessagePumpFuchsia::~MessagePumpFuchsia() {}
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_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::FdWatchController::WaitBegin() {
// Refresh the |handle_| and |desired_signals_| from the mxio for the fd.
// Some types of mxio map read/write events to different signals depending on
// their current state, so we must do this every time we begin to wait.
__fdio_wait_begin(io_, desired_events_, &handle_, &desired_signals_);
if (handle_ == ZX_HANDLE_INVALID) {
DLOG(ERROR) << "fdio_wait_begin failed";
return false;
}
return MessagePumpFuchsia::ZxHandleWatchController::WaitBegin();
}
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->handle_ == ZX_HANDLE_INVALID ||
handle == controller->handle_);
if (!controller->StopWatchingZxHandle())
NOTREACHED();
controller->handle_ = handle;
controller->persistent_ = persistent;
controller->desired_signals_ = signals;
controller->watcher_ = delegate;
controller->weak_pump_ = weak_factory_.GetWeakPtr();
return controller->WaitBegin();
}
bool MessagePumpFuchsia::ZxHandleWatchController::WaitBegin() {
DCHECK(!has_begun_);
zx_status_t status =
zx_object_wait_async(handle_, weak_pump_->port_.get(), wait_key(),
desired_signals_, ZX_WAIT_ASYNC_ONCE);
if (status != ZX_OK) {
DLOG(ERROR) << "zx_object_wait_async failed: "
<< zx_status_get_string(status)
<< " (port=" << weak_pump_->port_.get() << ")";
return false;
}
has_begun_ = true;
return true;
}
uint32_t MessagePumpFuchsia::ZxHandleWatchController::WaitEnd(
zx_signals_t signals) {
DCHECK(has_begun_);
has_begun_ = false;
// |signals| 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.
signals &= desired_signals_;
return signals;
}
bool MessagePumpFuchsia::HandleEvents(zx_time_t deadline) {
zx_port_packet_t packet;
const zx_status_t wait_status =
zx_port_wait(port_.get(), deadline, &packet, 0);
if (wait_status == ZX_ERR_TIMED_OUT)
return false;
if (wait_status != ZX_OK) {
NOTREACHED() << "unexpected wait status: "
<< zx_status_get_string(wait_status);
return false;
}
if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE) {
// A watched fd caused the wakeup via zx_object_wait_async().
DCHECK_EQ(ZX_OK, packet.status);
ZxHandleWatchController* controller =
reinterpret_cast<ZxHandleWatchController*>(
static_cast<uintptr_t>(packet.key));
DCHECK_NE(0u, packet.signal.trigger & packet.signal.observed);
zx_signals_t signals = controller->WaitEnd(packet.signal.observed);
// 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 controller_was_stopped = false;
controller->was_stopped_ = &controller_was_stopped;
controller->watcher_->OnZxHandleSignalled(controller->handle_, signals);
if (!controller_was_stopped) {
controller->was_stopped_ = nullptr;
if (controller->persistent_)
controller->WaitBegin();
}
} else {
// Wakeup caused by ScheduleWork().
DCHECK_EQ(ZX_PKT_TYPE_USER, packet.type);
}
return true;
}
void MessagePumpFuchsia::Run(Delegate* delegate) {
AutoReset<bool> auto_reset_keep_running(&keep_running_, true);
for (;;) {
bool did_work = delegate->DoWork();
if (!keep_running_)
break;
did_work |= delegate->DoDelayedWork(&delayed_work_time_);
if (!keep_running_)
break;
did_work |= HandleEvents(/*deadline=*/0);
if (!keep_running_)
break;
if (did_work)
continue;
did_work = delegate->DoIdleWork();
if (!keep_running_)
break;
if (did_work)
continue;
zx_time_t deadline = delayed_work_time_.is_null()
? ZX_TIME_INFINITE
: delayed_work_time_.ToZxTime();
HandleEvents(deadline);
}
}
void MessagePumpFuchsia::Quit() {
keep_running_ = false;
}
void MessagePumpFuchsia::ScheduleWork() {
// Since this can be called on any thread, we need to ensure that our Run loop
// wakes up.
zx_port_packet_t packet = {};
packet.type = ZX_PKT_TYPE_USER;
zx_status_t status = zx_port_queue(port_.get(), &packet, 0);
DLOG_IF(ERROR, status != ZX_OK)
<< "zx_port_queue failed: " << status << " (port=" << port_.get() << ")";
}
void MessagePumpFuchsia::ScheduleDelayedWork(
const TimeTicks& delayed_work_time) {
// We know that we can't be blocked right now since this method can only be
// called on the same thread as Run, so we only need to update our record of
// how long to sleep when we do sleep.
delayed_work_time_ = delayed_work_time;
}
} // namespace base