Skip to content

Renderer init failure crashes in the FatalError path (SIGSEGV, null soundSystem) instead of reporting the error #96

Description

@rebelancap

Summary

When renderer device initialisation fails, common->FatalError is reached but the process segfaults on a null soundSystem before the error is shown. A recoverable, clearly-diagnosable configuration problem therefore presents to the user as an unexplained SIGSEGV.

Environment

  • macOS 27.0, Apple M4 Pro, Xcode 26.6
  • openQ4 227ee810 (main), release build, platform_backend=sdl3

Reproduce

Any Vulkan init failure will do. The easiest is to run .install/ without MoltenVK staged where the SDL3 backend probes for it (that packaging mismatch is #95):

cd .install && ./openQ4-client_arm64 +set r_renderApi vulkan

Observed:

openQ4: fatal signal SIGSEGV (11), exiting without unsafe engine shutdown
openQ4: last renderer startup phase: idle
openQ4: last game module phase: idle

Under lldb:

stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
frame #0: idSoundSystem::StopAllSounds(int) + 20
->  0x1002d7f7c <+20>: ldr    x8, [x0]     ; x0 == 0
    0x1002d7f80 <+24>: ldr    x1, [x8, #0x18]
    0x1002d7f88 <+32>: br     x1

That is a virtual dispatch through a null object — soundSystem is null at the point the fatal-error path calls into it. (lldb cannot unwind past frame #0 because the thunk tail-calls, so the caller frame is already gone.)

The actual, useful message is only visible with +set logFile 2, in <fs_savepath>/baseoq4/qconsole.log:

----- VK_InitRenderDevice -----
WARNING: Vulkan: window creation failed
FATAL: Vulkan renderer device initialization failed

Note on diagnosis difficulty

Without logFile 2 this is genuinely misleading: stdout is block-buffered, so several KB of output is lost on the crash and the last line to reach the terminal is from a much earlier part of startup (machine-spec detection, mid-Printf):

	Video memory: 37.44 GB on an opopenQ4: fatal signal SIGSEGV (11), ...

That points investigators at the wrong subsystem entirely. Flushing (or fflush on the fatal path) before the handler exits would help a lot on its own.

Suggested fix

Null-guard the sound teardown on the fatal-error/shutdown path, as is already done elsewhere in the codebase — e.g. Session.cpp:4594:

if ( soundSystem ) {
    soundSystem->StopAllSounds( SOUNDWORLD_GAME );
}

More generally, the fatal-error path can be entered before or during subsystem init, so it should tolerate any subsystem being absent rather than assuming a fully-initialised engine.

Why this matters

A renderer that fails to initialise is exactly the situation where the user most needs to read the error — wrong driver, missing loader, unsupported device. Crashing first turns a one-line fix into a debugging session, and it hides genuinely actionable messages like the window creation failed above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions