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
- 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
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
- 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)
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
- throw ResultException(failure)
- throw PortableSettingsException(error)
- Failed to start the global hooking session
- ATL exception: HRESULT 0x
- Missing path value
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)