ramensoftware/windhawk · error · std::runtime_error

Failed to acquire customization session semaphore

Error message

Failed to acquire customization session semaphore

What it means

CustomizationSession::Start acquires a named semaphore to serialize customization sessions. When running from APC the wait timeout is 0 (non-blocking); otherwise it waits indefinitely. If acquire fails (returns a null lock), typically because another engine already holds the semaphore or two engines load simultaneously, the start is aborted with this error.

Solutions

  1. Check for multiple windhawk engine processes and terminate duplicates
  2. Verify the previous engine instance released the semaphore (restart the host process)
  3. Avoid loading Windhawk into a second process while a session is active
  4. Reboot if a dead process leaked the named semaphore handle
  5. Investigate why injection happens from APC if the timeout-0 path keeps failing

Example fix

// before: unconditional start can throw while another engine holds the lock
CustomizationSession::GetInstance().Start();
// after
try {
    CustomizationSession::GetInstance().Start();
} catch (const std::exception& e) {
    Log(L"session start skipped: %hs", e.what());
}
Defensive patterns

Strategy: retry

Try / catch

try {
    session.Start();
} catch (const std::runtime_error& e) {
    if (std::string_view(e.what()).find("semaphore") != npos) {
        Sleep(100);
        retryStart();
    }
}

Prevention

When it happens

Trigger: Calling CustomizationSession::Start (from InjectInit) when semaphore.acquire cannot be granted — another Windhawk engine instance holds the semaphore, or the 0-timeout APC path finds it busy.

Common situations: Two Windhawk engine instances loaded at once (e.g. DLL injected into multiple processes racing to start a session); a stale process holding the semaphore after a crash; re-entrancy during injection from APC.

Related errors


AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12). Data as JSON: /api/errors/21519ddef49237cd. Report an issue: GitHub.

Appendix: source

Thrown at src/windhawk/engine/customization_session.cpp:125

// static
void CustomizationSession::Start(
    bool runningFromAPC,
    bool threadAttachExempt,
    wil::unique_process_handle sessionManagerProcess,
    wil::unique_mutex_nothrow sessionMutex) {
    std::wstring semaphoreName = L"WindhawkCustomizationSessionSemaphore-pid=" +
                                 std::to_wstring(GetCurrentProcessId());
    wil::unique_semaphore semaphore(1, 1, semaphoreName.c_str());

    // We don't want to wait in APC context infinitely, since it will prevent
    // the process from launching. If we can't acquire the semaphore while
    // running from APC, it means that two Windhawk engines are being loaded
    // simultaneously, which is generally not supported.
    DWORD timeout = runningFromAPC ? 0 : INFINITE;
    wil::semaphore_release_scope_exit semaphoreLock =
        semaphore.acquire(nullptr, timeout);
    if (!semaphoreLock) {
        throw std::runtime_error(
            "Failed to acquire customization session semaphore");
    }

    std::optional<CustomizationSession>& session = GetInstance();
    if (session) {
        throw std::logic_error(
            "Only one session is supported at any given time");
    }

    session.emplace(ConstructorSecret{}, runningFromAPC, threadAttachExempt,
                    std::move(sessionManagerProcess), std::move(sessionMutex));

    session->StartInitialized(std::move(semaphore), std::move(semaphoreLock),
                              runningFromAPC);
}

// static
DWORD CustomizationSession::GetSessionManagerProcessId() {

View on GitHub (pinned to 61d99ed8e1)