glslang/shim: ghastty_glslang_finalize_process + atexit hook
Releases glslang's process-wide state at app shutdown — the per-thread TPoolAllocator pages that hit their high-water mark on the first surface's shader compiles and otherwise leak until process termination (Zig pthreads don't run C++ thread_local destructors, so glslang's TLS pool is never cleaned up incrementally). heaptrack attributed ~12 MB to this across allocation paths rooted in glslang::TPoolAllocator::allocate. Also clears the shim's SPV cache (the std::vector storage backing each cached entry) so the cleanup is symmetric. Wired via std::atexit in qt/src/main.cpp — runs AFTER Qt's teardown chain has destroyed every GhosttySurface (and joined every renderer thread), so glslang is provably quiescent and the FinalizeProcess contract holds. Cosmetic: the user's actual runtime memory doesn't change (the pool was never going to grow further during a session); this is purely about cleaner heaptrack output and not holding ~12 MB at process exit. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
7d8cdf6adb
commit
5e21396f27
|
|
@ -237,3 +237,21 @@ extern "C" void ghastty_glslang_free_spirv(uint32_t* spv) {
|
|||
extern "C" void ghastty_glslang_free_error(char* err) {
|
||||
std::free(err);
|
||||
}
|
||||
|
||||
extern "C" void ghastty_glslang_finalize_process(void) {
|
||||
// Drop the cached SPV blobs first. The map owns the std::vector
|
||||
// pages it holds; clearing returns them to the heap. Done before
|
||||
// FinalizeProcess so a malicious post-finalize compile attempt
|
||||
// (which would re-enter glslang on a dead process state) trips
|
||||
// glslang's own checks rather than handing out stale cache hits.
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(spv_cache_mutex());
|
||||
spv_cache().clear();
|
||||
}
|
||||
// Release glslang's process-wide state: the thread-local
|
||||
// TPoolAllocator pages that accumulated to their high-water mark
|
||||
// on the first surface's compiles + any per-thread bookkeeping.
|
||||
// Matches the implicit InitializeProcess on first use (or the
|
||||
// explicit C-API glslang_initialize_process in pkg/glslang/init.zig).
|
||||
glslang::FinalizeProcess();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,23 @@ int ghastty_glslang_compile_vulkan(
|
|||
void ghastty_glslang_free_spirv(uint32_t* spv);
|
||||
void ghastty_glslang_free_error(char* err);
|
||||
|
||||
// Release the process-wide glslang state: the per-thread
|
||||
// TPoolAllocator pages (the high-water-mark pool memory that
|
||||
// otherwise leaks for the process lifetime because Zig pthreads
|
||||
// don't run C++ thread_local destructors) AND the shim's
|
||||
// SPV cache.
|
||||
//
|
||||
// Idempotent. Call ONCE from the host's shutdown path AFTER all
|
||||
// renderer threads have joined — calling it while a renderer
|
||||
// thread might still touch glslang::TShader / TProgram is
|
||||
// undefined behavior per glslang's contract.
|
||||
//
|
||||
// libghostty's own renderer-thread teardown (Vulkan.threadExit)
|
||||
// is what serializes this safely: by the time the host's main()
|
||||
// returns from QApplication::exec(), every renderer thread has
|
||||
// already run threadExit and is joined.
|
||||
void ghastty_glslang_finalize_process(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
// Symbol exported by libghostty's bundled glslang shim
|
||||
// (pkg/glslang/override/ghastty_vk_shim.cpp). Declared locally
|
||||
// rather than via an include because main.cpp would otherwise
|
||||
// need to grow a glslang/SPIR-V include path it doesn't use for
|
||||
// anything else.
|
||||
extern "C" void ghastty_glslang_finalize_process(void);
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QIcon>
|
||||
|
|
@ -63,6 +70,17 @@ int main(int argc, char **argv) {
|
|||
// libghostty action handlers may also touch the renderer).
|
||||
defaultDisableMangoHud();
|
||||
|
||||
// Release glslang's process-wide state at process exit (the
|
||||
// per-thread TPoolAllocator pages that otherwise hit their
|
||||
// high-water mark from the first surface's shader compiles and
|
||||
// never get released — ~12 MB cosmetic leak per heaptrack).
|
||||
// atexit runs after main returns and after Qt's own teardown
|
||||
// chain has destroyed every GhosttySurface (and joined every
|
||||
// renderer thread), so glslang is guaranteed quiescent by then.
|
||||
// Idempotent on the libghostty side, so a double-registration
|
||||
// (or the unlikely racing return path) is harmless.
|
||||
std::atexit(ghastty_glslang_finalize_process);
|
||||
|
||||
// CLI action fast path: skip Qt entirely. ghostty_init parses argv
|
||||
// for the `+action`; ghostty_cli_try_action runs it and exits the
|
||||
// process. If something fails (unknown action, multiple actions),
|
||||
|
|
|
|||
Loading…
Reference in New Issue