Repository navigation
Guard Controller::m_connection against concurrent access - #1827
Conversation
Signal-poller threads and the Auto Optimizer copy the IPC connection through Controller::GetConnection() while the JS thread can reset it in disconnect(). Reading and writing one std::shared_ptr from two threads is a data race: a reader can obtain a pointer to an ipc::client_win that is being freed and then crash locking its mutex in ipc::client_win::call (seen in Sentry as an access violation in mtx_do_lock). - Protect every access to m_connection with a mutex. - Bump the connection epoch before dropping the connection, so finalizers that check it skip IPC for the old session. - Release the last client reference outside the lock, because ~client_win joins the IPC worker thread. - Make the System::Shutdown call on a local copy, outside the lock, and skip it if there is no connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e3af024 to
e56ae03
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The missing IPC client implementation prevents verification of destructor/worker-thread teardown and reentrancy, while the crash fix still needs Windows disconnect/reconnect stress validation with active pollers.
No open issues 🎉
What changed in this PR
This PR addresses a race between background connection readers and JS-thread disconnects by synchronizing access to Controller::m_connection.
Changes:
- Adds a mutex to protect connection reads and writes.
- Advances the connection epoch before clearing the connection and releases the client outside the lock.
- Performs server shutdown through a local connection copy, with a null check and without holding the mutex.
| File | Description |
|---|---|
obs-studio-client/source/controller.hpp |
Declares the connection mutex and includes <mutex>. |
obs-studio-client/source/controller.cpp |
Synchronizes connection access and adjusts disconnect ordering and client release. |
Review effort: Balanced
Exercise the Controller::m_connection locking: the global source callback worker and an output's WorkerSignals worker keep calling GetConnection() while IPC.disconnect() drops the connection on the JS thread. - Run 8 host/disconnect cycles with both pollers active, varying the delay so the disconnect lands at different points of the poll cycle. - Keep an output poller running across a reconnect, so it picks up the new connection, then check the new session still answers IPC calls. - Add OBSHandler.reconnect(), which hosts a new server on a fresh pipe. Pollers are stopped without IPC after a disconnect: reassigning signalHandler joins the output worker, and RemoveSourceCallback joins the global worker, which exits on its own once the connection is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The stress tests can skip poller cleanup on failure, potentially hanging the runner or leaving workers active across attempts, so teardown must unconditionally stop recording and global pollers.
1 open issue
Review effort: Balanced
If recording setup, a signal wait or the reconnect check threw, the test skipped its poller cleanup, and obs.shutdown() does not stop these pollers. An output worker could keep polling and the global callback stayed registered, which could hang the runner or leak into a retry. Track each recording as soon as it is created and the global poller as soon as it is registered, and stop all of them in afterEach. Neither stop makes an IPC call, so cleanup works connected or disconnected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Missing IPC client code and unavailable native Windows disconnect/reconnect stress testing leave safe client destruction and deadlock-free worker joins with active pollers unverified.
No open issues 🎉
1 resolved since last review
Review effort: Balanced

Description
Signal-poller threads and the Auto Optimizer copy the IPC connection through Controller::GetConnection() while the JS thread can reset it in disconnect(). Reading and writing one std::shared_ptr from two threads is a data race: a reader can obtain a pointer to an ipc::client_win that is being freed and then crash locking its mutex in ipc::client_win::call (seen in Sentry as an access violation in mtx_do_lock).
Motivation and Context
Want to fix Sentry minidump to improve app stability.
How Has This Been Tested?
Types of changes
Checklist: