Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/Utils/Threads.h
T
Ryan Houdek 86c492da13 LinuxSyscalls: Fixes a major stack memory leak
Fixes an issue where a thread that exits with the `exit` syscall never
actually frees its pivot stack. This is common practice and it was
missed when I was fixing the previous stack pivot leak.

This was uncovered when looking at the game
[RUINER](https://store.steampowered.com/agecheck/app/464060/?curator_clanid=4777282)
for timing bugs. Turns out the Linux build of the game creates and
destroys a VLC object every tick of the engine. Creating this VLC
object creates six threads behind the scenes. At what I assume the
default tick of the engine is of 120Hz(?) this would mean it is
attempting to create and destroy 720 threads per second, quickly
leading to memory exhaustion under FEX.

While this hits the biggest memory leak we have around thread creation,
this game is still hitting thread creation so hard that I can see other
leaks that I need to track down still.
2025-04-16 19:47:56 -07:00

81 lines
1.9 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::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);
[[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);
/**
* @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