diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e966d8..60c0be84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). shutting down. With Wine 6.5 terminating a Wine process no longer terminates its threads, which would cause yabridge's plugin and host components to wait for each other to shut down. +- Fixed a multithreading related memory error in the VST3 audio processor socket + management. ### yabridgectl diff --git a/src/common/communication/common.h b/src/common/communication/common.h index aacb8adf..4c455e57 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -526,6 +526,12 @@ class AdHocSocketHandler { socket.shutdown( boost::asio::local::stream_protocol::socket::shutdown_both, err); socket.close(); + + while (currently_listening) { + // If another thread is currently calling `receive_multi()`, we'll + // spinlock until that function has exited. We would otherwise get a + // use-after-free when this object is destroyed from another thread. + } } protected: @@ -621,6 +627,12 @@ class AdHocSocketHandler { void receive_multi(std::optional> logger, F primary_callback, G secondary_callback) { + // We use this flag to have the `close()` function wait for the this + // function to exit, to prevent use-after-frees when destroying this + // object from another thread. + assert(!currently_listening); + currently_listening = true; + // As described above we'll handle incoming requests for `socket` on // this thread. We'll also listen for incoming connections on `endpoint` // on another thread. For any incoming connection we'll spawn a new @@ -686,6 +698,8 @@ class AdHocSocketHandler { std::lock_guard lock(active_secondary_requests_mutex); secondary_context.stop(); acceptor.reset(); + + currently_listening = false; } /** @@ -766,6 +780,16 @@ class AdHocSocketHandler { */ std::optional acceptor; + /** + * After the socket gets closed, we do some cleanup at the end of + * `receive_multi()`. To prevent use-after-frees, we should wait for this + * function to exit when `close()`-ing a socket that's currently being + * listened on. Since after closing the socket the thread should terminate + * near instantly, we'll just do a spinlock here instead of using condition + * variables. + */ + std::atomic_bool currently_listening = false; + /** * A mutex that locks the primary `socket`. If this is locked, then any new * events will be sent over a new socket instead.