Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a783517
Fix HTTP client torn reads and response memory leak
bmehta001 Apr 28, 2026
28cf17d
Fix WorkerThread shutdown: safe cleanup and diagnostics
bmehta001 Apr 28, 2026
a355ec5
Make m_runningLatency and m_scheduledUploadTime atomic
bmehta001 Apr 28, 2026
de46cb2
Fix static-destruction-order crash in Logger destructor
bmehta001 Apr 28, 2026
706a01f
Use cleaner shutdown and scheduler synchronization fixes
bmehta001 Apr 30, 2026
0b27717
Avoid holding TPM scheduler mutex during cancel
bmehta001 Apr 30, 2026
2cdf817
Address runtime review comments
bmehta001 May 4, 2026
95519ef
Apply force-scheduled latency when running cancel fails
bmehta001 May 4, 2026
11820ae
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 May 6, 2026
68f4dd0
Simplify TPM cancellation cleanup
bmehta001 May 11, 2026
4a8cc9d
Simplify TPM force scheduling test
bmehta001 May 11, 2026
5638972
Keep TPM cancellation comment wording
bmehta001 May 11, 2026
05bd377
Address runtime review comments
bmehta001 May 11, 2026
2c559d0
Clean up runtime logging follow-ups
bmehta001 May 12, 2026
b0ad7d8
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 May 20, 2026
2241c38
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 May 20, 2026
eb3bfff
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 Jun 1, 2026
a111e11
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 Jun 1, 2026
42cfa76
Merge remote-tracking branch 'msft/main' into bhamehta/runtime-fixes
bmehta001 Jun 3, 2026
042f077
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 Jun 9, 2026
9ae10ec
pal: return a no-op handle when a scheduled task is dropped
bmehta001 Jun 9, 2026
e9b1957
tpm/tests: address Copilot round feedback (printf cast + test suite n…
bmehta001 Jun 10, 2026
6429fef
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 Jun 10, 2026
797ede0
Declare ITaskDispatcher::QueueWithResult after Cancel (preserve vtabl…
bmehta001 Jun 13, 2026
9762f94
Address Copilot on #1429: don't claim binary/ABI compatibility in vta…
bmehta001 Jun 13, 2026
b5ba867
HttpClient_WinInet: close session handle even when request handle is …
bmehta001 Jun 22, 2026
8935ef2
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 Jun 30, 2026
dd9e023
Add teardown-during-in-flight-upload smoke test
bmehta001 Jul 6, 2026
6be37b1
Fix teardown deadlock: always signal flush completion
bmehta001 Jul 8, 2026
80b9c80
Merge remote-tracking branch 'msft/main' into bhamehta/runtime-fixes
bmehta001 Jul 8, 2026
ce1699e
Drain pending tasks in the worker on shutdown to avoid a self-Join leak
bmehta001 Jul 8, 2026
e6769f1
Assert the /slow/ endpoint rewrite in the teardown smoke test
bmehta001 Jul 8, 2026
cc8ece8
Cast chrono counts to long long in %lld LOG_TRACE calls
bmehta001 Jul 9, 2026
c10f636
Drop issue-number reference from teardown smoke-test comment
bmehta001 Jul 9, 2026
21233a6
Drop issue-number reference from metastats opt-in comments
bmehta001 Jul 9, 2026
46e8b1d
Merge remote-tracking branch 'msft/main' into bhamehta/runtime-fixes
bmehta001 Jul 10, 2026
099348f
Fix use-after-free when the last worker reference is released on its …
bmehta001 Jul 10, 2026
be00ea0
Make worker self-dispose detection survive a prior detach()
bmehta001 Jul 11, 2026
9dd565a
Address review: portable worker-id storage and fix thread-id logging UB
bmehta001 Jul 12, 2026
b9d9d03
Avoid public queue-result dispatcher virtual
bmehta001 Jul 13, 2026
7ef8109
Fix deferred task lifetime tracking and shutdown cleanup
bmehta001 Jul 31, 2026
d325700
Guard OfflineStorageHandler::Flush against leaking StartActivity on e…
bmehta001 Aug 1, 2026
074c6e4
Leak LogManagerFactory and PAL singletons to avoid static-destruction…
bmehta001 Aug 1, 2026
aae640e
Merge upstream main into runtime fixes
bmehta001 Aug 4, 2026
d803615
Fix shutdown and flush review findings
bmehta001 Aug 5, 2026
fece2b2
Guard flush exception completion
bmehta001 Aug 5, 2026
36e4927
Merge branch 'main' into bhamehta/runtime-fixes
bmehta001 Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/api/LogManagerFactory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ namespace MAT_NS_BEGIN {

// C++11 Magic Statics (N2660)
static LogManagerFactory& instance() {
static LogManagerFactory impl;
// Deliberately never destroyed. LogManagerProvider::Release() must be
// able to walk this factory's registries during process teardown, but
// a normal function-local static's destruction order relative to that
// teardown call is unspecified -- if this were destroyed first,
// Release() would walk already-freed std::map nodes (a downstream
// consumer observed this as EXC_BAD_ACCESS in release() at process
// exit). Leaking one small, fixed-size object avoids the ordering
// hazard entirely; the OS reclaims it when the process exits.
static LogManagerFactory& impl = *new LogManagerFactory();
return impl;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/api/Logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ namespace MAT_NS_BEGIN

Logger::~Logger() noexcept
{
LOG_TRACE("%p: Destroyed", this);
// Intentionally empty — logging here triggers a static-destruction-order
// crash on iOS simulator (recursive_mutex used after teardown).
}

ISemanticContext* Logger::GetSemanticContext() const
Expand Down
3 changes: 3 additions & 0 deletions lib/http/HttpClient_WinInet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class WinInetRequestWrapper
if (m_hWinInetRequest != nullptr)
{
::InternetCloseHandle(m_hWinInetRequest);
}
if (m_hWinInetSession != nullptr)
{
::InternetCloseHandle(m_hWinInetSession);
}
}
Expand Down
3 changes: 0 additions & 3 deletions lib/http/HttpResponseDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,11 @@ namespace MAT_NS_BEGIN {
break;

case HttpResult_Aborted:
ctx->httpResponse = nullptr;
outcome = Abort;
break;

case HttpResult_LocalFailure:
case HttpResult_NetworkFailure:
ctx->httpResponse = nullptr;
outcome = RetryNetwork;
break;
}
Expand Down Expand Up @@ -132,7 +130,6 @@ namespace MAT_NS_BEGIN {
evt.param2 = ctx->recordIdsAndTenantIds.size();
DispatchEvent(evt);
}
ctx->httpResponse = nullptr;
// eventsRejected(ctx); // FIXME: [MG] - investigate why ctx gets corrupt after eventsRejected
requestAborted(ctx);
break;
Expand Down
2 changes: 1 addition & 1 deletion lib/include/public/ITaskDispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,11 @@ namespace MAT_NS_BEGIN
/// <param name="waitTime">Amount of time to wait for if the task is currently executing</param>
/// <returns>True if successfully cancelled, else false</returns>
virtual bool Cancel(Task* task, uint64_t waitTime = 0) = 0;

};

/// @endcond

} MAT_NS_END

#endif // ITASKDISPATCHER_HPP

146 changes: 100 additions & 46 deletions lib/offline/OfflineStorageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,44 @@ namespace MAT_NS_BEGIN {
}
}

/// <summary>
/// RAII guard around ILogManager::StartActivity()/EndActivity(). Flush()
/// used to pair these manually (StartActivity() at the top, EndActivity()
/// on the last line), so an exception thrown by disk I/O or by
/// IOfflineStorageObserver::OnStorageRecordsSaved() partway through would
/// skip EndActivity() and permanently leak the pause-activity count --
/// deadlocking every later FlushAndTeardown()'s WaitPause(). This guard
/// guarantees EndActivity() runs on every exit path, matching the existing
/// safe pattern used by PauseGuard (TransmissionPolicyManager.cpp) and
/// ActiveLoggerCall (Logger.cpp).
/// </summary>
class ActivityGuard
{
public:
explicit ActivityGuard(ILogManager& logManager) noexcept :
m_logManager(logManager),
m_active(logManager.StartActivity())
{
}

~ActivityGuard() noexcept
{
if (m_active)
{
m_logManager.EndActivity();
}
}

ActivityGuard(ActivityGuard const&) = delete;
ActivityGuard& operator=(ActivityGuard const&) = delete;

bool IsActive() const noexcept { return m_active; }

private:
ILogManager& m_logManager;
bool m_active;
};

bool OfflineStorageHandler::isKilled(StorageRecord const& record)
{
return (
Expand All @@ -64,7 +102,7 @@ namespace MAT_NS_BEGIN {
if (!m_flushPending)
return;
}
LOG_INFO("Waiting for pending Flush (%p) to complete...", m_flushHandle.m_task);
LOG_INFO("Waiting for pending Flush (%p) to complete...", m_flushHandle.GetTask());
m_flushComplete.wait();
}

Expand Down Expand Up @@ -163,65 +201,81 @@ namespace MAT_NS_BEGIN {

void OfflineStorageHandler::Flush()
{
if (!m_logManager.StartActivity()) {
ActivityGuard activityGuard(m_logManager);
if (!activityGuard.IsActive()) {
// The LogManager is shutting down, so the flush cannot run. Still
// signal completion and clear the pending flag so a concurrent
// WaitForFlush() (e.g. during teardown) does not block forever
// waiting for m_flushComplete.
LOCKGUARD(m_flushLock);
m_flushHandle.Cancel();
m_flushComplete.post();
m_flushPending = false;
return;
}
// Flush could be executed from context of worker thread, as well as from TPM and
// after HTTP callback. Make sure it is atomic / thread-safe.
LOCKGUARD(m_flushLock);
try
{
// Flush could be executed from context of worker thread, as well as from TPM and
// after HTTP callback. Make sure it is atomic / thread-safe.
LOCKGUARD(m_flushLock);

// If item isn't scheduled yet, it gets canceled, so that we don't do two flushes.
// If we are running that item right now (our thread), then nothing happens other
// than the handle gets replaced by nullptr in this DeferredCallbackHandle obj.
m_flushHandle.Cancel();
// If item isn't scheduled yet, it gets canceled, so that we don't do two flushes.
// If we are running that item right now (our thread), then nothing happens other
// than the handle reporting nullptr once that task finishes.
m_flushHandle.Cancel();

size_t dbSizeBeforeFlush = (m_offlineStorageMemory != nullptr) ? m_offlineStorageMemory->GetSize() : 0;
if ((m_offlineStorageMemory) && (dbSizeBeforeFlush > 0) && (m_offlineStorageDisk))
{
// This will block on and then take a lock for the duration of this move, and
// StoreRecord() will then block until the move completes.
auto records = m_offlineStorageMemory->GetRecords(false, EventLatency_Unspecified);
std::vector<StorageRecordId> ids;
size_t dbSizeBeforeFlush = (m_offlineStorageMemory != nullptr) ? m_offlineStorageMemory->GetSize() : 0;
if ((m_offlineStorageMemory) && (dbSizeBeforeFlush > 0) && (m_offlineStorageDisk))
{
// This will block on and then take a lock for the duration of this move, and
// StoreRecord() will then block until the move completes.
auto records = m_offlineStorageMemory->GetRecords(false, EventLatency_Unspecified);
std::vector<StorageRecordId> ids;

// TODO: [MG] - consider running the batch in transaction
// if (sqlite)
// sqlite->Execute("BEGIN");

// TODO: [MG] - consider running the batch in transaction
// if (sqlite)
// sqlite->Execute("BEGIN");
size_t totalSaved = m_offlineStorageDisk->StoreRecords(records);

size_t totalSaved = m_offlineStorageDisk->StoreRecords(records);
// TODO: [MG] - consider running the batch in transaction
// if (sqlite)
// sqlite->Execute("END");

// TODO: [MG] - consider running the batch in transaction
// if (sqlite)
// sqlite->Execute("END");
// Delete records from reserved on flush
HttpHeaders dummy;
bool fromMemory = true;
m_offlineStorageMemory->DeleteRecords(ids, dummy, fromMemory);
Comment thread
bmehta001 marked this conversation as resolved.

// Delete records from reserved on flush
HttpHeaders dummy;
bool fromMemory = true;
m_offlineStorageMemory->DeleteRecords(ids, dummy, fromMemory);
// Notify event listener about the records cached
OnStorageRecordsSaved(totalSaved);

// Notify event listener about the records cached
OnStorageRecordsSaved(totalSaved);
if (m_offlineStorageMemory->GetSize() > dbSizeBeforeFlush)
{
// We managed to accumulate as much data as we had before the flush,
// means we cannot keep up flushing at the same speed as incoming
// obviously because the disk is slower than ram.
LOG_WARN("Data is arriving too fast!");
}
}

if (m_offlineStorageMemory->GetSize() > dbSizeBeforeFlush)
// Checkpoint DB
if (m_config.HasConfig(CFG_BOOL_CHECKPOINT_DB_ON_FLUSH) && m_config[CFG_BOOL_CHECKPOINT_DB_ON_FLUSH])
{
// We managed to accumulate as much data as we had before the flush,
// means we cannot keep up flushing at the same speed as incoming
// obviously because the disk is slower than ram.
LOG_WARN("Data is arriving too fast!");
m_offlineStorageDisk->Flush();
}
}

// Checkpoint DB
if (m_config.HasConfig(CFG_BOOL_CHECKPOINT_DB_ON_FLUSH) && m_config[CFG_BOOL_CHECKPOINT_DB_ON_FLUSH])
m_isStorageFullNotificationSend = false;
m_flushComplete.post();
m_flushPending = false;
}
catch (...)
{
m_offlineStorageDisk->Flush();
LOCKGUARD(m_flushLock);
m_flushComplete.post();
m_flushPending = false;
throw;
}

m_isStorageFullNotificationSend = false;

// Flush is done, notify the waiters
m_flushComplete.post();
m_flushPending = false;
m_logManager.EndActivity();
}

bool OfflineStorageHandler::StoreRecord(StorageRecord const& record)
Expand Down Expand Up @@ -260,7 +314,7 @@ namespace MAT_NS_BEGIN {
m_flushPending = true;
m_flushComplete.Reset();
m_flushHandle = PAL::scheduleTask(&m_taskDispatcher, 0, this, &OfflineStorageHandler::Flush);
LOG_INFO("Requested Flush (%p)", m_flushHandle.m_task);
LOG_INFO("Requested Flush (%p)", m_flushHandle.GetTask());
}
m_flushLock.unlock();
}
Expand Down
13 changes: 12 additions & 1 deletion lib/pal/PAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,18 @@ namespace PAL_NS_BEGIN {

PlatformAbstractionLayer& GetPAL() noexcept
{
static PlatformAbstractionLayer pal;
// Deliberately never destroyed. PAL::shutdown() (called from
// LogManagerImpl::FlushAndTeardown()) must find this object's members
// still alive, but PAL is constructed lazily on first use, so whether
// this function-local static is destroyed before or after that
// teardown call depends on runtime timing, not source order -- if it
// is destroyed first, shutdown() releases shared_ptr members of an
// already-destroyed object (a downstream consumer observed this as
// intermittent EXC_BAD_ACCESS in ~shared_ptr<ISystemInformation> at
// process exit). Leaking one fixed-size object avoids the ordering
// hazard entirely: shutdown() already performs the real resource
// teardown explicitly, and the OS reclaims the object at process exit.
static PlatformAbstractionLayer& pal = *new PlatformAbstractionLayer();
return pal;
}

Expand Down
Loading
Loading