Skip to content

Commit 98a2c9d

Browse files
authored
Merge pull request #266 from microsoft/maxgolov/shutdown_fixes
Collection of fixes for various shutdown scenario race conditions
2 parents 8abb53b + 0ad7924 commit 98a2c9d

25 files changed

+2217
-1795
lines changed

examples/cpp/SampleCppMini/SampleCppMini.vcxproj

Lines changed: 1582 additions & 1570 deletions
Large diffs are not rendered by default.

lib/api/LogManagerImpl.cpp

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,9 @@ namespace ARIASDK_NS_BEGIN
292292
void LogManagerImpl::FlushAndTeardown()
293293
{
294294
LOG_INFO("Shutting down...");
295-
295+
LOCKGUARD(m_lock);
296+
if (m_alive)
296297
{
297-
LOCKGUARD(m_lock);
298-
299298
LOG_INFO("Tearing down modules");
300299
TeardownModules();
301300

@@ -315,15 +314,14 @@ namespace ARIASDK_NS_BEGIN
315314
m_httpClient = nullptr;
316315
m_taskDispatcher = nullptr;
317316
m_dataViewer = nullptr;
318-
}
319317

320-
m_filters.UnregisterAllFilters();
321-
322-
auto shutTime = GetUptimeMs();
323-
PAL::shutdown();
324-
shutTime = GetUptimeMs() - shutTime;
325-
LOG_INFO("Shutdown complete in %lld ms", shutTime);
318+
m_filters.UnregisterAllFilters();
326319

320+
auto shutTime = GetUptimeMs();
321+
PAL::shutdown();
322+
shutTime = GetUptimeMs() - shutTime;
323+
LOG_INFO("Shutdown complete in %lld ms", shutTime);
324+
}
327325
m_alive = false;
328326

329327
}

lib/http/HttpClientManager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ namespace ARIASDK_NS_BEGIN {
106106
PAL::scheduleTask(&m_taskDispatcher, 0, this, &HttpClientManager::onHttpResponse, callback);
107107
}
108108

109+
/* This method may get executed synchronously on Windows from handleSendRequest in case of connection failure */
109110
void HttpClientManager::onHttpResponse(HttpCallback* callback)
110111
{
111112
EventsUploadContextPtr &ctx = callback->m_ctx;

lib/http/HttpClientManager.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class HttpClientManager
5151
ILogManager& m_logManager;
5252
IHttpClient& m_httpClient;
5353
ITaskDispatcher& m_taskDispatcher;
54-
std::mutex m_httpCallbacksMtx;
54+
std::recursive_mutex m_httpCallbacksMtx;
5555
std::list<HttpCallback*> m_httpCallbacks;
5656
};
5757

lib/http/HttpClient_WinInet.cpp

Lines changed: 135 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
// clang-format off
12
// Copyright (c) Microsoft. All rights reserved.
23
#include "mat/config.h"
34

45
#ifdef HAVE_MAT_DEFAULT_HTTP_CLIENT
5-
6+
#pragma warning(push)
7+
#pragma warning(disable:4189) /* Turn off Level 4: local variable is initialized but not referenced. dwError unused in Release without printing it. */
68
#include "HttpClient_WinInet.hpp"
79
#include "utils/Utils.hpp"
810

@@ -22,12 +24,13 @@ class WinInetRequestWrapper
2224
protected:
2325
HttpClient_WinInet& m_parent;
2426
std::string m_id;
25-
IHttpResponseCallback* m_appCallback {};
26-
HINTERNET m_hWinInetSession {};
27-
HINTERNET m_hWinInetRequest {};
27+
IHttpResponseCallback* m_appCallback {nullptr};
28+
HINTERNET m_hWinInetSession {nullptr};
29+
HINTERNET m_hWinInetRequest {nullptr};
2830
SimpleHttpRequest* m_request;
29-
BYTE m_buffer[1024] {};
30-
31+
BYTE m_buffer[1024] {0};
32+
bool isCallbackCalled {false};
33+
bool isAborted {false};
3134
public:
3235
WinInetRequestWrapper(HttpClient_WinInet& parent, SimpleHttpRequest* request)
3336
: m_parent(parent),
@@ -50,15 +53,32 @@ class WinInetRequestWrapper
5053
}
5154
}
5255

53-
/**
54-
* Async cancel pending request
55-
*/
56+
/// <summary>
57+
/// Asynchronously cancel pending request. This method is not directly calling
58+
/// the object destructor, but rather hints the implementation to speed-up the
59+
/// destruction.
60+
///
61+
/// Two possible outcomes:.
62+
////
63+
/// - set isAborted to true: cancel request without sending to WinInet stack,
64+
/// in case if request has not been sent to WinInet stack yet.
65+
////
66+
/// - close m_hWinInetRequest handle: WinInet fails all subsequent attempts to
67+
/// use invalidated handle and aborts all pending WinInet worker threads on it.
68+
/// In that case we complete with ERROR_INTERNET_OPERATION_CANCELLED.
69+
///
70+
/// It may happen that we get some feedback from WinInet, i.e. we are canceling
71+
/// at that same moment when the request is complete. In that case we process
72+
/// completion in the callback (INTERNET_STATUS_REQUEST_COMPLETE).
73+
/// </summary>
5674
void cancel()
5775
{
76+
LOCKGUARD(m_parent.m_requestsMutex);
77+
isAborted = true;
5878
if (m_hWinInetRequest != nullptr)
5979
{
6080
::InternetCloseHandle(m_hWinInetRequest);
61-
// don't wait for request callback
81+
// async request callback destroys the object
6282
}
6383
}
6484

@@ -110,28 +130,61 @@ class WinInetRequestWrapper
110130
return true;
111131
}
112132

133+
// Asynchronously send HTTP request and invoke response callback.
134+
// Ownership semantics: send(...) method self-destroys *this* upon
135+
// receiving WinInet callback. There must be absolutely no methods
136+
// that attempt to use the object after triggering send on it.
137+
// Send operation on request may be issued no more than once.
138+
//
139+
// Implementation details:
140+
//
141+
// lockguard around m_requestsMutex covers the following stages:
142+
// - request added to map
143+
// - URL parsed
144+
// - DNS lookup performed, socket opened, SSL handshake
145+
// - MS-Root SSL cert validation (if requested)
146+
// - populating HTTP request headers
147+
// - scheduling async(!) upload of HTTP post body
148+
//
149+
// Note that if any of the stages above fails, we invoke onRequestComplete(...).
150+
// That method destroys "this" request object and in order to avoid
151+
// any corruption we immediately return after invoking onRequestComplete(...).
152+
//
113153
void send(IHttpResponseCallback* callback)
114154
{
155+
LOCKGUARD(m_parent.m_requestsMutex);
156+
// Register app callback and request in HttpClient map
115157
m_appCallback = callback;
158+
m_parent.m_requests[m_id] = this;
116159

160+
// If outside code asked us to abort that request before we could proceed with
161+
// creating a WinInet handle, then clean it right away before proceeding with
162+
// any async WinInet API calls.
163+
if (isAborted)
117164
{
118-
std::lock_guard<std::mutex> lock(m_parent.m_requestsMutex);
119-
m_parent.m_requests[m_id] = this;
165+
// Request force-aborted before creating a WinInet handle.
166+
DispatchEvent(OnConnectFailed);
167+
onRequestComplete(ERROR_INTERNET_OPERATION_CANCELLED);
168+
return;
120169
}
121170

171+
DispatchEvent(OnConnecting);
122172
URL_COMPONENTSA urlc;
123173
memset(&urlc, 0, sizeof(urlc));
124174
urlc.dwStructSize = sizeof(urlc);
125-
char hostname[256];
175+
char hostname[256] = { 0 };
126176
urlc.lpszHostName = hostname;
127177
urlc.dwHostNameLength = sizeof(hostname);
128-
char path[1024];
178+
char path[1024] = { 0 };
129179
urlc.lpszUrlPath = path;
130180
urlc.dwUrlPathLength = sizeof(path);
131-
if (!::InternetCrackUrlA(m_request->m_url.data(), (DWORD)m_request->m_url.size(), 0, &urlc)) {
181+
if (!::InternetCrackUrlA(m_request->m_url.data(), (DWORD)m_request->m_url.size(), 0, &urlc))
182+
{
132183
DWORD dwError = ::GetLastError();
133184
LOG_WARN("InternetCrackUrl() failed: dwError=%d url=%s", dwError, m_request->m_url.data());
134-
onRequestComplete(dwError);
185+
// Invalid URL passed to WinInet API
186+
DispatchEvent(OnConnectFailed);
187+
onRequestComplete(ERROR_INTERNET_OPERATION_CANCELLED);
135188
return;
136189
}
137190

@@ -140,7 +193,9 @@ class WinInetRequestWrapper
140193
if (m_hWinInetSession == NULL) {
141194
DWORD dwError = ::GetLastError();
142195
LOG_WARN("InternetConnect() failed: %d", dwError);
143-
onRequestComplete(dwError);
196+
// Cannot connect to host
197+
DispatchEvent(OnConnectFailed);
198+
onRequestComplete(ERROR_INTERNET_OPERATION_CANCELLED);
144199
return;
145200
}
146201
// TODO: Session handle for the same target should be cached across requests to enable keep-alive.
@@ -155,7 +210,9 @@ class WinInetRequestWrapper
155210
if (m_hWinInetRequest == NULL) {
156211
DWORD dwError = ::GetLastError();
157212
LOG_WARN("HttpOpenRequest() failed: %d", dwError);
158-
onRequestComplete(dwError);
213+
// Request cannot be opened to given URL because of some connectivity issue
214+
DispatchEvent(OnConnectFailed);
215+
onRequestComplete(ERROR_INTERNET_OPERATION_CANCELLED);
159216
return;
160217
}
161218

@@ -164,6 +221,8 @@ class WinInetRequestWrapper
164221
{
165222
if (!isMsRootCert())
166223
{
224+
// Request cannot be completed: end-point certificate is not MS-Rooted
225+
DispatchEvent(OnConnectFailed);
167226
onRequestComplete(ERROR_INTERNET_SEC_INVALID_CERT);
168227
return;
169228
}
@@ -175,8 +234,20 @@ class WinInetRequestWrapper
175234
for (auto const& header : m_request->m_headers) {
176235
os << header.first << ": " << header.second << "\r\n";
177236
}
178-
::HttpAddRequestHeadersA(m_hWinInetRequest, os.str().data(), static_cast<DWORD>(os.tellp()), HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
179237

238+
if (!::HttpAddRequestHeadersA(m_hWinInetRequest, os.str().data(), static_cast<DWORD>(os.tellp()), HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE))
239+
{
240+
DWORD dwError = ::GetLastError();
241+
LOG_WARN("HttpAddRequestHeadersA() failed: %d", dwError);
242+
// Unable to add request headers. There's no point in proceeding with upload because
243+
// our server is expecting those custom request headers to always be there.
244+
DispatchEvent(OnConnectFailed);
245+
onRequestComplete(ERROR_INTERNET_OPERATION_CANCELLED);
246+
return;
247+
}
248+
249+
// Try to send headers and request body to server
250+
DispatchEvent(OnSending);
180251
void *data = static_cast<void *>(m_request->m_body.data());
181252
DWORD size = static_cast<DWORD>(m_request->m_body.size());
182253
BOOL bResult = ::HttpSendRequest(m_hWinInetRequest, NULL, 0, data, (DWORD)size);
@@ -185,8 +256,12 @@ class WinInetRequestWrapper
185256
if (bResult == TRUE && dwError != ERROR_IO_PENDING) {
186257
dwError = ::GetLastError();
187258
LOG_WARN("HttpSendRequest() failed: %d", dwError);
188-
onRequestComplete(dwError);
259+
// Unable to send requerst
260+
DispatchEvent(OnSendFailed);
261+
onRequestComplete(ERROR_INTERNET_OPERATION_CANCELLED);
262+
return;
189263
}
264+
// Async request has been queued in WinInet thread pool
190265
}
191266

192267
static void CALLBACK winInetCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
@@ -207,6 +282,12 @@ class WinInetRequestWrapper
207282
return;
208283
}
209284

285+
case INTERNET_STATUS_HANDLE_CLOSING:
286+
// HANDLE_CLOSING should always come after REQUEST_COMPLETE. When (and if)
287+
// it (ever) happens, WinInetRequestWrapper* self pointer may point to object
288+
// that has been already destroyed. We do not perform any actions on it.
289+
return;
290+
210291
case INTERNET_STATUS_REQUEST_COMPLETE: {
211292
assert(dwStatusInformationLength >= sizeof(INTERNET_ASYNC_RESULT));
212293
INTERNET_ASYNC_RESULT& result = *static_cast<INTERNET_ASYNC_RESULT*>(lpvStatusInformation);
@@ -222,6 +303,14 @@ class WinInetRequestWrapper
222303
}
223304
}
224305

306+
void DispatchEvent(HttpStateEvent type)
307+
{
308+
if (m_appCallback != nullptr)
309+
{
310+
m_appCallback->OnHttpStateEvent(type, static_cast<void*>(m_hWinInetRequest), 0);
311+
}
312+
}
313+
225314
void onRequestComplete(DWORD dwError)
226315
{
227316
std::unique_ptr<SimpleHttpResponse> response(new SimpleHttpResponse(m_id));
@@ -257,6 +346,7 @@ class WinInetRequestWrapper
257346
} while (m_bufferUsed == sizeof(m_buffer));
258347
}
259348

349+
// SUCCESS with no IO_PENDING means we're done with the response body: try to parse the response headers.
260350
if (dwError == ERROR_SUCCESS) {
261351
response->m_result = HttpResult_OK;
262352

@@ -308,6 +398,10 @@ class WinInetRequestWrapper
308398
response->m_headers.add(name, value1);
309399
ptr = eol + 2;
310400
}
401+
// This event handler covers the only positive case when we actually got some server response.
402+
// We may still invoke OnHttpResponse(...) below for this positive as well as other negative
403+
// cases where there was a short-read, connection failuire or timeout on reading the response.
404+
DispatchEvent(OnResponse);
311405

312406
} else {
313407
switch (dwError) {
@@ -348,9 +442,18 @@ class WinInetRequestWrapper
348442
}
349443
}
350444

351-
// 'response' gets released in EventsUploadContext.clear()
352-
m_appCallback->OnHttpResponse(response.release());
353-
m_parent.erase(m_id);
445+
assert(isCallbackCalled == false);
446+
if (!isCallbackCalled)
447+
{
448+
// Only one WinInet worker thread may invoke async callback for a given request at any given moment of time.
449+
// That ensures that isCallbackCalled does not require a lock around it. We unregister the callback here
450+
// to ensure that no more callbacks are coming for that m_hWinInetRequest.
451+
::InternetSetStatusCallback(m_hWinInetRequest, NULL);
452+
isCallbackCalled = true;
453+
m_appCallback->OnHttpResponse(response.release());
454+
// HttpClient parent is destroying this HttpRequest object by id
455+
m_parent.erase(m_id);
456+
}
354457
}
355458
};
356459

@@ -370,9 +473,13 @@ HttpClient_WinInet::~HttpClient_WinInet()
370473
::InternetCloseHandle(m_hInternet);
371474
}
372475

476+
/**
477+
* This method is called exclusively from onRequestComplete .
478+
* No other code paths that lead to request destruction.
479+
*/
373480
void HttpClient_WinInet::erase(std::string const& id)
374481
{
375-
std::lock_guard<std::mutex> lock(m_requestsMutex);
482+
LOCKGUARD(m_requestsMutex);
376483
auto it = m_requests.find(id);
377484
if (it != m_requests.end()) {
378485
auto req = it->second;
@@ -397,11 +504,10 @@ void HttpClient_WinInet::SendRequestAsync(IHttpRequest* request, IHttpResponseCa
397504

398505
void HttpClient_WinInet::CancelRequestAsync(std::string const& id)
399506
{
400-
WinInetRequestWrapper* request = nullptr;
401507
LOCKGUARD(m_requestsMutex);
402508
auto it = m_requests.find(id);
403509
if (it != m_requests.end()) {
404-
request = it->second;
510+
auto request = it->second;
405511
if (request) {
406512
request->cancel();
407513
}
@@ -414,7 +520,7 @@ void HttpClient_WinInet::CancelAllRequests()
414520
// vector of all request IDs
415521
std::vector<std::string> ids;
416522
{
417-
std::lock_guard<std::mutex> lock(m_requestsMutex);
523+
LOCKGUARD(m_requestsMutex);
418524
for (auto const& item : m_requests) {
419525
ids.push_back(item.first);
420526
}
@@ -452,5 +558,6 @@ bool HttpClient_WinInet::IsMsRootCheckRequired()
452558
}
453559

454560
} ARIASDK_NS_END
455-
561+
#pragma warning(pop)
456562
#endif // HAVE_MAT_DEFAULT_HTTP_CLIENT
563+
// clang-format on

lib/http/HttpClient_WinInet.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class HttpClient_WinInet : public IHttpClient {
3636

3737
protected:
3838
HINTERNET m_hInternet;
39-
std::mutex m_requestsMutex;
39+
std::recursive_mutex m_requestsMutex;
4040
std::map<std::string, WinInetRequestWrapper*> m_requests;
4141
static unsigned s_nextRequestId;
4242
bool m_msRootCheck;

0 commit comments

Comments
 (0)