| // 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/fdio/io.h> | |
| #include <lib/fdio/private.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 "starboard/types.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_dispatcher_, this); | |
| if (status != ZX_OK) { | |
| ZX_DLOG(ERROR, status) << "async_begin_wait() failed"; | |
| 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_dispatcher_, this); | |
| ZX_DLOG_IF(ERROR, result != ZX_OK, result) << "async_cancel_wait failed"; | |
| 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) { | |
| if (status != ZX_OK) { | |
| ZX_LOG(WARNING, status) << "async wait failed"; | |
| return; | |
| } | |
| ZxHandleWatchController* controller = | |
| static_cast<ZxHandleWatchController*>(wait); | |
| DCHECK_EQ(controller->handler, &HandleSignal); | |
| controller->handler = nullptr; | |
| // |signal| 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. | |
| zx_signals_t signals = signal->trigger & signal->observed; | |
| DCHECK_NE(0u, signals); | |
| // 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, signals); | |
| 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_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) | |
| : 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_wait_begin(io_, desired_events_, &object, &trigger); | |
| if (async_wait_t::object == ZX_HANDLE_INVALID) { | |
| DLOG(ERROR) << "fdio_wait_begin failed"; | |
| return false; | |
| } | |
| return MessagePumpFuchsia::ZxHandleWatchController::WaitBegin(); | |
| } | |
| bool MessagePumpFuchsia::FdWatchController::StopWatchingFileDescriptor() { | |
| bool success = StopWatchingZxHandle(); | |
| if (io_) { | |
| __fdio_release(io_); | |
| io_ = nullptr; | |
| } | |
| return success; | |
| } | |
| MessagePumpFuchsia::MessagePumpFuchsia() : 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_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::HandleEvents(zx_time_t deadline) { | |
| zx_status_t status = async_dispatcher_.DispatchOrWaitUntil(deadline); | |
| switch (status) { | |
| // Return true if some tasks or events were dispatched or if the dispatcher | |
| // was stopped by ScheduleWork(). | |
| case ZX_OK: | |
| case ZX_ERR_CANCELED: | |
| 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 (;;) { | |
| 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() { | |
| // Stop AsyncDispatcher to let MessagePumpFuchsia::Run() handle message loop | |
| // tasks. | |
| async_dispatcher_.Stop(); | |
| } | |
| 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 |