| Index: mojo/public/cpp/system/simple_watcher.cc
|
| diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..218a350fc95237d462c0b01bf7205c49921bd3a9
|
| --- /dev/null
|
| +++ b/mojo/public/cpp/system/simple_watcher.cc
|
| @@ -0,0 +1,273 @@
|
| +// 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 "mojo/public/cpp/system/simple_watcher.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/macros.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/single_thread_task_runner.h"
|
| +#include "base/synchronization/lock.h"
|
| +#include "base/trace_event/heap_profiler.h"
|
| +#include "mojo/public/c/system/watcher.h"
|
| +
|
| +namespace mojo {
|
| +
|
| +// Thread-safe Context object used to dispatch watch notifications from a
|
| +// arbitrary threads.
|
| +class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
|
| + public:
|
| + // Creates a |Context| instance for a new watch on |watcher|, to watch
|
| + // |handle| for |signals|.
|
| + static scoped_refptr<Context> Create(
|
| + base::WeakPtr<SimpleWatcher> watcher,
|
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner,
|
| + WatcherHandle watcher_handle,
|
| + Handle handle,
|
| + MojoHandleSignals signals,
|
| + MojoResult* watch_result) {
|
| + scoped_refptr<Context> context = new Context(watcher, task_runner);
|
| +
|
| + // If MojoWatch succeeds, it assumes ownership of a reference to |context|.
|
| + // In that case, this reference is balanced in CallNotify() when |result| is
|
| + // |MOJO_RESULT_CANCELLED|.
|
| + context->AddRef();
|
| +
|
| + *watch_result = MojoWatch(watcher_handle.value(), handle.value(), signals,
|
| + context->value());
|
| + if (*watch_result != MOJO_RESULT_OK) {
|
| + // Balanced by the AddRef() above since watching failed.
|
| + context->Release();
|
| + return nullptr;
|
| + }
|
| +
|
| + return context;
|
| + }
|
| +
|
| + static void CallNotify(uintptr_t context_value,
|
| + MojoResult result,
|
| + MojoHandleSignalsState signals_state,
|
| + MojoWatcherNotificationFlags flags) {
|
| + auto* context = reinterpret_cast<Context*>(context_value);
|
| + context->Notify(result, signals_state, flags);
|
| +
|
| + // That was the last notification for the context. We can release the ref
|
| + // owned by the watch, which may in turn delete the Context.
|
| + if (result == MOJO_RESULT_CANCELLED)
|
| + context->Release();
|
| + }
|
| +
|
| + uintptr_t value() const { return reinterpret_cast<uintptr_t>(this); }
|
| +
|
| + void DisableCancellationNotifications() {
|
| + base::AutoLock lock(lock_);
|
| + enable_cancellation_notifications_ = false;
|
| + }
|
| +
|
| + private:
|
| + friend class base::RefCountedThreadSafe<Context>;
|
| +
|
| + Context(base::WeakPtr<SimpleWatcher> weak_watcher,
|
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner)
|
| + : weak_watcher_(weak_watcher), task_runner_(task_runner) {}
|
| + ~Context() {}
|
| +
|
| + void Notify(MojoResult result,
|
| + MojoHandleSignalsState signals_state,
|
| + MojoWatcherNotificationFlags flags) {
|
| + if (result == MOJO_RESULT_CANCELLED) {
|
| + // The SimpleWatcher may have explicitly cancelled this watch, so we don't
|
| + // bother dispatching the notification - it would be ignored anyway.
|
| + //
|
| + // TODO(rockot): This shouldn't really be necessary, but there are already
|
| + // instances today where bindings object may be bound and subsequently
|
| + // closed due to pipe error, all before the thread's TaskRunner has been
|
| + // properly initialized.
|
| + base::AutoLock lock(lock_);
|
| + if (!enable_cancellation_notifications_)
|
| + return;
|
| + }
|
| +
|
| + if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) &&
|
| + task_runner_->RunsTasksOnCurrentThread() && weak_watcher_ &&
|
| + weak_watcher_->is_default_task_runner_) {
|
| + // System notifications will trigger from the task runner passed to
|
| + // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the
|
| + // default task runner for the IO thread.
|
| + weak_watcher_->OnHandleReady(make_scoped_refptr(this), result);
|
| + } else {
|
| + task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_,
|
| + make_scoped_refptr(this), result));
|
| + }
|
| + }
|
| +
|
| + const base::WeakPtr<SimpleWatcher> weak_watcher_;
|
| + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
|
| +
|
| + base::Lock lock_;
|
| + bool enable_cancellation_notifications_ = true;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Context);
|
| +};
|
| +
|
| +SimpleWatcher::SimpleWatcher(const tracked_objects::Location& from_here,
|
| + ArmingPolicy arming_policy,
|
| + scoped_refptr<base::SingleThreadTaskRunner> runner)
|
| + : arming_policy_(arming_policy),
|
| + task_runner_(std::move(runner)),
|
| + is_default_task_runner_(task_runner_ ==
|
| + base::ThreadTaskRunnerHandle::Get()),
|
| + heap_profiler_tag_(from_here.file_name()),
|
| + weak_factory_(this) {
|
| + MojoResult rv = CreateWatcher(&Context::CallNotify, &watcher_handle_);
|
| + DCHECK_EQ(MOJO_RESULT_OK, rv);
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +}
|
| +
|
| +SimpleWatcher::~SimpleWatcher() {
|
| + if (IsWatching())
|
| + Cancel();
|
| +}
|
| +
|
| +bool SimpleWatcher::IsWatching() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return context_ != nullptr;
|
| +}
|
| +
|
| +MojoResult SimpleWatcher::Watch(Handle handle,
|
| + MojoHandleSignals signals,
|
| + const ReadyCallback& callback) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(!IsWatching());
|
| + DCHECK(!callback.is_null());
|
| +
|
| + callback_ = callback;
|
| + handle_ = handle;
|
| +
|
| + MojoResult watch_result = MOJO_RESULT_UNKNOWN;
|
| + context_ =
|
| + Context::Create(weak_factory_.GetWeakPtr(), task_runner_,
|
| + watcher_handle_.get(), handle_, signals, &watch_result);
|
| + if (!context_) {
|
| + handle_.set_value(kInvalidHandleValue);
|
| + callback_.Reset();
|
| + DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, watch_result);
|
| + return watch_result;
|
| + }
|
| +
|
| + if (arming_policy_ == ArmingPolicy::AUTOMATIC)
|
| + ArmOrNotify();
|
| +
|
| + return MOJO_RESULT_OK;
|
| +}
|
| +
|
| +void SimpleWatcher::Cancel() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // The watcher may have already been cancelled if the handle was closed.
|
| + if (!context_)
|
| + return;
|
| +
|
| + // Prevent the cancellation notification from being dispatched to
|
| + // OnHandleReady() when cancellation is explicit. See the note in the
|
| + // implementation of DisableCancellationNotifications() above.
|
| + context_->DisableCancellationNotifications();
|
| +
|
| + handle_.set_value(kInvalidHandleValue);
|
| + callback_.Reset();
|
| +
|
| + // Ensure |context_| is unset by the time we call MojoCancelWatch, as may
|
| + // re-enter the notification callback and we want to ensure |context_| is
|
| + // unset by then.
|
| + scoped_refptr<Context> context;
|
| + std::swap(context, context_);
|
| + MojoResult rv =
|
| + MojoCancelWatch(watcher_handle_.get().value(), context->value());
|
| +
|
| + // It's possible this cancellation could race with a handle closure
|
| + // notification, in which case the watch may have already been implicitly
|
| + // cancelled.
|
| + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
|
| +}
|
| +
|
| +MojoResult SimpleWatcher::Arm(MojoResult* ready_result) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + uint32_t num_ready_contexts = 1;
|
| + uintptr_t ready_context;
|
| + MojoResult local_ready_result;
|
| + MojoHandleSignalsState ready_state;
|
| + MojoResult rv =
|
| + MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts,
|
| + &ready_context, &local_ready_result, &ready_state);
|
| + if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
|
| + DCHECK(context_);
|
| + DCHECK_EQ(1u, num_ready_contexts);
|
| + DCHECK_EQ(context_->value(), ready_context);
|
| + if (ready_result)
|
| + *ready_result = local_ready_result;
|
| + }
|
| +
|
| + return rv;
|
| +}
|
| +
|
| +void SimpleWatcher::ArmOrNotify() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // Already cancelled, nothing to do.
|
| + if (!IsWatching())
|
| + return;
|
| +
|
| + MojoResult ready_result;
|
| + MojoResult rv = Arm(&ready_result);
|
| + if (rv == MOJO_RESULT_OK)
|
| + return;
|
| +
|
| + DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv);
|
| + task_runner_->PostTask(FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady,
|
| + weak_factory_.GetWeakPtr(),
|
| + context_, ready_result));
|
| +}
|
| +
|
| +void SimpleWatcher::OnHandleReady(scoped_refptr<const Context> context,
|
| + MojoResult result) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // This notification may be for a previously watched context, in which case
|
| + // we just ignore it.
|
| + if (context != context_)
|
| + return;
|
| +
|
| + ReadyCallback callback = callback_;
|
| + if (result == MOJO_RESULT_CANCELLED) {
|
| + // Implicit cancellation due to someone closing the watched handle. We clear
|
| + // the SimppleWatcher's state before dispatching this.
|
| + context_ = nullptr;
|
| + handle_.set_value(kInvalidHandleValue);
|
| + callback_.Reset();
|
| + }
|
| +
|
| + // NOTE: It's legal for |callback| to delete |this|.
|
| + if (!callback.is_null()) {
|
| + TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_);
|
| +
|
| + base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr();
|
| + callback.Run(result);
|
| + if (!weak_self)
|
| + return;
|
| +
|
| + if (unsatisfiable_)
|
| + return;
|
| +
|
| + // Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying
|
| + // at most once in AUTOMATIC arming mode.
|
| + if (result == MOJO_RESULT_FAILED_PRECONDITION)
|
| + unsatisfiable_ = true;
|
| +
|
| + if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching())
|
| + ArmOrNotify();
|
| + }
|
| +}
|
| +
|
| +} // namespace mojo
|
|
|