WebSocket shutdown can close the termination event while accepted clients still wait on it
Summary¶
Accepted WebSocket client threads wait on the global s_hTerminate handle, but
shutdown only waits for the listener thread before closing that handle. If any
accepted client thread is still alive, it can continue waiting on a handle that
has been closed by another thread.
This blocks Beta 0.7.3 because Win32 explicitly does not make cross-thread handle close while waiting safe; shutdown can become nondeterministic under active Web UI or REST clients.
Evidence¶
srchybrid/WebSocket.cpp:360includess_hTerminatein accepted-client wait handles.srchybrid/WebSocket.cpp:516creates one accepted-clientCWinThreadper accepted socket.srchybrid/WebSocket.cpp:522does not store the accepted thread for later join/drain.srchybrid/WebSocket.cpp:644closess_hTerminateafter listener shutdown, with no accepted-client join.
Execution Plan¶
- Revalidate the current thread ownership model for listener and accepted WebSocket threads.
- Introduce explicit accepted-client lifetime tracking or another narrow ownership mechanism that lets shutdown know when client threads have exited.
- Signal termination first, then wake listener and accepted clients.
- Wait for accepted-client exits before closing the shared termination event.
- Ensure failure paths for thread creation close sockets and free
SocketData. - Add shutdown coverage with at least one idle accepted connection and one request in progress.
Acceptance Criteria¶
s_hTerminateis never closed while accepted-client threads can wait on it.- WebServer shutdown completes cleanly with active HTTP and REST clients.
- No accepted-client thread leaks past WebServer shutdown.
- No behavior change to request routing or Web UI responses.
Validation¶
- 2026-05-08: Done in app commit
aa66699. python -m emule_workspace validate --workspace-root .python -m emule_workspace build app --workspace-root . --config Release --platform x64 --variant main- Code validation: accepted WebSocket client
CWinThreadobjects are tracked withm_bAutoDelete = FALSE, joined/reaped befores_hTerminatecloses, and the termination handle is kept open if any socket thread fails to exit.