Skip to content

Commit d5fb402

Browse files
OnlineServices: Implement graceful shutdown of online services
1 parent 0f88975 commit d5fb402

4 files changed

Lines changed: 134 additions & 7 deletions

File tree

GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <vector>
66
#include <mutex>
77
#include <thread>
8+
#include <atomic>
89
#include <winhttp.h>
910
#include "../NGMP_include.h"
1011

@@ -62,6 +63,8 @@ class HTTPManager
6263
std::string m_strProxyAddr;
6364
uint16_t m_proxyPort;
6465

66+
std::atomic<bool> m_bShuttingDown = false;
67+
6568
std::vector<HTTPRequest*> m_vecRequestsPendingStart = std::vector<HTTPRequest*>();
6669
std::vector<HTTPRequest*> m_vecRequestsInFlight = std::vector<HTTPRequest*>();
6770
};

GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enum class EScreenshotType : int
2222
};
2323

2424
#include <mutex>
25+
#include <atomic>
2526

2627
#pragma comment(lib, "libcurl/libcurl.lib")
2728
#pragma comment(lib, "sentry/sentry.lib")
@@ -218,6 +219,8 @@ class WebSocket
218219
const int64_t m_timeBetweenUserPings = 1000;
219220
const int64_t m_timeForWSTimeout = 10000;
220221

222+
std::atomic<bool> m_bShuttingDown = false;
223+
221224
std::recursive_timed_mutex m_mutex;
222225
};
223226

@@ -376,6 +379,8 @@ class NGMP_OnlineServicesManager
376379

377380
void Shutdown();
378381

382+
void WaitForScreenshotThreads();
383+
379384
void GetAndParseServiceConfig(std::function<void(void)> cbOnDone);
380385

381386
~NGMP_OnlineServicesManager()
@@ -481,6 +486,10 @@ class NGMP_OnlineServicesManager
481486
static std::mutex m_ScreenshotMutex;
482487
static std::vector<std::string> m_vecGuardedSSData;
483488

489+
// Screenshot thread management
490+
std::vector<std::thread*> m_vecScreenshotThreads;
491+
std::mutex m_mutexScreenshotThreads;
492+
484493
ServiceConfig m_ServiceConfig;
485494

486495
HTTPManager* m_pHTTPManager = nullptr;

GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,72 @@ void HTTPManager::Shutdown()
5050
{
5151
CHECK_MAIN_THREAD;
5252

53-
curl_multi_cleanup(m_pCurl);
54-
m_pCurl = nullptr;
53+
// Signal that we're shutting down
54+
m_bShuttingDown = true;
55+
56+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] Shutdown initiated, canceling pending requests...");
57+
58+
// Cancel all pending requests
59+
for (HTTPRequest* pRequest : m_vecRequestsPendingStart)
60+
{
61+
if (pRequest != nullptr)
62+
{
63+
delete pRequest;
64+
}
65+
}
66+
m_vecRequestsPendingStart.clear();
67+
68+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] Waiting for %d in-flight requests to complete...", (int)m_vecRequestsInFlight.size());
69+
70+
// Wait for all in-flight requests to complete
71+
if (m_pCurl != nullptr)
72+
{
73+
int numRunning = 0;
74+
do
75+
{
76+
// Perform any pending operations
77+
curl_multi_perform(m_pCurl, &numRunning);
78+
79+
// Check for completed requests
80+
int msgq = 0;
81+
CURLMsg* m = nullptr;
82+
while ((m = curl_multi_info_read(m_pCurl, &msgq)) != nullptr)
83+
{
84+
if (m->msg == CURLMSG_DONE)
85+
{
86+
CURL* pCurlHandle = m->easy_handle;
87+
88+
// Find and remove the associated request
89+
for (auto it = m_vecRequestsInFlight.begin(); it != m_vecRequestsInFlight.end(); ++it)
90+
{
91+
HTTPRequest* pRequest = *it;
92+
if (pRequest != nullptr && pRequest->EasyHandleMatches(pCurlHandle))
93+
{
94+
pRequest->Threaded_SetComplete(m->data.result);
95+
delete pRequest;
96+
m_vecRequestsInFlight.erase(it);
97+
break;
98+
}
99+
}
100+
}
101+
}
102+
103+
// Small sleep to avoid busy-waiting if there are still operations pending
104+
if (numRunning > 0)
105+
{
106+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
107+
}
108+
109+
} while (numRunning > 0 || !m_vecRequestsInFlight.empty());
110+
111+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] All in-flight requests completed");
112+
113+
// Now safe to cleanup
114+
curl_multi_cleanup(m_pCurl);
115+
m_pCurl = nullptr;
116+
}
117+
118+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] Shutdown complete");
55119
}
56120

57121

GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,19 +238,51 @@ void NGMP_OnlineServicesManager::CommitReplay(AsciiString absoluteReplayPath)
238238
}, nullptr, HTTP_UPLOAD_TIMEOUT);
239239
}
240240

241-
void NGMP_OnlineServicesManager::Shutdown()
241+
void NGMP_OnlineServicesManager::WaitForScreenshotThreads()
242242
{
243-
if (m_pHTTPManager != nullptr)
243+
std::scoped_lock<std::mutex> lock(m_mutexScreenshotThreads);
244+
245+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Waiting for %d screenshot threads to complete...", (int)m_vecScreenshotThreads.size());
246+
247+
for (std::thread* pThread : m_vecScreenshotThreads)
244248
{
245-
m_pHTTPManager->Shutdown();
249+
if (pThread != nullptr && pThread->joinable())
250+
{
251+
pThread->join();
252+
delete pThread;
253+
}
246254
}
255+
256+
m_vecScreenshotThreads.clear();
257+
258+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] All screenshot threads completed");
259+
}
247260

261+
void NGMP_OnlineServicesManager::Shutdown()
262+
{
263+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] OnlineServicesManager shutdown initiated");
264+
265+
// CRITICAL: Wait for all screenshot threads to complete first
266+
// This prevents threads from accessing destroyed objects
267+
WaitForScreenshotThreads();
268+
269+
// Now shutdown network components in safe order
270+
// Shutdown WebSocket first to stop incoming messages
248271
if (m_pWebSocket != nullptr)
249272
{
250273
m_pWebSocket->Shutdown();
251274
}
252275

276+
// Then shutdown HTTP manager to complete any pending requests
277+
if (m_pHTTPManager != nullptr)
278+
{
279+
m_pHTTPManager->Shutdown();
280+
}
281+
282+
// Finally shutdown Sentry
253283
ShutdownSentry();
284+
285+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] OnlineServicesManager shutdown complete");
254286
}
255287

256288
void NGMP_OnlineServicesManager::StartVersionCheck(std::function<void(bool bSuccess, bool bNeedsUpdate)> fnCallback)
@@ -460,8 +492,8 @@ void NGMP_OnlineServicesManager::CaptureScreenshot(bool bResizeForTransmit, std:
460492
// release the image surface
461493
surf->Release();
462494

463-
// process on thread
464-
new std::thread([cbOnDataAvailable, width, height, pBits, pitch, rgbData, bResizeForTransmit]()
495+
// process on thread - track the thread so we can join it during shutdown
496+
std::thread* pNewThread = new std::thread([cbOnDataAvailable, width, height, pBits, pitch, rgbData, bResizeForTransmit]()
465497
{
466498
CHECK_WORKER_THREAD;
467499

@@ -512,6 +544,13 @@ void NGMP_OnlineServicesManager::CaptureScreenshot(bool bResizeForTransmit, std:
512544
cbOnDataAvailable(vecData);
513545
}
514546
);
547+
548+
// Store the thread so we can join it during shutdown
549+
if (m_pOnlineServicesManager != nullptr)
550+
{
551+
std::scoped_lock<std::mutex> lock(m_pOnlineServicesManager->m_mutexScreenshotThreads);
552+
m_pOnlineServicesManager->m_vecScreenshotThreads.push_back(pNewThread);
553+
}
515554
}
516555

517556
void NGMP_OnlineServicesManager::CancelUpdate()
@@ -761,7 +800,19 @@ std::string NGMP_OnlineServicesManager::GetPatcherDirectoryPath()
761800

762801
void WebSocket::Shutdown()
763802
{
803+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Shutdown initiated");
804+
805+
// Signal that we're shutting down
806+
m_bShuttingDown = true;
807+
808+
// Disconnect from the websocket
764809
Disconnect();
810+
811+
// Give CURL time to process the disconnect and cease operations
812+
// This ensures any background I/O threads have completed before we return
813+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
814+
815+
NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Shutdown complete");
765816
}
766817

767818
void WebSocket::SendData_ChangeLobbyPassword(UnicodeString& strNewPassword)

0 commit comments

Comments
 (0)