blob: 0e4b37c562318cc6899d5a3e9120c3287ba0675d [file] [log] [blame]
// 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_