Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/Utils/Threads.h
T
Ryan Houdek c44d8eed35 LinuxEmulation: Fixes remaining memory leak on pthread teardown
With the previous stack leak fix, RUINER reduced its memory leaking down
to around 50MB/s. The remaining memory leaks come from the pthread stack
that we are required to allocate (128KB per thread) and some internal
DTV tracking structures.

The problems come in the fact that glibc/pthread only tears down its
internal state for these if the pthread function actually returns! We
can **technically** switch the initial stack over to a "user" stack but
that introduces more problems around internal dtv tracking that we
already fixed months ago, so we can't actually do that in practice.

This leaves us no choice, we effectively are mandated to return from the
pthread function in order to free the memory from glibc. The only way we
can safely do this is with a long jump and deferring some data structure
management until that case.

This is all incredibly sucky but it's necessary to work. With these
changes, RUINER is no longer leaking memory (Hovering at around 3GB used
while in-game) and even Steam is consuming less memory.

It doesn't solve the problem that thread creation and teardown could
likely be faster, but not many applications are creating 720
threads/second.
2025-04-17 18:16:32 -07:00

90 lines
2.1 KiB
C++

// SPDX-License-Identifier: MIT
#pragma once
#include <FEXCore/fextl/deque.h>
#include <FEXCore/fextl/memory.h>
#include <cstddef>
#include <mutex>
namespace FEXCore::Threads {
class Thread;
}
namespace FEX::HLE {
struct ThreadStateObject;
}
namespace FEX::LinuxEmulation::Threads {
/**
* @brief Size of the stack that this interface creates.
*/
constexpr size_t STACK_SIZE = 8 * 1024 * 1024;
// Stack pool handling
struct StackPoolItem {
void* Ptr;
size_t Size;
};
struct DeadStackPoolItem {
void* Ptr;
size_t Size;
bool ReadyToBeReaped;
};
class StackTracker final : public FEXCore::Allocator::FEXAllocOperators {
public:
void* AllocateStackObject();
bool* AddStackToDeadPool(void* Ptr);
void AddStackToLivePool(void* Ptr);
void RemoveStackFromLivePool(void* Ptr);
void DeallocateStackObjectImmediately(void* Ptr);
[[noreturn]]
void DeallocateStackObjectAndExit(void* Ptr, int Status);
void CleanupAfterFork_PThread();
void Shutdown();
private:
std::mutex DeadStackPoolMutex {};
std::mutex LiveStackPoolMutex {};
fextl::deque<DeadStackPoolItem> DeadStackPool {};
fextl::deque<StackPoolItem> LiveStackPool {};
};
/**
* @brief Allocates a stack object from the internally managed stack pool.
*/
void* AllocateStackObject();
/**
* @brief Deallocates a stack from the internally managed stack pool.
*
* Will not free the memory immediately, instead saving for reuse temporarily to solve race conditions on stack usage while stack tears down.
*
* @param Ptr The stack base from `AllocateStackObject`
* @param Status The status to pass to the exit syscall.
*/
[[noreturn]]
void DeallocateStackObjectAndExit(void* Ptr, int Status);
void* GetStackBase(FEXCore::Threads::Thread* ThreadObject);
[[noreturn]]
void LongjumpDeallocateAndExit(FEX::HLE::ThreadStateObject* ThreadObject, int Status);
/**
* @brief Registers thread creation handlers with FEXCore.
*/
fextl::unique_ptr<StackTracker> SetupThreadHandlers();
/**
* @brief Cleans up any remaining stack objects in the pools.
*/
void Shutdown(fextl::unique_ptr<StackTracker> STracker);
} // namespace FEX::LinuxEmulation::Threads