Skip to content

Commit 9d1b3ec

Browse files
committed
Merge remote-tracking branch 'origin/bhamehta/winhttp-default-windows-transport' into bhamehta/winhttp-default-windows-transport
# Conflicts: # lib/http/HttpClientManager.cpp # lib/http/HttpClientManager.hpp # lib/system/TelemetrySystem.cpp
2 parents 8b38be1 + 9bd4ae2 commit 9d1b3ec

15 files changed

Lines changed: 283 additions & 50 deletions

‎build-tests.cmd‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,10 @@ set MAXCPUCOUNT=%NUMBER_OF_PROCESSORS%
5353
set SOLUTION=Solutions\MSTelemetrySDK.sln
5454

5555
msbuild %SOLUTION% /target:sqlite:Rebuild,zlib:Rebuild,Tests\gmock:Rebuild,Tests\gtest:Rebuild,Tests\UnitTests:Rebuild,Tests\FuncTests:Rebuild /p:BuildProjectReferences=true /maxcpucount:%MAXCPUCOUNT% /detailedsummary /p:Configuration=%CONFIGURATION% /p:Platform=%PLAT% %CUSTOM_PROPS%
56-
if errorLevel 1 goto end
56+
if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL%
5757
Solutions\out\%CONFIGURATION%\%PLAT%\UnitTests\UnitTests.exe
58-
if errorLevel 1 goto end
58+
if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL%
5959
Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe
60-
:end
61-
if errorLevel 1 goto end
62-
start "" Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe --gtest_filter=MultipleLogManagersTests.MultiProcessesLogManager
63-
start "" Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe --gtest_filter=MultipleLogManagersTests.MultiProcessesLogManager
64-
:end
60+
if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL%
61+
powershell -NoProfile -ExecutionPolicy Bypass -Command "$path = Join-Path (Get-Location) 'Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe'; $args = '--gtest_filter=MultipleLogManagersTests.MultiProcessesLogManager'; $p1 = Start-Process -FilePath $path -ArgumentList $args -PassThru; $p2 = Start-Process -FilePath $path -ArgumentList $args -PassThru; $p1.WaitForExit(); $p2.WaitForExit(); if ($p1.ExitCode -ne 0 -or $p2.ExitCode -ne 0) { exit 1 }"
62+
if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL%

‎lib/http/HttpClientManager.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ namespace MAT_NS_BEGIN {
139139

140140
LOG_TRACE("HTTP remove callback=%p", callback);
141141
m_httpCallbacks.remove(callback);
142+
// Wake cancelAllRequests() waiting for the list to drain.
142143
m_httpCallbacksCV.notify_all();
143144
}
144145

@@ -197,12 +198,16 @@ namespace MAT_NS_BEGIN {
197198

198199
void HttpClientManager::cancelAllRequests(bool bestEffort)
199200
{
201+
// Use the transport-specific bounded path when available; older clients
202+
// fall back to cancelling tracked requests individually.
200203
const auto cancelStart = std::chrono::steady_clock::now();
201204
cancelAllRequestsAsync(bestEffort ? m_cancelDrainTimeout : std::chrono::milliseconds::zero());
202205

206+
// Drain callbacks through the condition variable signaled by onHttpResponse.
203207
std::unique_lock<std::recursive_mutex> lock(m_httpCallbacksMtx);
204208
if (bestEffort)
205209
{
210+
// Keep pause bounded, including time spent in the transport cancel.
206211
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
207212
std::chrono::steady_clock::now() - cancelStart);
208213
const auto remaining = (elapsed < m_cancelDrainTimeout)
@@ -211,11 +216,12 @@ namespace MAT_NS_BEGIN {
211216
[this] { return m_httpCallbacks.empty(); }))
212217
{
213218
LOG_WARN("cancelAllRequests: %zu callback(s) still draining after %lld ms (best-effort)",
214-
m_httpCallbacks.size(), static_cast<long long>(m_cancelDrainTimeout.count()));
219+
m_httpCallbacks.size(), static_cast<long long>(m_cancelDrainTimeout.count()));
215220
}
216221
}
217222
else
218223
{
224+
// Shutdown/cleanup is the lifetime barrier for callback state, so drain fully.
219225
m_httpCallbacksCV.wait(lock, [this] { return m_httpCallbacks.empty(); });
220226
}
221227
}

‎lib/http/HttpClientManager.hpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class HttpClientManager
3030

3131
virtual ~HttpClientManager() noexcept;
3232

33+
// Cancel in-flight requests. Shutdown drains fully; pause uses a bounded,
34+
// best-effort drain because it may run under the LogManager lock.
3335
void cancelAllRequests(bool bestEffort = false);
3436

3537
size_t requestCount() const
@@ -65,7 +67,13 @@ class HttpClientManager
6567
ITaskDispatcher& m_taskDispatcher;
6668
mutable std::recursive_mutex m_httpCallbacksMtx;
6769
std::list<HttpCallback*> m_httpCallbacks;
70+
// Signaled from onHttpResponse when a callback is removed, so cancelAllRequests
71+
// can drain via a condition variable instead of a poll loop.
6872
std::condition_variable_any m_httpCallbacksCV;
73+
// Upper bound on how long cancelAllRequests waits for callbacks to drain. A
74+
// last-resort safety valve so a stalled dispatcher/HTTP stack can never make
75+
// the drain spin or block forever. Adjustable so tests can
76+
// exercise the timeout path without a long wait.
6977
std::chrono::milliseconds m_cancelDrainTimeout{std::chrono::seconds(30)};
7078
};
7179

‎lib/http/HttpClient_WinInet.cpp‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ void HttpClient_WinInet::erase(std::string const& id)
502502
if (it != m_requests.end()) {
503503
auto req = it->second;
504504
m_requests.erase(it);
505+
// Wake CancelAllRequests() waiting for the map to drain.
506+
m_requestsCV.notify_all();
505507
// delete WinInetRequestWrapper
506508
delete req;
507509
}
@@ -534,6 +536,11 @@ void HttpClient_WinInet::CancelRequestAsync(std::string const& id)
534536

535537

536538
void HttpClient_WinInet::CancelAllRequests()
539+
{
540+
CancelAllRequests(std::chrono::milliseconds::zero());
541+
}
542+
543+
void HttpClient_WinInet::CancelAllRequests(std::chrono::milliseconds bestEffortTimeout)
537544
{
538545
// vector of all request IDs
539546
std::vector<std::string> ids;
@@ -547,11 +554,26 @@ void HttpClient_WinInet::CancelAllRequests()
547554
for (const auto &id : ids)
548555
CancelRequestAsync(id);
549556

550-
// wait for all destructors to run
551-
while (!m_requests.empty())
557+
// Wait for all request destructors to run (erase() removes them on the WinInet
558+
// callback thread). Use a condition variable signaled from erase() rather than a
559+
// poll loop so this never spins at 100% CPU while draining. WinInet delivers the
560+
// cancellation callbacks on its own threads, so the wait completes without
561+
// depending on the SDK task dispatcher.
562+
std::unique_lock<std::recursive_mutex> lock(m_requestsMutex);
563+
if (bestEffortTimeout > std::chrono::milliseconds::zero())
564+
{
565+
// Best-effort (e.g. pause): the caller must not block indefinitely. The client
566+
// is NOT being destroyed here, so a late callback that arrives after this
567+
// returns still runs erase() on a live client -- returning early is safe.
568+
m_requestsCV.wait_for(lock, bestEffortTimeout, [this] { return m_requests.empty(); });
569+
}
570+
else
552571
{
553-
PAL::sleep(100);
554-
std::this_thread::yield();
572+
// Full drain barrier (the destructor calls this): returning early with
573+
// requests still in flight would let a late WinInet callback invoke
574+
// WinInetRequestWrapper::OnHttpResponse -> m_parent.erase() on a destroyed
575+
// client, so wait for every request to drain.
576+
m_requestsCV.wait(lock, [this] { return m_requests.empty(); });
555577
}
556578
}
557579

‎lib/http/HttpClient_WinInet.hpp‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
#ifdef HAVE_MAT_DEFAULT_HTTP_CLIENT
99

1010
#include "IHttpClient.hpp"
11+
#include "IBoundedHttpClientCancel.hpp"
1112
#include "pal/PAL.hpp"
1213

1314
#include "ILogManager.hpp"
1415

16+
#include <condition_variable>
17+
#include <mutex>
18+
1519
namespace MAT_NS_BEGIN {
1620

1721
#ifndef _WININET_
@@ -20,7 +24,7 @@ typedef void* HINTERNET;
2024

2125
class WinInetRequestWrapper;
2226

23-
class HttpClient_WinInet : public IHttpClient {
27+
class HttpClient_WinInet : public IHttpClient, public IBoundedHttpClientCancel {
2428
public:
2529
// Common IHttpClient methods
2630
HttpClient_WinInet();
@@ -29,6 +33,7 @@ class HttpClient_WinInet : public IHttpClient {
2933
virtual void SendRequestAsync(IHttpRequest* request, IHttpResponseCallback* callback) final;
3034
virtual void CancelRequestAsync(std::string const& id) final;
3135
virtual void CancelAllRequests() final;
36+
virtual void CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) final;
3237

3338
virtual void ApplySettings(ILogConfiguration& config) override;
3439

@@ -43,6 +48,9 @@ class HttpClient_WinInet : public IHttpClient {
4348
HINTERNET m_hInternet;
4449
std::recursive_mutex m_requestsMutex;
4550
std::map<std::string, WinInetRequestWrapper*> m_requests;
51+
// Signaled from erase() when a request is removed, so CancelAllRequests can drain
52+
// via a condition variable instead of a poll loop (no 100% CPU spin).
53+
std::condition_variable_any m_requestsCV;
4654
static unsigned s_nextRequestId;
4755
bool m_msRootCheck;
4856
friend class WinInetRequestWrapper;
@@ -53,4 +61,3 @@ class HttpClient_WinInet : public IHttpClient {
5361
#endif // HAVE_MAT_DEFAULT_HTTP_CLIENT
5462

5563
#endif // HTTPCLIENT_WININET_HPP
56-

‎lib/http/HttpClient_WinRt.cpp‎

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,11 @@ namespace MAT_NS_BEGIN {
396396
}
397397

398398
void HttpClient_WinRt::CancelAllRequests()
399+
{
400+
CancelAllRequests(std::chrono::milliseconds::zero());
401+
}
402+
403+
void HttpClient_WinRt::CancelAllRequests(std::chrono::milliseconds bestEffortTimeout)
399404
{
400405
// vector of all request IDs
401406
std::vector<std::string> ids;
@@ -409,11 +414,40 @@ namespace MAT_NS_BEGIN {
409414
for (const auto &id : ids)
410415
CancelRequestAsync(id);
411416

412-
// wait for all destructors to run
413-
while (!m_requests.empty())
417+
// wait for all destructors to run. Read m_requests under the lock each
418+
// iteration; erase() runs on the PPL continuation thread under the same lock.
419+
// A zero timeout drains fully (shutdown); a positive timeout is a best-effort
420+
// cap so callers such as pause do not block indefinitely.
421+
const bool bounded = bestEffortTimeout > std::chrono::milliseconds::zero();
422+
const auto deadline = std::chrono::steady_clock::now() + bestEffortTimeout;
423+
bool done;
414424
{
415-
PAL::sleep(100);
425+
std::lock_guard<std::mutex> lock(m_requestsMutex);
426+
done = m_requests.empty();
427+
}
428+
while (!done)
429+
{
430+
if (bounded)
431+
{
432+
const auto now = std::chrono::steady_clock::now();
433+
if (now >= deadline)
434+
break;
435+
// Sleep no longer than the remaining budget so the bounded wait does not
436+
// overshoot bestEffortTimeout by up to a full poll interval.
437+
long long remainingMs = std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now).count();
438+
if (remainingMs < 1) remainingMs = 1;
439+
if (remainingMs > 100) remainingMs = 100;
440+
PAL::sleep(static_cast<unsigned>(remainingMs));
441+
}
442+
else
443+
{
444+
PAL::sleep(100);
445+
}
416446
std::this_thread::yield();
447+
{
448+
std::lock_guard<std::mutex> lock(m_requestsMutex);
449+
done = m_requests.empty();
450+
}
417451
}
418452
};
419453

‎lib/http/HttpClient_WinRt.hpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <Windows.h>
1414

1515
#include "IHttpClient.hpp"
16+
#include "IBoundedHttpClientCancel.hpp"
1617
#include "pal/PAL.hpp"
1718

1819
#include <ppltasks.h>
@@ -28,14 +29,15 @@ namespace MAT_NS_BEGIN {
2829

2930
class WinRtRequestWrapper;
3031

31-
class HttpClient_WinRt : public IHttpClient {
32+
class HttpClient_WinRt : public IHttpClient, public IBoundedHttpClientCancel {
3233
public:
3334
HttpClient_WinRt();
3435
virtual ~HttpClient_WinRt();
3536
virtual IHttpRequest* CreateRequest() override;
3637
virtual void SendRequestAsync(IHttpRequest* request, IHttpResponseCallback* callback) override;
3738
virtual void CancelRequestAsync(std::string const& id) override;
3839
virtual void CancelAllRequests() override;
40+
virtual void CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) override;
3941
HttpClient^ getHttpClient() { return m_httpClient; }
4042

4143
protected:
@@ -55,4 +57,3 @@ class HttpClient_WinRt : public IHttpClient {
5557
#endif // HAVE_MAT_DEFAULT_HTTP_CLIENT
5658

5759
#endif // HTTPCLIENT_WINRT_HPP
58-

‎lib/include/public/IHttpClient.hpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ namespace MAT_NS_BEGIN
556556
/// <param name="id">A string that contains the ID of the request to cancel.</param>
557557
virtual void CancelRequestAsync(std::string const& id) = 0;
558558

559+
/// <summary>
560+
/// Cancels all pending requests, draining fully before returning when the
561+
/// implementation owns a synchronous drain.
562+
/// </summary>
559563
virtual void CancelAllRequests() {}
560564

561565
/// <summary>
@@ -572,4 +576,3 @@ namespace MAT_NS_BEGIN
572576
} MAT_NS_END
573577

574578
#endif
575-

‎lib/jni/JniConvertors.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ std::string JStringToStdString(JNIEnv* env, const jstring& jstr) {
1313

1414
size_t jstr_length = env->GetStringUTFLength(jstr);
1515
auto jstr_utf = env->GetStringUTFChars(jstr, nullptr);
16+
if (jstr_utf == nullptr) {
17+
// Preserve the pending Java exception (typically an allocation failure)
18+
// so the JNI caller observes the real failure instead of an empty value.
19+
return "";
20+
}
1621
std::string str(jstr_utf, jstr_utf + jstr_length);
1722
env->ReleaseStringUTFChars(jstr, jstr_utf);
1823
return str;
@@ -160,7 +165,7 @@ EventProperties GetEventProperties(JNIEnv* env, const jstring& jstrEventName, co
160165
EventProperties eventProperties;
161166
eventProperties.SetName(JStringToStdString(env, jstrEventName));
162167
if (jstrEventType != NULL) {
163-
// An empty type means "unset" (the native default). Before #1329 the
168+
// An empty type means "unset" (the native default). Previously the
164169
// Java getType() returned null for a default EventProperties, so this
165170
// branch was skipped. getType() now returns "" to fix a Java-side NPE;
166171
// forwarding SetType("") here would fail native event-name validation
@@ -208,4 +213,3 @@ std::vector<std::string> ConvertJObjectArrayToStdStringVector(JNIEnv* env, const
208213
}
209214

210215
} MAT_NS_END
211-

‎lib/jni/Signals_jni.cpp‎

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,22 @@ Java_com_microsoft_applications_events_Signals_sendSignal(JNIEnv *env,
2525
jlong nativeLoggerPtr,
2626
jstring signal_item_json) {
2727
jboolean isCopy = true;
28+
auto logger = reinterpret_cast<ILogger*>(nativeLoggerPtr);
29+
if (logger == nullptr) {
30+
return false;
31+
}
32+
if (signal_item_json == nullptr) {
33+
return false;
34+
}
2835
const char *signalItemJson = (env)->GetStringUTFChars(signal_item_json, &isCopy);
29-
env->ReleaseStringUTFChars(signal_item_json, signalItemJson);
36+
if (signalItemJson == nullptr) {
37+
// Preserve the pending Java exception (typically an allocation failure)
38+
// rather than masking it as a clean false result.
39+
return false;
40+
}
3041

31-
auto logger = reinterpret_cast<ILogger*>(nativeLoggerPtr);
3242
EventProperties eventProperties = Signals::CreateEventProperties(signalItemJson);
43+
env->ReleaseStringUTFChars(signal_item_json, signalItemJson);
3344
logger->LogEvent(eventProperties);
3445
return true;
3546
}
@@ -54,20 +65,34 @@ Java_com_microsoft_applications_events_Signals_nativeInitialize(JNIEnv *env, jcl
5465
SubstrateSignalsConfiguration config;
5566

5667
jboolean isCopy = true;
57-
const char *convertedValue = (env)->GetStringUTFChars(base_url, &isCopy);
58-
if (strlen(convertedValue) > 0) {
59-
config.ServiceRequestConfig.BaseUrl = convertedValue;
68+
if (base_url != nullptr) {
69+
const char *convertedValue = (env)->GetStringUTFChars(base_url, &isCopy);
70+
if (convertedValue == nullptr) {
71+
// Preserve the pending Java exception (typically an allocation failure)
72+
// rather than masking it as a clean false result.
73+
return false;
74+
}
75+
if (strlen(convertedValue) > 0) {
76+
config.ServiceRequestConfig.BaseUrl = convertedValue;
77+
}
78+
env->ReleaseStringUTFChars(base_url, convertedValue);
6079
}
61-
env->ReleaseStringUTFChars(base_url, convertedValue);
6280

6381
config.ServiceRequestConfig.TimeoutMs = reinterpret_cast<int>(timeout_ms);
6482
config.ServiceRequestConfig.RetryTimes = reinterpret_cast<int>(retry_times);
6583
config.ServiceRequestConfig.RetryTimesToWait = reinterpret_cast<int>(retry_time_to_wait);
6684

67-
jsize size = env->GetArrayLength(retry_status_codes);
68-
std::vector<int> retryStatusCodes(size);
69-
env->GetIntArrayRegion(retry_status_codes, jsize{0}, size, &retryStatusCodes[0] );
70-
config.ServiceRequestConfig.RetryStatusCodes = std::vector<int64_t>(retryStatusCodes.begin(), retryStatusCodes.end());
85+
if (retry_status_codes != nullptr)
86+
{
87+
jsize size = env->GetArrayLength(retry_status_codes);
88+
std::vector<int> retryStatusCodes(size);
89+
if (size > 0)
90+
{
91+
env->GetIntArrayRegion(retry_status_codes, jsize{0}, size, retryStatusCodes.data());
92+
}
93+
config.ServiceRequestConfig.RetryStatusCodes =
94+
std::vector<int64_t>(retryStatusCodes.begin(), retryStatusCodes.end());
95+
}
7196

7297
spDataInspector = Signals::CreateSignalsEventInspector(nullptr, config);
7398
return true;

0 commit comments

Comments
 (0)