1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "mojo/public/cpp/system/simple_watcher.h"
6 
7 #include "base/bind.h"
8 #include "base/macros.h"
9 #include "base/memory/ptr_util.h"
10 #include "base/sequenced_task_runner.h"
11 #include "base/synchronization/lock.h"
12 #include "base/task/common/task_annotator.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "base/trace_event/heap_profiler.h"
15 #include "base/trace_event/trace_event.h"
16 #include "base/trace_event/typed_macros.h"
17 #include "mojo/public/c/system/trap.h"
18 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pbzero.h"
19 
20 namespace mojo {
21 
22 // Thread-safe Context object used to schedule trap events from arbitrary
23 // threads.
24 class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
25  public:
26   // Creates a |Context| instance for a new watch on |watcher|, to observe
27   // |signals| on |handle|.
Create(base::WeakPtr<SimpleWatcher> watcher,scoped_refptr<base::SequencedTaskRunner> task_runner,TrapHandle trap_handle,Handle handle,MojoHandleSignals signals,MojoTriggerCondition condition,int watch_id,MojoResult * result,const char * handler_tag)28   static scoped_refptr<Context> Create(
29       base::WeakPtr<SimpleWatcher> watcher,
30       scoped_refptr<base::SequencedTaskRunner> task_runner,
31       TrapHandle trap_handle,
32       Handle handle,
33       MojoHandleSignals signals,
34       MojoTriggerCondition condition,
35       int watch_id,
36       MojoResult* result,
37       const char* handler_tag) {
38     scoped_refptr<Context> context =
39         new Context(watcher, task_runner, watch_id, handler_tag);
40 
41     // If MojoAddTrigger succeeds, it effectively assumes ownership of a
42     // reference to |context|. In that case, this reference is balanced in
43     // CallNotify() when |result| is |MOJO_RESULT_CANCELLED|.
44     context->AddRef();
45 
46     *result = MojoAddTrigger(trap_handle.value(), handle.value(), signals,
47                              condition, context->value(), nullptr);
48     if (*result != MOJO_RESULT_OK) {
49       // Balanced by the AddRef() above since MojoAddTrigger failed.
50       context->Release();
51       return nullptr;
52     }
53 
54     return context;
55   }
56 
CallNotify(const MojoTrapEvent * event)57   static void CallNotify(const MojoTrapEvent* event) {
58     auto* context = reinterpret_cast<Context*>(event->trigger_context);
59     context->Notify(event->result, event->signals_state, event->flags);
60 
61     // The trigger was removed. We can release the ref it owned, which in turn
62     // may delete the Context.
63     if (event->result == MOJO_RESULT_CANCELLED)
64       context->Release();
65   }
66 
value() const67   uintptr_t value() const { return reinterpret_cast<uintptr_t>(this); }
68 
69  private:
70   friend class base::RefCountedThreadSafe<Context>;
71 
Context(base::WeakPtr<SimpleWatcher> weak_watcher,scoped_refptr<base::SequencedTaskRunner> task_runner,int watch_id,const char * handler_tag)72   Context(base::WeakPtr<SimpleWatcher> weak_watcher,
73           scoped_refptr<base::SequencedTaskRunner> task_runner,
74           int watch_id,
75           const char* handler_tag)
76       : weak_watcher_(weak_watcher),
77         task_runner_(task_runner),
78         watch_id_(watch_id),
79         handler_tag_(handler_tag) {}
80 
81   ~Context() = default;
82 
Notify(MojoResult result,MojoHandleSignalsState signals_state,MojoTrapEventFlags flags)83   void Notify(MojoResult result,
84               MojoHandleSignalsState signals_state,
85               MojoTrapEventFlags flags) {
86     HandleSignalsState state(signals_state.satisfied_signals,
87                              signals_state.satisfiable_signals);
88     if (!(flags & MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL) &&
89         task_runner_->RunsTasksInCurrentSequence() && weak_watcher_ &&
90         weak_watcher_->is_default_task_runner_) {
91       // System notifications will trigger from the task runner passed to
92       // mojo::core::ScopedIPCSupport. In Chrome this happens to always be
93       // the default task runner for the IO thread.
94       weak_watcher_->OnHandleReady(watch_id_, result, state);
95     } else {
96       {
97         // Annotate the posted task with |handler_tag_| as the IPC interface.
98         base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(handler_tag_);
99         task_runner_->PostTask(
100             FROM_HERE, base::BindOnce(&SimpleWatcher::OnHandleReady,
101                                       weak_watcher_, watch_id_, result, state));
102       }
103     }
104   }
105 
106   const base::WeakPtr<SimpleWatcher> weak_watcher_;
107   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
108   const int watch_id_;
109   const char* handler_tag_ = nullptr;
110 
111   DISALLOW_COPY_AND_ASSIGN(Context);
112 };
113 
SimpleWatcher(const base::Location & from_here,ArmingPolicy arming_policy,scoped_refptr<base::SequencedTaskRunner> runner,const char * handler_tag)114 SimpleWatcher::SimpleWatcher(const base::Location& from_here,
115                              ArmingPolicy arming_policy,
116                              scoped_refptr<base::SequencedTaskRunner> runner,
117                              const char* handler_tag)
118     : arming_policy_(arming_policy),
119       task_runner_(std::move(runner)),
120       is_default_task_runner_(base::ThreadTaskRunnerHandle::IsSet() &&
121                               task_runner_ ==
122                                   base::ThreadTaskRunnerHandle::Get()),
123       handler_tag_(handler_tag ? handler_tag : from_here.file_name()) {
124   MojoResult rv = CreateTrap(&Context::CallNotify, &trap_handle_);
125   DCHECK_EQ(MOJO_RESULT_OK, rv);
126   DCHECK(task_runner_->RunsTasksInCurrentSequence());
127 }
128 
~SimpleWatcher()129 SimpleWatcher::~SimpleWatcher() {
130   if (IsWatching())
131     Cancel();
132 }
133 
IsWatching() const134 bool SimpleWatcher::IsWatching() const {
135   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
136   return context_ != nullptr;
137 }
138 
Watch(Handle handle,MojoHandleSignals signals,MojoTriggerCondition condition,ReadyCallbackWithState callback)139 MojoResult SimpleWatcher::Watch(Handle handle,
140                                 MojoHandleSignals signals,
141                                 MojoTriggerCondition condition,
142                                 ReadyCallbackWithState callback) {
143   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
144   DCHECK(!IsWatching());
145   DCHECK(!callback.is_null());
146 
147   callback_ = std::move(callback);
148   handle_ = handle;
149   watch_id_ += 1;
150 
151   MojoResult result = MOJO_RESULT_UNKNOWN;
152   context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_,
153                              trap_handle_.get(), handle_, signals, condition,
154                              watch_id_, &result, handler_tag_);
155   if (!context_) {
156     handle_.set_value(kInvalidHandleValue);
157     callback_.Reset();
158     DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, result);
159     return result;
160   }
161 
162   if (arming_policy_ == ArmingPolicy::AUTOMATIC)
163     ArmOrNotify();
164 
165   return MOJO_RESULT_OK;
166 }
167 
Cancel()168 void SimpleWatcher::Cancel() {
169   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
170 
171   // The watcher may have already been cancelled if the handle was closed.
172   if (!context_)
173     return;
174 
175   handle_.set_value(kInvalidHandleValue);
176   callback_.Reset();
177 
178   // Ensure |context_| is unset by the time we call MojoRemoveTrigger, as it may
179   // re-enter the notification callback and we want to ensure |context_| is
180   // unset by then.
181   scoped_refptr<Context> context;
182   std::swap(context, context_);
183   MojoResult rv =
184       MojoRemoveTrigger(trap_handle_.get().value(), context->value(), nullptr);
185 
186   // It's possible this cancellation could race with a handle closure
187   // notification, in which case the watch may have already been implicitly
188   // cancelled.
189   DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
190 
191   weak_factory_.InvalidateWeakPtrs();
192 }
193 
Arm(MojoResult * ready_result,HandleSignalsState * ready_state)194 MojoResult SimpleWatcher::Arm(MojoResult* ready_result,
195                               HandleSignalsState* ready_state) {
196   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
197   uint32_t num_blocking_events = 1;
198   MojoTrapEvent blocking_event = {sizeof(blocking_event)};
199   MojoResult rv = MojoArmTrap(trap_handle_.get().value(), nullptr,
200                               &num_blocking_events, &blocking_event);
201   if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
202     DCHECK(context_);
203     DCHECK_EQ(1u, num_blocking_events);
204     DCHECK_EQ(context_->value(), blocking_event.trigger_context);
205     if (ready_result)
206       *ready_result = blocking_event.result;
207     if (ready_state) {
208       *ready_state =
209           HandleSignalsState(blocking_event.signals_state.satisfied_signals,
210                              blocking_event.signals_state.satisfiable_signals);
211     }
212   }
213 
214   return rv;
215 }
216 
ArmOrNotify()217 void SimpleWatcher::ArmOrNotify() {
218   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
219 
220   // Already cancelled, nothing to do.
221   if (!IsWatching())
222     return;
223 
224   MojoResult ready_result;
225   HandleSignalsState ready_state;
226   MojoResult rv = Arm(&ready_result, &ready_state);
227 
228   // NOTE: If the watched handle has been closed, the above call will result in
229   // MOJO_RESULT_NOT_FOUND. A MOJO_RESULT_CANCELLED notification will already
230   // have been posted to this object as a result, so there's nothing else to do.
231   if (rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND)
232     return;
233 
234   DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv);
235   {
236     // Annotate the posted task with |handler_tag_| as the IPC interface.
237     base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(handler_tag_);
238     task_runner_->PostTask(FROM_HERE,
239                            base::BindOnce(&SimpleWatcher::OnHandleReady,
240                                           weak_factory_.GetWeakPtr(), watch_id_,
241                                           ready_result, ready_state));
242   }
243 }
244 
OnHandleReady(int watch_id,MojoResult result,const HandleSignalsState & state)245 void SimpleWatcher::OnHandleReady(int watch_id,
246                                   MojoResult result,
247                                   const HandleSignalsState& state) {
248   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
249 
250   // This notification may be for a previously watched context, in which case
251   // we just ignore it.
252   if (watch_id != watch_id_)
253     return;
254 
255   ReadyCallbackWithState callback = callback_;
256   if (result == MOJO_RESULT_CANCELLED) {
257     // Implicit cancellation due to someone closing the watched handle. We clear
258     // the SimpleWatcher's state before dispatching this.
259     context_ = nullptr;
260     handle_.set_value(kInvalidHandleValue);
261     callback_.Reset();
262   }
263 
264   // NOTE: It's legal for |callback| to delete |this|.
265   if (!callback.is_null()) {
266     TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(handler_tag_);
267     // Lot of janks caused are grouped to OnHandleReady tasks. This trace event
268     // helps identify the cause of janks. It is ok to pass |handler_tag_|
269     // here since it is a string literal.
270     TRACE_EVENT("toplevel", "SimpleWatcher::OnHandleReady",
271                 [this](perfetto::EventContext ctx) {
272                   ctx.event()
273                       ->set_chrome_mojo_event_info()
274                       ->set_watcher_notify_interface_tag(handler_tag_);
275                 });
276 
277     base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr();
278     callback.Run(result, state);
279     if (!weak_self)
280       return;
281 
282     // Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying
283     // at most once in AUTOMATIC arming mode.
284     if (result == MOJO_RESULT_FAILED_PRECONDITION)
285       return;
286 
287     if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching())
288       ArmOrNotify();
289   }
290 }
291 }  // namespace mojo
292