blob: 2dcf1869327080353cf2f5024fb8232369ba0f47 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Lightweight Quarantine (LQ) provides a low-cost quarantine mechanism with
// following characteristics.
//
// - Built on PartitionAlloc: only supports allocations in a known root
// - As fast as PA: LQ just defers `Free()` handling and may benefit from thread
// cache etc.
// - Thread-safe
// - No allocation time information: triggered on `Free()`
// - Don't use quarantined objects' payload - available for zapping
// - Don't allocate heap memory.
// - Flexible to support several applications
//
// `LightweightQuarantineRoot` represents one quarantine system
// (e.g. scheduler loop quarantine).
// `LightweightQuarantineBranch` provides a quarantine request interface.
// It belongs to a `LightweightQuarantineRoot` and there can be multiple
// instances (e.g. one per thread). By having one branch per thread, it requires
// no lock for faster quarantine.
// ┌────────────────────────────┐
// │PartitionRoot │
// └┬──────────────────────────┬┘
// ┌▽────────────────────────┐┌▽────────────────────┐
// │LQRoot 1 ││LQRoot 2 │
// └┬───────────┬───────────┬┘└──────────────┬──┬──┬┘
// ┌▽─────────┐┌▽─────────┐┌▽─────────┐ ▽ ▽ ▽
// │LQBranch 1││LQBranch 2││LQBranch 3│
// └──────────┘└──────────┘└──────────┘
#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
#include <stdint.h>
#include <array>
#include <atomic>
#include <limits>
#include <type_traits>
#include "partition_alloc/partition_alloc_base/export_template.h"
#include "partition_alloc/partition_alloc_base/rand_util.h"
#include "partition_alloc/partition_alloc_base/thread_annotations.h"
#include "partition_alloc/partition_lock.h"
#include "partition_alloc/partition_stats.h"
namespace partition_alloc {
struct PartitionRoot;
struct LightweightQuarantineStats;
namespace internal {
template <size_t QuarantineCapacityCount>
class LightweightQuarantineBranch;
class LightweightQuarantineRoot {
public:
explicit LightweightQuarantineRoot(PartitionRoot& allocator_root,
size_t capacity_in_bytes = 0)
: allocator_root_(allocator_root),
capacity_in_bytes_(capacity_in_bytes) {}
template <size_t QuarantineCapacityCount>
LightweightQuarantineBranch<QuarantineCapacityCount> CreateBranch(
bool lock_required = true) {
return LightweightQuarantineBranch<QuarantineCapacityCount>(*this,
lock_required);
}
void AccumulateStats(LightweightQuarantineStats& stats) const {
stats.count += count_.load(std::memory_order_relaxed);
stats.size_in_bytes += size_in_bytes_.load(std::memory_order_relaxed);
stats.cumulative_count += cumulative_count_.load(std::memory_order_relaxed);
stats.cumulative_size_in_bytes +=
cumulative_size_in_bytes_.load(std::memory_order_relaxed);
stats.quarantine_miss_count +=
quarantine_miss_count_.load(std::memory_order_relaxed);
}
size_t GetCapacityInBytes() const {
return capacity_in_bytes_.load(std::memory_order_relaxed);
}
void SetCapacityInBytesForTesting(size_t capacity) {
capacity_in_bytes_.store(capacity, std::memory_order_relaxed);
// `size_in_bytes` may exceed `capacity_in_bytes` here.
// Each branch will try to shrink their quarantine later.
}
private:
PartitionRoot& allocator_root_;
std::atomic_size_t capacity_in_bytes_;
// Number of quarantined entries.
std::atomic_size_t count_ = 0;
// Total size of quarantined entries, capped by `capacity_in_bytes`.
std::atomic_size_t size_in_bytes_ = 0;
// Stats.
std::atomic_size_t cumulative_count_ = 0;
std::atomic_size_t cumulative_size_in_bytes_ = 0;
std::atomic_size_t quarantine_miss_count_ = 0;
template <size_t>
friend class LightweightQuarantineBranch;
};
template <size_t QuarantineCapacityCount>
class LightweightQuarantineBranch {
public:
// `QuarantineCapacityCount` must be a positive number.
static constexpr uint32_t kQuarantineCapacityCount = QuarantineCapacityCount;
static_assert(0 < QuarantineCapacityCount);
using Root = LightweightQuarantineRoot;
LightweightQuarantineBranch(const LightweightQuarantineBranch&) = delete;
LightweightQuarantineBranch(LightweightQuarantineBranch&& b)
: root_(b.root_),
lock_required_(b.lock_required_),
slots_(std::move(b.slots_)),
branch_count_(b.branch_count_),
branch_size_in_bytes_(b.branch_size_in_bytes_) {}
~LightweightQuarantineBranch() { Purge(); }
// Quarantines an object. This list holds information you put into `entry`
// as much as possible. If the object is too large, this may return
// `false`, meaning that quarantine request has failed (and freed
// immediately). Otherwise, returns `true`.
bool Quarantine(void* object);
// Dequarantine all entries **held by this branch**.
// It is possible that another branch with entries and it remains untouched.
void Purge() {
ConditionalScopedGuard guard(lock_required_, lock_);
PurgeInternal(0, 0);
}
// Determines this list contains an object.
bool IsQuarantinedForTesting(void* object) {
ConditionalScopedGuard guard(lock_required_, lock_);
for (size_t i = 0; i < branch_count_; i++) {
if (slots_[i] == object) {
return true;
}
}
return false;
}
Root& GetRoot() { return root_; }
private:
explicit LightweightQuarantineBranch(Root& root, bool lock_required)
: root_(root), lock_required_(lock_required) {}
// Try to dequarantine entries to satisfy below:
// branch_count_ <= target_branch_count
// && root_.size_in_bytes_ <= target_size_in_bytes
// It is possible that this branch cannot satisfy the
// request as it has control over only what it has. If you need to ensure the
// constraint, call `Purge()` for each branch in sequence, synchronously.
void PurgeInternal(size_t target_branch_count, size_t target_size_in_bytes)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
Root& root_;
bool lock_required_;
Lock lock_;
// An utility to lock only if a condition is met.
class PA_SCOPED_LOCKABLE ConditionalScopedGuard {
public:
explicit ConditionalScopedGuard(bool condition, Lock& lock)
PA_EXCLUSIVE_LOCK_FUNCTION(lock)
: condition_(condition), lock_(lock) {
if (condition_) {
lock_.Acquire();
}
}
~ConditionalScopedGuard() PA_UNLOCK_FUNCTION() {
if (condition_) {
lock_.Release();
}
}
private:
const bool condition_;
Lock& lock_;
};
// Non-cryptographic random number generator.
// Thread-unsafe so guarded by `lock_`.
base::InsecureRandomGenerator random_ PA_GUARDED_BY(lock_);
// `slots_` hold an array of quarantined entries.
// The contents of empty slots are undefined and reads should not occur.
// First `branch_count_` slots are used and entries should be shuffled.
std::array<void*, kQuarantineCapacityCount> slots_ PA_GUARDED_BY(lock_);
// # of quarantined entries in this branch.
size_t branch_count_ PA_GUARDED_BY(lock_) = 0;
size_t branch_size_in_bytes_ PA_GUARDED_BY(lock_) = 0;
friend class LightweightQuarantineRoot;
};
#define EXPORT_TEMPLATE \
extern template class PA_EXPORT_TEMPLATE_DECLARE( \
PA_COMPONENT_EXPORT(PARTITION_ALLOC))
using LightweightQuarantineBranchForTesting = LightweightQuarantineBranch<1024>;
using SchedulerLoopQuarantineRoot = LightweightQuarantineRoot;
using SchedulerLoopQuarantineBranch = LightweightQuarantineBranch<1024>;
EXPORT_TEMPLATE LightweightQuarantineBranch<1024>;
#undef EXPORT_TEMPLATE
} // namespace internal
} // namespace partition_alloc
#endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_