Crash on exit after 'Goodbye!' (static destruction order) → long core dump makes shutdown appear to hang #275

Closed
opened 2026-10-04 00:36:15 +00:00 by sky_line · 1 comment
Owner

Symptom: after closing the viewer the window disappears, but the process lingers for a long time (tens of seconds). A relaunch can't start until it's gone.

Cause (confirmed from logs + symbolized backtrace): it's not a hang. The viewer segfaults in its exit handlers after logging Goodbye!, and the OS then writes a core dump of a ~12 GB process. The core dump is the long wait.

  • The log reaches cleanup : Goodbye! / status: stopped within ~4 s of QUITTING (e.g. 00:08:54 → 00:08:58Z).
  • The launcher log then shows Segmentation fault (core dumped) bin/do-not-directly-run-slipstream-bin / *** Bad shutdown (139). ***.
  • It happened in 8 of the last ~12 local sessions; every user will hit it on exit.

Backtrace (symbolized against the run's unstripped binary):

default_unix_signal_handler            llcommon/llapp.cpp:633
std::_Hashtable<U64, LLImageGL*>::erase   (sVKAlphaImages)
LLImageGL::~LLImageGL                  llrender/llimagegl.cpp:299  (lock sVKAlphaMutex; sVKAlphaImages.erase)
LLPointer<LLImageGL>::unref
LLGLTexture::~LLGLTexture              llrender/llgltexture.cpp:61
LLViewerMediaTexture::~LLViewerMediaTexture
std::map<LLUUID, LLPointer<LLViewerMediaTexture>>::~map   <- LLViewerMediaTexture::sMediaMap (static)
__run_exit_handlers / exit

Static destruction order: LLViewerMediaTexture::sMediaMap (newview) is destroyed after sVKAlphaMutex / sVKAlphaImages (llrender static in llimagegl.cpp), so the media textures' destructors touch an already-destroyed map and mutex.

Root regression: the GL strip of LLViewerWindow (549ab6826c "glr-tools: strip GL from LLViewerWindow and llui scissor") removed LLViewerWindow::shutdownGL()'s texture teardown, including gTextureList.shutdown(), LLWorldMapView::cleanupTextures(), LLViewerTextureManager::cleanup() (which calls LLViewerMediaTexture::cleanUpClass() → sMediaMap.clear()) and SUBSYSTEM_CLEANUP(LLImageGL). Nothing releases the static texture pointers (default/blank/white/missing images, sMediaMap, …) before static destruction any more. gTextureList.shutdown() now only runs once, after the Vulkan window/device is deleted. The comment says LLViewerMedia cleanup must precede it, and no LLViewerMedia cleanup call is visible in LLAppViewer::cleanup either; verify.

Fix plan:

  1. Restore an explicit texture teardown in LLAppViewer::cleanup() before delete gViewerWindow (while the VK device still exists), in LL's original order: media plugin/LLViewerMedia cleanup → gTextureList.shutdown() → LLWorldMapView::cleanupTextures() → LLViewerTextureManager::cleanup() → (any VK-side image teardown the renderer needs).
  2. Defence in depth: make the llimagegl.cpp VK registries (sVKAlphaMutex/sVKAlphaImages, and any similar statics touched from destructors) immune to destruction order: function-local leaky singletons, or an "alive" flag checked in ~LLImageGL.
  3. Audit other statics holding LLPointer<LLViewer*Texture> / LLImageGL across newview for the same pattern.
  4. Consider capping crash-time core dumps (or prctl(PR_SET_DUMPABLE) policy / documenting ulimit -c) so a crash at exit doesn't stall for a 12 GB dump. Keep dumps available for real crashes though.

Verify: quit 10× from busy regions with media present: no Bad shutdown (139), and the process exits within ~1-2 s of Goodbye!.

**Symptom:** after closing the viewer the window disappears, but the process lingers for a long time (tens of seconds). A relaunch can't start until it's gone. **Cause (confirmed from logs + symbolized backtrace):** it's not a hang. The viewer **segfaults in its exit handlers after logging `Goodbye!`**, and the OS then writes a core dump of a ~12 GB process. The core dump is the long wait. - The log reaches `cleanup : Goodbye!` / `status: stopped` within ~4 s of `QUITTING` (e.g. 00:08:54 → 00:08:58Z). - The launcher log then shows `Segmentation fault (core dumped) bin/do-not-directly-run-slipstream-bin` / `*** Bad shutdown (139). ***`. - It happened in 8 of the last ~12 local sessions; every user will hit it on exit. **Backtrace** (symbolized against the run's unstripped binary): ``` default_unix_signal_handler llcommon/llapp.cpp:633 std::_Hashtable<U64, LLImageGL*>::erase (sVKAlphaImages) LLImageGL::~LLImageGL llrender/llimagegl.cpp:299 (lock sVKAlphaMutex; sVKAlphaImages.erase) LLPointer<LLImageGL>::unref LLGLTexture::~LLGLTexture llrender/llgltexture.cpp:61 LLViewerMediaTexture::~LLViewerMediaTexture std::map<LLUUID, LLPointer<LLViewerMediaTexture>>::~map <- LLViewerMediaTexture::sMediaMap (static) __run_exit_handlers / exit ``` Static destruction order: `LLViewerMediaTexture::sMediaMap` (newview) is destroyed after `sVKAlphaMutex` / `sVKAlphaImages` (llrender static in llimagegl.cpp), so the media textures' destructors touch an already-destroyed map and mutex. **Root regression:** the GL strip of LLViewerWindow (`549ab6826c` "glr-tools: strip GL from LLViewerWindow and llui scissor") removed `LLViewerWindow::shutdownGL()`'s texture teardown, including `gTextureList.shutdown()`, `LLWorldMapView::cleanupTextures()`, `LLViewerTextureManager::cleanup()` (which calls `LLViewerMediaTexture::cleanUpClass()` → `sMediaMap.clear()`) and `SUBSYSTEM_CLEANUP(LLImageGL)`. Nothing releases the static texture pointers (default/blank/white/missing images, `sMediaMap`, …) before static destruction any more. `gTextureList.shutdown()` now only runs once, *after* the Vulkan window/device is deleted. The comment says LLViewerMedia cleanup must precede it, and no LLViewerMedia cleanup call is visible in `LLAppViewer::cleanup` either; verify. **Fix plan:** 1. Restore an explicit texture teardown in `LLAppViewer::cleanup()` **before** `delete gViewerWindow` (while the VK device still exists), in LL's original order: media plugin/LLViewerMedia cleanup → `gTextureList.shutdown()` → `LLWorldMapView::cleanupTextures()` → `LLViewerTextureManager::cleanup()` → (any VK-side image teardown the renderer needs). 2. Defence in depth: make the llimagegl.cpp VK registries (`sVKAlphaMutex`/`sVKAlphaImages`, and any similar statics touched from destructors) immune to destruction order: function-local leaky singletons, or an "alive" flag checked in `~LLImageGL`. 3. Audit other statics holding `LLPointer<LLViewer*Texture>` / `LLImageGL` across newview for the same pattern. 4. Consider capping crash-time core dumps (or `prctl(PR_SET_DUMPABLE)` policy / documenting `ulimit -c`) so a crash at exit doesn't stall for a 12 GB dump. Keep dumps available for real crashes though. **Verify:** quit 10× from busy regions with media present: no `Bad shutdown (139)`, and the process exits within ~1-2 s of `Goodbye!`.
Author
Owner

Verified done: textures are released before the window goes and the exit statics are order-proof; a live run exits cleanly after 'Goodbye!' with no segfault or core dump. Commits: 4932f2a8c9. Shipped in v1.0.0-beta.4.

Verified done: textures are released before the window goes and the exit statics are order-proof; a live run exits cleanly after 'Goodbye!' with no segfault or core dump. Commits: 4932f2a8c9. Shipped in v1.0.0-beta.4.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream#275
No description provided.