| // 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/threading/thread_task_runner_handle.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::SequencedTaskRunner> task_runner, |
| WatcherHandle watcher_handle, |
| Handle handle, |
| MojoHandleSignals signals, |
| MojoWatchCondition condition, |
| int watch_id, |
| MojoResult* watch_result) { |
| scoped_refptr<Context> context = |
| new Context(watcher, task_runner, watch_id); |
| |
| // 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, |
| condition, 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::SequencedTaskRunner> task_runner, |
| int watch_id) |
| : weak_watcher_(weak_watcher), |
| task_runner_(task_runner), |
| watch_id_(watch_id) {} |
| ~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; |
| } |
| |
| HandleSignalsState state(signals_state.satisfied_signals, |
| signals_state.satisfiable_signals); |
| if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) && |
| task_runner_->RunsTasksInCurrentSequence() && weak_watcher_ && |
| weak_watcher_->is_default_task_runner_) { |
| // System notifications will trigger from the task runner passed to |
| // mojo::edk::ScopedIPCSupport. In Chrome this happens to always be the |
| // default task runner for the IO thread. |
| weak_watcher_->OnHandleReady(watch_id_, result, state); |
| } else { |
| task_runner_->PostTask( |
| FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_, |
| watch_id_, result, state)); |
| } |
| } |
| |
| const base::WeakPtr<SimpleWatcher> weak_watcher_; |
| const scoped_refptr<base::SequencedTaskRunner> task_runner_; |
| const int watch_id_; |
| |
| base::Lock lock_; |
| bool enable_cancellation_notifications_ = true; |
| |
| DISALLOW_COPY_AND_ASSIGN(Context); |
| }; |
| |
| SimpleWatcher::SimpleWatcher(const base::Location& from_here, |
| ArmingPolicy arming_policy, |
| scoped_refptr<base::SequencedTaskRunner> runner) |
| : arming_policy_(arming_policy), |
| task_runner_(std::move(runner)), |
| is_default_task_runner_(base::ThreadTaskRunnerHandle::IsSet() && |
| 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_->RunsTasksInCurrentSequence()); |
| } |
| |
| SimpleWatcher::~SimpleWatcher() { |
| if (IsWatching()) |
| Cancel(); |
| } |
| |
| bool SimpleWatcher::IsWatching() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return context_ != nullptr; |
| } |
| |
| MojoResult SimpleWatcher::Watch(Handle handle, |
| MojoHandleSignals signals, |
| MojoWatchCondition condition, |
| const ReadyCallbackWithState& callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!IsWatching()); |
| DCHECK(!callback.is_null()); |
| |
| callback_ = callback; |
| handle_ = handle; |
| watch_id_ += 1; |
| |
| MojoResult watch_result = MOJO_RESULT_UNKNOWN; |
| context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_, |
| watcher_handle_.get(), handle_, signals, condition, |
| watch_id_, &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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // 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, |
| HandleSignalsState* ready_state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| uint32_t num_ready_contexts = 1; |
| uintptr_t ready_context; |
| MojoResult local_ready_result; |
| HandleSignalsState local_ready_state; |
| if (!ready_state) |
| ready_state = &local_ready_state; |
| MojoResult rv = |
| MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts, |
| &ready_context, &local_ready_result, |
| reinterpret_cast<MojoHandleSignalsState*>(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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Already cancelled, nothing to do. |
| if (!IsWatching()) |
| return; |
| |
| MojoResult ready_result; |
| HandleSignalsState ready_state; |
| MojoResult rv = Arm(&ready_result, &ready_state); |
| 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(), |
| watch_id_, ready_result, ready_state)); |
| } |
| |
| void SimpleWatcher::OnHandleReady(int watch_id, |
| MojoResult result, |
| const HandleSignalsState& state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // This notification may be for a previously watched context, in which case |
| // we just ignore it. |
| if (watch_id != watch_id_) |
| return; |
| |
| ReadyCallbackWithState 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, state); |
| 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 |