202 lines
7.0 KiB
C
202 lines
7.0 KiB
C
|
|
// Copyright 2022 The Chromium Authors
|
||
|
|
// 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_INTERNAL_H_
|
||
|
|
#define BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
|
||
|
|
|
||
|
|
#include <memory>
|
||
|
|
#include <type_traits>
|
||
|
|
#include <utility>
|
||
|
|
|
||
|
|
#include "base/compiler_specific.h"
|
||
|
|
#include "base/functional/bind.h"
|
||
|
|
#include "base/functional/callback.h"
|
||
|
|
#include "base/functional/callback_helpers.h"
|
||
|
|
#include "base/location.h"
|
||
|
|
#include "base/memory/aligned_memory.h"
|
||
|
|
#include "base/memory/raw_ptr.h"
|
||
|
|
#include "base/task/sequenced_task_runner.h"
|
||
|
|
|
||
|
|
namespace base::sequence_bound_internal {
|
||
|
|
|
||
|
|
struct CrossThreadTraits {
|
||
|
|
template <typename Signature>
|
||
|
|
using CrossThreadTask = OnceCallback<Signature>;
|
||
|
|
|
||
|
|
template <typename Functor, typename... Args>
|
||
|
|
static inline auto BindOnce(Functor&& functor, Args&&... args) {
|
||
|
|
return ::base::BindOnce(std::forward<Functor>(functor),
|
||
|
|
std::forward<Args>(args)...);
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename T>
|
||
|
|
static inline auto Unretained(T ptr) {
|
||
|
|
return ::base::Unretained(ptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline bool PostTask(SequencedTaskRunner& task_runner,
|
||
|
|
const Location& location,
|
||
|
|
OnceClosure&& task) {
|
||
|
|
return task_runner.PostTask(location, std::move(task));
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline bool PostTaskAndReply(SequencedTaskRunner& task_runner,
|
||
|
|
const Location& location,
|
||
|
|
OnceClosure&& task,
|
||
|
|
OnceClosure&& reply) {
|
||
|
|
return task_runner.PostTaskAndReply(location, std::move(task),
|
||
|
|
std::move(reply));
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename TaskReturnType, typename ReplyArgType>
|
||
|
|
static inline bool PostTaskAndReplyWithResult(
|
||
|
|
SequencedTaskRunner& task_runner,
|
||
|
|
const Location& location,
|
||
|
|
OnceCallback<TaskReturnType()>&& task,
|
||
|
|
OnceCallback<void(ReplyArgType)>&& reply) {
|
||
|
|
return task_runner.PostTaskAndReplyWithResult(location, std::move(task),
|
||
|
|
std::move(reply));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Accept RepeatingCallback here since it's convertible to a OnceCallback.
|
||
|
|
template <template <typename> class CallbackType>
|
||
|
|
using EnableIfIsCrossThreadTask = EnableIfIsBaseCallback<CallbackType>;
|
||
|
|
};
|
||
|
|
|
||
|
|
template <typename T, typename CrossThreadTraits>
|
||
|
|
class Storage {
|
||
|
|
public:
|
||
|
|
using Ptr = T*;
|
||
|
|
|
||
|
|
Ptr get() const { return ptr_; }
|
||
|
|
|
||
|
|
// Marked NO_SANITIZE because cfi doesn't like casting uninitialized memory to
|
||
|
|
// `T*`. However, this is safe here because:
|
||
|
|
//
|
||
|
|
// 1. The cast is well-defined (see https://eel.is/c++draft/basic.life#6) and
|
||
|
|
// 2. The resulting pointer is only ever dereferenced on `task_runner`.
|
||
|
|
// By the time SequenceBound's constructor returns, the task to construct
|
||
|
|
// `T` will already be posted; thus, subsequent dereference of `ptr_` on
|
||
|
|
// `task_runner` are safe.
|
||
|
|
template <typename... Args>
|
||
|
|
NO_SANITIZE("cfi-unrelated-cast")
|
||
|
|
void Construct(SequencedTaskRunner& task_runner, Args&&... args) {
|
||
|
|
// TODO(https://crbug.com/1382549): Use universal forwarding and assert that
|
||
|
|
// T is constructible from args for better error messages.
|
||
|
|
DCHECK(!alloc_);
|
||
|
|
DCHECK(!ptr_);
|
||
|
|
|
||
|
|
// Allocate space for but do not construct an instance of `T`.
|
||
|
|
// AlignedAlloc() requires alignment be a multiple of sizeof(void*).
|
||
|
|
alloc_ = AlignedAlloc(
|
||
|
|
sizeof(T), sizeof(void*) > alignof(T) ? sizeof(void*) : alignof(T));
|
||
|
|
ptr_ = reinterpret_cast<Ptr>(alloc_.get());
|
||
|
|
|
||
|
|
// Ensure that `ptr_` will be initialized.
|
||
|
|
CrossThreadTraits::PostTask(
|
||
|
|
task_runner, FROM_HERE,
|
||
|
|
CrossThreadTraits::BindOnce(&InternalConstruct<Args...>,
|
||
|
|
CrossThreadTraits::Unretained(ptr_),
|
||
|
|
std::forward<Args>(args)...));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Marked NO_SANITIZE since:
|
||
|
|
// 1. SequenceBound can be moved before `ptr_` is constructed on its managing
|
||
|
|
// `SequencedTaskRunner` but
|
||
|
|
// 2. Implicit conversions to non-virtual base classes are allowed before the
|
||
|
|
// lifetime of the object that `ptr_` points at has begun (see
|
||
|
|
// https://eel.is/c++draft/basic.life#6).
|
||
|
|
template <typename U>
|
||
|
|
NO_SANITIZE("cfi-unrelated-cast")
|
||
|
|
void TakeFrom(Storage<U, CrossThreadTraits>&& other) {
|
||
|
|
// Subtle: this must not use static_cast<>, since the lifetime of the
|
||
|
|
// managed `T` may not have begun yet. However, the standard explicitly
|
||
|
|
// still allows implicit conversion to a non-virtual base class.
|
||
|
|
ptr_ = std::exchange(other.ptr_, nullptr);
|
||
|
|
alloc_ = std::exchange(other.alloc_, nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
void Destruct(SequencedTaskRunner& task_runner) {
|
||
|
|
CrossThreadTraits::PostTask(
|
||
|
|
task_runner, FROM_HERE,
|
||
|
|
CrossThreadTraits::BindOnce(
|
||
|
|
&InternalDestruct, CrossThreadTraits::Unretained(ptr_),
|
||
|
|
CrossThreadTraits::Unretained(alloc_.get())));
|
||
|
|
ptr_ = nullptr;
|
||
|
|
alloc_ = nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
// Needed to allow conversions from compatible `U`s.
|
||
|
|
template <typename U, typename V>
|
||
|
|
friend class Storage;
|
||
|
|
|
||
|
|
// Helpers for constructing and destroying `T` on its managing
|
||
|
|
// `SequencedTaskRunner`.
|
||
|
|
template <typename... Args>
|
||
|
|
static void InternalConstruct(T* ptr, std::decay_t<Args>&&... args) {
|
||
|
|
new (ptr) T(std::move(args)...);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void InternalDestruct(T* ptr, void* alloc) {
|
||
|
|
ptr->~T();
|
||
|
|
AlignedFree(alloc);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pointer to the managed `T`.
|
||
|
|
Ptr ptr_ = nullptr;
|
||
|
|
|
||
|
|
// Storage originally allocated by `AlignedAlloc()`. Maintained separately
|
||
|
|
// from `ptr_` since the original, unadjusted pointer needs to be passed to
|
||
|
|
// `AlignedFree()`.
|
||
|
|
raw_ptr<void, DanglingUntriaged> alloc_ = nullptr;
|
||
|
|
};
|
||
|
|
|
||
|
|
template <typename T, typename CrossThreadTraits>
|
||
|
|
struct Storage<std::unique_ptr<T>, CrossThreadTraits> {
|
||
|
|
public:
|
||
|
|
using Ptr = T*;
|
||
|
|
|
||
|
|
Ptr get() const { return ptr_; }
|
||
|
|
|
||
|
|
template <typename U>
|
||
|
|
void Construct(SequencedTaskRunner& task_runner, std::unique_ptr<U> arg) {
|
||
|
|
// TODO(https://crbug.com/1382549): Use universal forwarding and assert that
|
||
|
|
// there is one arg that is a unique_ptr for better error messages.
|
||
|
|
DCHECK(!ptr_);
|
||
|
|
|
||
|
|
ptr_ = arg.release();
|
||
|
|
// No additional storage needs to be allocated since `T` is already
|
||
|
|
// constructed and lives on the heap.
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename U>
|
||
|
|
void TakeFrom(Storage<std::unique_ptr<U>, CrossThreadTraits>&& other) {
|
||
|
|
ptr_ = std::exchange(other.ptr_, nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
void Destruct(SequencedTaskRunner& task_runner) {
|
||
|
|
CrossThreadTraits::PostTask(
|
||
|
|
task_runner, FROM_HERE,
|
||
|
|
CrossThreadTraits::BindOnce(&InternalDestruct,
|
||
|
|
CrossThreadTraits::Unretained(ptr_)));
|
||
|
|
|
||
|
|
ptr_ = nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
// Needed to allow conversions from compatible `U`s.
|
||
|
|
template <typename U, typename V>
|
||
|
|
friend class Storage;
|
||
|
|
|
||
|
|
static void InternalDestruct(T* ptr) { delete ptr; }
|
||
|
|
|
||
|
|
// Pointer to the heap-allocated `T`.
|
||
|
|
Ptr ptr_ = nullptr;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace base::sequence_bound_internal
|
||
|
|
|
||
|
|
#endif // BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
|