Skip to content

Guard Controller::m_connection against concurrent access - #1827

Merged
sandboxcoder merged 3 commits into
stagingfrom
rno/fix-ipc-mutex
Nov 2, 2026
Merged

sandboxcoder merged 3 commits into
stagingfrom
rno/fix-ipc-mutex

Conversation

@sandboxcoder

@sandboxcoder sandboxcoder commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

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).

  • 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.

Motivation and Context

Want to fix Sentry minidump to improve app stability.

EXCEPTION_ACCESS_VIOLATION_READ / 0x0
Fatal Error: EXCEPTION_ACCESS_VIOLATION_READ / 0x0
mechanism
minidump
handled
false
MSVCP140
+0x0012c10
mtx_do_lock (mutex.cpp:103)

obs_studio_client.node
+0x01b382e
ipc::client_win::call
obs_studio_client.node
+0x027330f
destructor'
obs_studio_client.node
+0x0278af7
destructor'
ntdll
+0x0031cfa
RtlAllocateHeap
ucrtbase
+0x00050d8
malloc_base
<unknown>
0x00a70a7fcf60
<unknown>
obs_studio_client.node
+0x00de78a
api::SetWorkingDirectory
obs_studio_client.node
+0x0278af7
destructor'
Streamlabs OBS
+0x2d4fe28
v8::internal::`anonymous namespace'::StringWriteToFlatOneByte (external-reference.cc:1365)

Streamlabs OBS
+0x2fc8a00
Builtins_StringSlowFlatten

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

@sandboxcoder
sandboxcoder marked this pull request as ready for review November 2, 2026 16:44
@sandboxcoder
sandboxcoder merged commit 97cf0a3 into staging Nov 2, 2026
17 checks passed
@sandboxcoder
sandboxcoder deleted the rno/fix-ipc-mutex branch November 2, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants