summaryrefslogtreecommitdiff
path: root/chromium/base/threading/sequence_bound.h
blob: 3ee8ef6e4c937998f112a0cf3795946f8b2e8480 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// Copyright 2018 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.

#ifndef BASE_THREADING_SEQUENCE_BOUND_H_
#define BASE_THREADING_SEQUENCE_BOUND_H_

#include <new>
#include <type_traits>

#include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/ptr_util.h"
#include "base/sequenced_task_runner.h"

namespace base {

// SequenceBound facilitates owning objects that live on a specified sequence,
// which is potentially different than the owner's sequence.  It encapsulates
// the work of posting tasks to the specified sequence to construct T, call
// methods on T, and destroy T.
//
// It does not provide explicit access to the underlying object directly, to
// prevent accidentally using it from the wrong sequence.
//
// Like std::unique_ptr<T>, a SequenceBound<T> may be moved between owners,
// and posted across threads.  It may also be up-casted (only), to permit
// SequenceBound to be used with interfaces.
//
// Basic usage looks like this:
//
//   // Some class that lives on |main_task_runner|.
//   class MyClass {
//    public:
//     explicit MyClass(const char* widget_title) {}
//     virtual ~MyClass() { ... }
//     virtual void DoSomething(int arg) { ... }
//   };
//
//   // On any thread...
//   scoped_refptr<SequencedTaskRunner> main_task_runner = ...;
//   auto widget = SequenceBound<MyClass>(main_task_runner, "My Title");
//
//   // Execute a single method on the object, on |main_task_runner|.
//   widget.Post(FROM_HERE, &MyClass::DoSomething, 1234);
//
//   // Execute an arbitrary task on |main_task_runner| with a non-const pointer
//   // to the object.
//   widget.PostTaskWithThisObject(
//       FROM_HERE,
//       base::BindOnce([](MyClass* widget) {
//         // Unlike with Post, we can issue multiple calls on |widget| within
//         // the same stack frame.
//         widget->DoSomething(42);
//         widget->DoSomething(13);
//       }));
//
//   // Execute an arbitrary task on |main_task_runner| with a const reference
//   // to the object.
//   widget.PostTaskWithThisObject(
//       FROM_HERE,
//       base::BindOnce([](const MyClass& widget) { ... }));
//
// Note that |widget| is constructed asynchronously on |main_task_runner|,
// but calling Post() immediately is safe, since the actual call is posted
// to |main_task_runner| as well.
//
// |widget| will be deleted on |main_task_runner| asynchronously when it goes
// out of scope, or when Reset() is called.
//
// Here is a more complicated example that shows injection and upcasting:
//
//   // Some unrelated class that uses a |MyClass| to do something.
//   class SomeConsumer {
//    public:
//    // Note that ownership of |widget| is given to us!
//    explicit SomeConsumer(SequenceBound<MyClass> widget)
//        : widget_(std::move(widget)) { ... }
//
//    ~SomeConsumer() {
//      // |widget_| will be destroyed on the associated task runner.
//    }
//
//     SequenceBound<MyClass> widget_;
//   };
//
//   // Implementation of MyClass.
//   class MyDerivedClass : public MyClass { ... };
//
//   auto widget =
//     SequenceBound<MyDerivedClass>(main_task_runner, ctor args);
//   auto c = new SomeConsumer(std::move(widget));  // upcasts to MyClass

template <typename T>
class SequenceBound {
 public:
  // Allow explicit null.
  SequenceBound() = default;

  // Construct a new instance of |T| that will be accessed only on
  // |task_runner|.  One may post calls to it immediately upon return.
  // This is marked as NO_SANITIZE because cfi doesn't like that we're casting
  // uninitialized memory to a |T*|.  However, it's safe since (a) the cast is
  // defined (see http://eel.is/c++draft/basic.life#6 for details), and (b) we
  // don't use the resulting pointer in any way that requries it to be
  // constructed, except by posting such a access to |impl_task_runner_| after
  // posting construction there as well.
  template <typename... Args>
  NO_SANITIZE("cfi-unrelated-cast")
  SequenceBound(scoped_refptr<base::SequencedTaskRunner> task_runner,
                Args&&... args)
      : impl_task_runner_(std::move(task_runner)) {
    // Allocate space for but do not construct an instance of |T|.
    // AlignedAlloc() requires alignment be a multiple of sizeof(void*).
    storage_ = AlignedAlloc(
        sizeof(T), sizeof(void*) > alignof(T) ? sizeof(void*) : alignof(T));
    t_ = reinterpret_cast<T*>(storage_);

    // Post construction to the impl thread.
    impl_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&ConstructOwnerRecord<Args...>, base::Unretained(t_),
                       std::forward<Args>(args)...));
  }

  ~SequenceBound() { Reset(); }

  // Move construction from the same type can just take the pointer without
  // adjusting anything.  This is required in addition to the move conversion
  // constructor below.
  SequenceBound(SequenceBound&& other) { MoveRecordFrom(other); }

  // Move construction is supported from any type that's compatible with |T|.
  // This case handles |From| != |T|, so we must adjust the pointer offset.
  template <typename From>
  SequenceBound(SequenceBound<From>&& other) {
    MoveRecordFrom(other);
  }

  SequenceBound& operator=(SequenceBound&& other) {
    // Clean up any object we currently own.
    Reset();
    MoveRecordFrom(other);
    return *this;
  }

  template <typename From>
  SequenceBound<T>& operator=(SequenceBound<From>&& other) {
    // Clean up any object that we currently own.
    Reset();
    MoveRecordFrom(other);
    return *this;
  }

  // Post a call to |method| to |impl_task_runner_|.
  template <typename... MethodArgs, typename... Args>
  void Post(const base::Location& from_here,
            void (T::*method)(MethodArgs...),
            Args&&... args) const {
    DCHECK(t_);
    impl_task_runner_->PostTask(from_here,
                                base::BindOnce(method, base::Unretained(t_),
                                               std::forward<Args>(args)...));
  }

  // Posts |task| to |impl_task_runner_|, passing it a reference to the wrapped
  // object. This allows arbitrary logic to be safely executed on the object's
  // task runner. The object is guaranteed to remain alive for the duration of
  // the task.
  using ConstPostTaskCallback = base::OnceCallback<void(const T&)>;
  void PostTaskWithThisObject(const base::Location& from_here,
                              ConstPostTaskCallback callback) const {
    DCHECK(t_);
    impl_task_runner_->PostTask(
        from_here,
        base::BindOnce([](ConstPostTaskCallback callback,
                          const T* t) { std::move(callback).Run(*t); },
                       std::move(callback), t_));
  }

  // Same as above, but for non-const operations. The callback takes a pointer
  // to the wrapped object rather than a const ref.
  using PostTaskCallback = base::OnceCallback<void(T*)>;
  void PostTaskWithThisObject(const base::Location& from_here,
                              PostTaskCallback callback) const {
    DCHECK(t_);
    impl_task_runner_->PostTask(from_here,
                                base::BindOnce(std::move(callback), t_));
  }

  // TODO(liberato): Add PostOrCall(), to support cases where synchronous calls
  // are okay if it's the same task runner.

  // TODO(liberato): Add PostAndReply()

  // TODO(liberato): Allow creation of callbacks that bind to a weak pointer,
  // and thread-hop to |impl_task_runner_| if needed.

  // Post destruction of any object we own, and return to the null state.
  void Reset() {
    if (is_null())
      return;

    // Destruct the object on the impl thread.
    impl_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&DeleteOwnerRecord, base::Unretained(t_),
                                  base::Unretained(storage_)));

    impl_task_runner_ = nullptr;
    t_ = nullptr;
    storage_ = nullptr;
  }

  // Same as above, but allows the caller to provide a closure to be invoked
  // immediately after destruction. The Closure is invoked on
  // |impl_task_runner_|, iff the owned object was non-null.
  void ResetWithCallbackAfterDestruction(base::OnceClosure callback) {
    if (is_null())
      return;

    impl_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(
                       [](base::OnceClosure callback, T* t, void* storage) {
                         DeleteOwnerRecord(t, storage);
                         std::move(callback).Run();
                       },
                       std::move(callback), t_, storage_));

    impl_task_runner_ = nullptr;
    t_ = nullptr;
    storage_ = nullptr;
  }

  // Return whether we own anything.  Note that this does not guarantee that any
  // previously owned object has been destroyed.  In particular, it will return
  // true immediately after a call to Reset(), though the underlying object
  // might still be pending destruction on the impl thread.
  bool is_null() const { return !t_; }

  // True if and only if we have an object, with the same caveats as is_null().
  explicit operator bool() const { return !is_null(); }

 private:
  // Move everything from |other|, doing pointer adjustment as needed.
  // This method is marked as NO_SANITIZE since (a) it might run before the
  // posted ctor runs on |impl_task_runner_|, and (b) implicit conversions to
  // non-virtual base classes are allowed before construction by the standard.
  // See http://eel.is/c++draft/basic.life#6 for more information.
  template <typename From>
  void NO_SANITIZE("cfi-unrelated-cast") MoveRecordFrom(From&& other) {
    // |other| might be is_null(), but that's okay.
    impl_task_runner_ = std::move(other.impl_task_runner_);

    // Note that static_cast<> isn't, in general, safe, since |other| might not
    // be constructed yet.  Implicit conversion is supported, as long as it
    // doesn't convert to a virtual base.  Of course, it allows only upcasts.
    t_ = other.t_;

    // The original storage is kept unmodified, so we can free it later.
    storage_ = other.storage_;

    other.storage_ = nullptr;
    other.t_ = nullptr;
  }

  // Pointer to the object,  Pointer may be modified on the owning thread.
  T* t_ = nullptr;

  // Original allocated storage for the object.
  void* storage_ = nullptr;

  // The task runner on which all access to |t_| should happen.
  scoped_refptr<base::SequencedTaskRunner> impl_task_runner_;

  // For move conversion.
  template <typename U>
  friend class SequenceBound;

  // Run on impl thread to construct |t|'s storage.
  template <typename... Args>
  static void ConstructOwnerRecord(T* t, std::decay_t<Args>&&... args) {
    new (t) T(std::move(args)...);
  }

  // Destruct the object associated with |t|, and delete |storage|.
  static void DeleteOwnerRecord(T* t, void* storage) {
    t->~T();
    AlignedFree(storage);
  }

  // To preserve ownership semantics, we disallow copy construction / copy
  // assignment.  Move construction / assignment is fine.
  DISALLOW_COPY_AND_ASSIGN(SequenceBound);
};

}  // namespace base

#endif  // BASE_THREADING_SEQUENCE_BOUND_H_