blob: 28bba39ee94c0d49c1de2b8d00eac785c7f2e04e [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.
#include "base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.h"
#include "base/allocator/partition_allocator/src/partition_alloc/partition_page.h"
#include "base/allocator/partition_allocator/src/partition_alloc/partition_root.h"
#include "base/allocator/partition_allocator/src/partition_alloc/partition_stats.h"
namespace partition_alloc::internal {
namespace {
size_t GetObjectSize(void* object) {
const auto* entry_slot_span = SlotSpanMetadata::FromObject(object);
return entry_slot_span->GetUtilizedSlotSize();
}
} // namespace
template <typename QuarantineEntry, size_t CapacityCount>
uint32_t LightweightQuarantineList<QuarantineEntry, CapacityCount>::Quarantine(
QuarantineEntry&& entry) {
const auto entry_size = GetObjectSize(entry.GetObject());
const size_t capacity_in_bytes =
capacity_in_bytes_.load(std::memory_order_relaxed);
if (capacity_in_bytes < entry_size) {
// Even this single entry does not fit within the capacity.
root_->Free(entry.GetObject());
quarantine_miss_count_.fetch_add(1u, std::memory_order_relaxed);
return kInvalidEntryID;
}
size_t entry_id;
{
// It may be possible to narrow down the locked section, but we will not
// make any detailed adjustments for now, as we aim to create a lock-free
// implementation by having a thread-local list.
ScopedGuard guard(lock_);
// Dequarantine some entries as required.
size_t count = count_.load(std::memory_order_acquire);
size_t size_in_bytes = size_in_bytes_.load(std::memory_order_acquire);
while (kCapacityCount < count + 1 ||
capacity_in_bytes < size_in_bytes + entry_size) {
PA_DCHECK(0 < count);
// As quarantined entries are shuffled, picking last entry is equivalent
// to picking random entry.
void* to_free =
slots_[entry_ids_[count - 1] & kSlotIndexMask].GetObject();
size_t to_free_size = GetObjectSize(to_free);
PA_DCHECK(to_free);
// We don't guarantee the deferred `Free()` has the same `FreeFlags`.
root_->Free<FreeFlags::kNoHooks>(to_free);
// Increment the counter embedded in the entry id.
// This helps to identify the entry associated with this slot.
entry_ids_[count - 1] += kCapacityCount;
if (PA_UNLIKELY(entry_ids_[count - 1] == kInvalidEntryID)) {
// Increment again so that it does not collide with the invalid id.
entry_ids_[count - 1] += kCapacityCount;
}
count--;
size_in_bytes -= to_free_size;
// Contents of `slots_[...]` remains as is, to keep the free-time
// information as much as possible.
}
// Obtain an entry id.
PA_DCHECK(count < kCapacityCount);
entry_id = entry_ids_[count];
count++;
size_in_bytes += entry_size;
// Update stats (locked).
count_.store(count, std::memory_order_release);
size_in_bytes_.store(size_in_bytes, std::memory_order_release);
// Swap randomly so that the quarantine indices remain shuffled.
// This is not uniformly random, but sufficiently random.
const size_t random_index = random_.RandUint32() % count;
std::swap(entry_ids_[random_index], entry_ids_[count - 1]);
auto& slot = slots_[entry_id & kSlotIndexMask];
slot.entry_id = entry_id;
slot.entry = std::move(entry);
}
// Update stats (not locked).
cumulative_count_.fetch_add(1, std::memory_order_relaxed);
cumulative_size_in_bytes_.fetch_add(entry_size, std::memory_order_relaxed);
return entry_id;
}
template <typename QuarantineEntry, size_t CapacityCount>
void LightweightQuarantineList<QuarantineEntry, CapacityCount>::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);
}
template <typename QuarantineEntry, size_t CapacityCount>
bool LightweightQuarantineList<QuarantineEntry, CapacityCount>::
IsQuarantinedForTesting(void* object) {
ScopedGuard guard(lock_);
for (size_t i = 0; i < count_; i++) {
if (slots_[entry_ids_[i] & kSlotIndexMask].GetObject() == object) {
return true;
}
}
return false;
}
template <typename QuarantineEntry, size_t CapacityCount>
void LightweightQuarantineList<QuarantineEntry, CapacityCount>::Purge() {
ScopedGuard guard(lock_);
size_t count = count_.load(std::memory_order_acquire);
while (0 < count) {
void* to_free = slots_[entry_ids_[count - 1] & kSlotIndexMask].GetObject();
PA_DCHECK(to_free);
root_->Free<FreeFlags::kNoHooks>(to_free);
count--;
}
count_.store(0, std::memory_order_release);
size_in_bytes_.store(0, std::memory_order_release);
std::iota(entry_ids_.begin(), entry_ids_.end(), 0);
}
template class PA_EXPORT_TEMPLATE_DEFINE(PA_COMPONENT_EXPORT(PARTITION_ALLOC))
LightweightQuarantineList<LightweightQuarantineEntry, 1024>;
} // namespace partition_alloc::internal