From baf3d0e63c0e6dd5dfaff212dd18ee52a9e1196e Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Fri, 29 May 2026 19:35:06 +0200 Subject: [PATCH] Fix race condition in Twitch event sub reconnection logic --- plugins/twitch/event-sub.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/plugins/twitch/event-sub.cpp b/plugins/twitch/event-sub.cpp index a4a37f8c..e83f9f61 100644 --- a/plugins/twitch/event-sub.cpp +++ b/plugins/twitch/event-sub.cpp @@ -262,12 +262,21 @@ std::string EventSub::AddEventSubscription(std::shared_ptr token, return ""; } + // Capture the session ID and release the lock before making the HTTP + // request. Holding the mutex during a network call would block + // SubscriptionIsActive() on the condition-check thread for the entire + // duration of the request, causing visibly slow macro evaluations. + const std::string sessionID = eventSub->_sessionID; + lock.unlock(); + OBSDataAutoRelease postData = copyData(subscription.data); - setTransportData(postData.Get(), eventSub->_sessionID); + setTransportData(postData.Get(), sessionID); auto result = SendPostRequest(*token, registerSubscriptionURL.data(), registerSubscriptionPath.data(), {}, postData.Get()); + lock.lock(); + if (result.status != 202) { const char *error = obs_data_get_string(result.data, "error"); const char *message = @@ -280,6 +289,24 @@ std::string EventSub::AddEventSubscription(std::shared_ptr token, return ""; } + // Verify the session has not been replaced while the HTTP request was + // in flight (e.g. a reconnect occurred). If it was, the subscription + // we just created belongs to a dead session and must be discarded so + // the caller will retry with the new session ID. + if (eventSub->_sessionID != sessionID) { + blog(LOG_INFO, + "discarding Twitch EventSub registration - session ID " + "changed during HTTP request"); + return ""; + } + + // Another thread may have registered the same subscription while the + // lock was released. + if (isAlreadySubscribed(eventSub->_activeSubscriptions, subscription)) { + eventSub->LogActiveSubscriptions(); + return eventSub->_activeSubscriptions.find(subscription)->id; + } + OBSDataArrayAutoRelease replyArray = obs_data_get_array(result.data, "data"); OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0); @@ -439,9 +466,10 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message) void EventSub::HandleWelcome(obs_data_t *data) { + std::lock_guard lock(_subscriptionMtx); OBSDataAutoRelease session = obs_data_get_obj(data, "session"); _sessionID = obs_data_get_string(session, "id"); - ClearActiveSubscriptions(); + _activeSubscriptions.clear(); blog(LOG_INFO, "Twitch EventSub connected"); vblog(LOG_INFO, "Twitch EventSub session id: %s", _sessionID.c_str()); }