1 // Copyright 2022 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_ 6 #define BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_ 7 8 #include <memory> 9 #include <type_traits> 10 #include <utility> 11 12 #include "base/compiler_specific.h" 13 #include "base/functional/bind.h" 14 #include "base/functional/callback.h" 15 #include "base/functional/callback_helpers.h" 16 #include "base/location.h" 17 #include "base/memory/aligned_memory.h" 18 #include "base/memory/raw_ptr.h" 19 #include "base/task/sequenced_task_runner.h" 20 21 namespace base::sequence_bound_internal { 22 23 struct CrossThreadTraits { 24 template <typename Signature> 25 using CrossThreadTask = OnceCallback<Signature>; 26 27 template <typename Functor, typename... Args> BindOnceCrossThreadTraits28 static inline auto BindOnce(Functor&& functor, Args&&... args) { 29 return ::base::BindOnce(std::forward<Functor>(functor), 30 std::forward<Args>(args)...); 31 } 32 33 template <typename T> UnretainedCrossThreadTraits34 static inline auto Unretained(T ptr) { 35 return ::base::Unretained(ptr); 36 } 37 PostTaskCrossThreadTraits38 static inline bool PostTask(SequencedTaskRunner& task_runner, 39 const Location& location, 40 OnceClosure&& task) { 41 return task_runner.PostTask(location, std::move(task)); 42 } 43 PostTaskAndReplyCrossThreadTraits44 static inline bool PostTaskAndReply(SequencedTaskRunner& task_runner, 45 const Location& location, 46 OnceClosure&& task, 47 OnceClosure&& reply) { 48 return task_runner.PostTaskAndReply(location, std::move(task), 49 std::move(reply)); 50 } 51 52 template <typename TaskReturnType, typename ReplyArgType> PostTaskAndReplyWithResultCrossThreadTraits53 static inline bool PostTaskAndReplyWithResult( 54 SequencedTaskRunner& task_runner, 55 const Location& location, 56 OnceCallback<TaskReturnType()>&& task, 57 OnceCallback<void(ReplyArgType)>&& reply) { 58 return task_runner.PostTaskAndReplyWithResult(location, std::move(task), 59 std::move(reply)); 60 } 61 62 // Accept RepeatingCallback here since it's convertible to a OnceCallback. 63 template <template <typename> class CallbackType> 64 static constexpr bool IsCrossThreadTask = 65 IsBaseCallback<CallbackType<void()>>; 66 }; 67 68 template <typename T, typename CrossThreadTraits> 69 class Storage { 70 public: 71 using element_type = T; 72 is_null()73 bool is_null() const { return ptr_ == nullptr; } GetPtrForBind()74 auto GetPtrForBind() const { return CrossThreadTraits::Unretained(ptr_); } GetRefForBind()75 auto GetRefForBind() const { return std::cref(*ptr_); } 76 77 // Marked NO_SANITIZE because cfi doesn't like casting uninitialized memory to 78 // `T*`. However, this is safe here because: 79 // 80 // 1. The cast is well-defined (see https://eel.is/c++draft/basic.life#6) and 81 // 2. The resulting pointer is only ever dereferenced on `task_runner`. 82 // By the time SequenceBound's constructor returns, the task to construct 83 // `T` will already be posted; thus, subsequent dereference of `ptr_` on 84 // `task_runner` are safe. 85 template <typename... Args> 86 NO_SANITIZE("cfi-unrelated-cast") Construct(SequencedTaskRunner & task_runner,Args &&...args)87 void Construct(SequencedTaskRunner& task_runner, Args&&... args) { 88 // TODO(https://crbug.com/1382549): Use universal forwarding and assert that 89 // T is constructible from args for better error messages. 90 DCHECK(!alloc_); 91 DCHECK(!ptr_); 92 93 // Allocate space for but do not construct an instance of `T`. 94 // AlignedAlloc() requires alignment be a multiple of sizeof(void*). 95 alloc_ = AlignedAlloc( 96 sizeof(T), sizeof(void*) > alignof(T) ? sizeof(void*) : alignof(T)); 97 ptr_ = reinterpret_cast<T*>(alloc_.get()); 98 99 // Ensure that `ptr_` will be initialized. 100 CrossThreadTraits::PostTask( 101 task_runner, FROM_HERE, 102 CrossThreadTraits::BindOnce(&InternalConstruct<Args...>, 103 CrossThreadTraits::Unretained(ptr_), 104 std::forward<Args>(args)...)); 105 } 106 107 // Marked NO_SANITIZE since: 108 // 1. SequenceBound can be moved before `ptr_` is constructed on its managing 109 // `SequencedTaskRunner` but 110 // 2. Implicit conversions to non-virtual base classes are allowed before the 111 // lifetime of the object that `ptr_` points at has begun (see 112 // https://eel.is/c++draft/basic.life#6). 113 template <typename U> 114 NO_SANITIZE("cfi-unrelated-cast") TakeFrom(Storage<U,CrossThreadTraits> && other)115 void TakeFrom(Storage<U, CrossThreadTraits>&& other) { 116 // Subtle: this must not use static_cast<>, since the lifetime of the 117 // managed `T` may not have begun yet. However, the standard explicitly 118 // still allows implicit conversion to a non-virtual base class. 119 ptr_ = std::exchange(other.ptr_, nullptr); 120 alloc_ = std::exchange(other.alloc_, nullptr); 121 } 122 Destruct(SequencedTaskRunner & task_runner)123 void Destruct(SequencedTaskRunner& task_runner) { 124 CrossThreadTraits::PostTask( 125 task_runner, FROM_HERE, 126 CrossThreadTraits::BindOnce( 127 &InternalDestruct, 128 CrossThreadTraits::Unretained(std::exchange(ptr_, nullptr)), 129 CrossThreadTraits::Unretained(std::exchange(alloc_, nullptr)))); 130 } 131 132 private: 133 // Needed to allow conversions from compatible `U`s. 134 template <typename U, typename V> 135 friend class Storage; 136 137 // Helpers for constructing and destroying `T` on its managing 138 // `SequencedTaskRunner`. 139 template <typename... Args> InternalConstruct(T * ptr,std::decay_t<Args> &&...args)140 static void InternalConstruct(T* ptr, std::decay_t<Args>&&... args) { 141 new (ptr) T(std::move(args)...); 142 } 143 InternalDestruct(T * ptr,void * alloc)144 static void InternalDestruct(T* ptr, void* alloc) { 145 ptr->~T(); 146 AlignedFree(alloc); 147 } 148 149 // Pointer to the managed `T`. 150 raw_ptr<T> ptr_ = nullptr; 151 152 // Storage originally allocated by `AlignedAlloc()`. Maintained separately 153 // from `ptr_` since the original, unadjusted pointer needs to be passed to 154 // `AlignedFree()`. 155 raw_ptr<void> alloc_ = nullptr; 156 }; 157 158 template <typename T, typename CrossThreadTraits> 159 struct Storage<std::unique_ptr<T>, CrossThreadTraits> { 160 public: 161 using element_type = T; 162 163 bool is_null() const { return ptr_ == nullptr; } 164 auto GetPtrForBind() const { return CrossThreadTraits::Unretained(ptr_); } 165 auto GetRefForBind() const { return std::cref(*ptr_); } 166 167 template <typename U> 168 void Construct(SequencedTaskRunner& task_runner, std::unique_ptr<U> arg) { 169 // TODO(https://crbug.com/1382549): Use universal forwarding and assert that 170 // there is one arg that is a unique_ptr for better error messages. 171 DCHECK(!ptr_); 172 173 ptr_ = arg.release(); 174 // No additional storage needs to be allocated since `T` is already 175 // constructed and lives on the heap. 176 } 177 178 template <typename U> 179 void TakeFrom(Storage<std::unique_ptr<U>, CrossThreadTraits>&& other) { 180 ptr_ = std::exchange(other.ptr_, nullptr); 181 } 182 183 void Destruct(SequencedTaskRunner& task_runner) { 184 CrossThreadTraits::PostTask( 185 task_runner, FROM_HERE, 186 CrossThreadTraits::BindOnce( 187 &InternalDestruct, 188 CrossThreadTraits::Unretained(std::exchange(ptr_, nullptr)))); 189 } 190 191 private: 192 // Needed to allow conversions from compatible `U`s. 193 template <typename U, typename V> 194 friend class Storage; 195 196 static void InternalDestruct(T* ptr) { delete ptr; } 197 198 // Pointer to the heap-allocated `T`. 199 raw_ptr<T> ptr_ = nullptr; 200 }; 201 202 } // namespace base::sequence_bound_internal 203 204 #endif // BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_ 205