{"record":{"id":"21519ddef49237cd","repo":"ramensoftware/windhawk","slug":"failed-to-acquire-customization-session-semaphore","errorCode":null,"errorMessage":"Failed to acquire customization session semaphore","messagePattern":"Failed to acquire customization session semaphore","errorType":"exception","errorClass":"std::runtime_error","httpStatus":null,"severity":"error","filePath":"src/windhawk/engine/customization_session.cpp","lineNumber":125,"sourceCode":"// static\nvoid CustomizationSession::Start(\n    bool runningFromAPC,\n    bool threadAttachExempt,\n    wil::unique_process_handle sessionManagerProcess,\n    wil::unique_mutex_nothrow sessionMutex) {\n    std::wstring semaphoreName = L\"WindhawkCustomizationSessionSemaphore-pid=\" +\n                                 std::to_wstring(GetCurrentProcessId());\n    wil::unique_semaphore semaphore(1, 1, semaphoreName.c_str());\n\n    // We don't want to wait in APC context infinitely, since it will prevent\n    // the process from launching. If we can't acquire the semaphore while\n    // running from APC, it means that two Windhawk engines are being loaded\n    // simultaneously, which is generally not supported.\n    DWORD timeout = runningFromAPC ? 0 : INFINITE;\n    wil::semaphore_release_scope_exit semaphoreLock =\n        semaphore.acquire(nullptr, timeout);\n    if (!semaphoreLock) {\n        throw std::runtime_error(\n            \"Failed to acquire customization session semaphore\");\n    }\n\n    std::optional<CustomizationSession>& session = GetInstance();\n    if (session) {\n        throw std::logic_error(\n            \"Only one session is supported at any given time\");\n    }\n\n    session.emplace(ConstructorSecret{}, runningFromAPC, threadAttachExempt,\n                    std::move(sessionManagerProcess), std::move(sessionMutex));\n\n    session->StartInitialized(std::move(semaphore), std::move(semaphoreLock),\n                              runningFromAPC);\n}\n\n// static\nDWORD CustomizationSession::GetSessionManagerProcessId() {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk/engine/customization_session.cpp#L107-L143","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check for multiple windhawk engine processes and terminate duplicates","Verify the previous engine instance released the semaphore (restart the host process)","Avoid loading Windhawk into a second process while a session is active","Reboot if a dead process leaked the named semaphore handle","Investigate why injection happens from APC if the timeout-0 path keeps failing"],"exampleFix":"// before: unconditional start can throw while another engine holds the lock\nCustomizationSession::GetInstance().Start();\n// after\ntry {\n    CustomizationSession::GetInstance().Start();\n} catch (const std::exception& e) {\n    Log(L\"session start skipped: %hs\", e.what());\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    session.Start();\n} catch (const std::runtime_error& e) {\n    if (std::string_view(e.what()).find(\"semaphore\") != npos) {\n        Sleep(100);\n        retryStart();\n    }\n}","preventionTips":["Ensure only one Windhawk engine instance loads per machine","Terminate stale engine processes after crashes","Avoid APC-based injection paths when a session is already active","Check that the previous session's End() ran (no leaked semaphore)"],"tags":["concurrency","semaphore","cpp","windows","injection"],"backgroundTag":"resource-already-in-use","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}