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.
66 lines
2 KiB
C++
66 lines
2 KiB
C++
/*
|
|
* Copyright (c) 2021-2023, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Atomic.h>
|
|
#include <AK/Vector.h>
|
|
#include <LibGC/Forward.h>
|
|
#include <LibSync/ConditionVariable.h>
|
|
#include <LibSync/Mutex.h>
|
|
|
|
namespace GC {
|
|
|
|
enum class DeferDecommit {
|
|
No,
|
|
Yes,
|
|
};
|
|
|
|
class DecommitWorker;
|
|
|
|
class GC_API BlockAllocator {
|
|
public:
|
|
BlockAllocator();
|
|
~BlockAllocator();
|
|
|
|
void* allocate_block(char const* name);
|
|
void deallocate_block(void*, DeferDecommit = DeferDecommit::Yes);
|
|
|
|
size_t block_count();
|
|
|
|
// Wake the global decommit worker so it processes any deferred madvise
|
|
// work that's piled up. Call this at the end of a GC sweep.
|
|
static void wake_decommit_worker_async();
|
|
|
|
private:
|
|
friend class DecommitWorker;
|
|
|
|
// Slots in "ready to reuse" state -- have been MADV_FREE_REUSABLE'd by
|
|
// the worker (Darwin) so allocate_block pairs them with MADV_FREE_REUSE.
|
|
Vector<void*> m_blocks;
|
|
|
|
// Slots freed by deallocate_block but not yet madvised. allocate_block
|
|
// pops directly from here to skip the madvise round-trip on hot recycle
|
|
// paths -- this is the main payoff of deferring decommit.
|
|
Vector<void*> m_freshly_freed;
|
|
|
|
// Protects m_blocks, m_freshly_freed, and m_in_decommit_registry. Held
|
|
// briefly on the alloc/dealloc hot path; uncontended in the common case.
|
|
Sync::Mutex m_mutex;
|
|
|
|
// Refcount the decommit worker bumps while it has a reference to this
|
|
// allocator. The destructor waits on m_worker_cv until it hits zero so
|
|
// we never let our storage go away while the worker is still running.
|
|
AK::Atomic<int> m_worker_refcount { 0 };
|
|
Sync::ConditionVariable m_worker_cv;
|
|
|
|
// True iff this allocator is currently in the worker's pending list.
|
|
// Avoids re-registering on every dealloc; cleared by the worker at the
|
|
// start of process_one, set by deallocate_block under m_mutex.
|
|
bool m_in_decommit_registry { false };
|
|
};
|
|
|
|
}
|