ladybird/Libraries/LibGC/CellAllocator.cpp
Andreas Kling 42da44868b LibGC: Avoid decommit worker during full sweep teardown
Heap destruction uses CollectEverything to synchronously free every live
cell. Empty blocks from that pass do not need deferred madvise work.
Registering them with the global decommit worker can start a detached
thread during process teardown. LeakSanitizer can then hang while doing
its final thread scan with log_threads enabled.

Let full sweep deallocation opt out of deferred decommit registration.
Normal incremental sweeping stays on the background worker path. The
sanitizer LibGC container and visitor tests now exit with verbose LSan
thread logging enabled.
2026-05-15 15:54:28 +02:00

71 lines
2.4 KiB
C++

/*
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Badge.h>
#include <LibGC/BlockAllocator.h>
#include <LibGC/CellAllocator.h>
#include <LibGC/Heap.h>
#include <LibGC/HeapBlock.h>
namespace GC {
CellAllocator::CellAllocator(size_t cell_size, Optional<StringView> class_name, bool overrides_must_survive_garbage_collection, bool overrides_finalize)
: m_class_name(class_name)
, m_cell_size(cell_size)
, m_overrides_must_survive_garbage_collection(overrides_must_survive_garbage_collection)
, m_overrides_finalize(overrides_finalize)
{
}
Cell* CellAllocator::allocate_cell(Heap& heap)
{
if (!m_list_node.is_in_list())
heap.register_cell_allocator({}, *this);
if (m_usable_blocks.is_empty() && heap.is_incremental_sweep_active() && !heap.is_gc_deferred()) {
// Sweep our own pending blocks first to try to find free cells
// before allocating a new block.
while (!m_usable_blocks.is_empty() || !m_blocks_pending_sweep.is_empty()) {
if (!m_usable_blocks.is_empty())
break;
heap.sweep_block(*m_blocks_pending_sweep.first());
}
}
if (m_usable_blocks.is_empty()) {
auto block = HeapBlock::create_with_cell_size(heap, *this, m_cell_size, m_overrides_must_survive_garbage_collection, m_overrides_finalize);
auto block_ptr = reinterpret_cast<FlatPtr>(block.ptr());
if (m_min_block_address > block_ptr)
m_min_block_address = block_ptr;
if (m_max_block_address < block_ptr)
m_max_block_address = block_ptr;
m_usable_blocks.append(*block.leak_ptr());
}
auto& block = *m_usable_blocks.last();
auto* cell = block.allocate();
VERIFY(cell);
if (block.is_full())
m_full_blocks.append(*m_usable_blocks.last());
return cell;
}
void CellAllocator::block_did_become_empty(Badge<Heap>, HeapBlock& block, DeferDecommit defer_decommit)
{
block.m_list_node.remove();
block.heap().m_live_heap_blocks.remove(&block);
// NOTE: HeapBlocks are managed by the BlockAllocator, so we don't want to `delete` the block here.
block.~HeapBlock();
m_block_allocator.deallocate_block(&block, defer_decommit);
}
void CellAllocator::block_did_become_usable(Badge<Heap>, HeapBlock& block)
{
VERIFY(!block.is_full());
m_usable_blocks.append(block);
}
}