Repository navigation
Fix 1DS telemetry deadlock during Windows process termination #33291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Jiajia Qin (qjia7)
merged 4 commits into
microsoft:main
from
qjia7:fix/1ds-windows-process-exit
Oct 17, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
5578aab
Fix 1DS telemetry deadlock during Windows process termination
qjia7 625b47c
Add Windows child-process coverage for 1DS teardown
qjia7 f18ad8a
Move telemetry lifecycle test targets beside other tests
qjia7 184affe
Merge latest main into fix/1ds-windows-process-exit
qjia7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
onnxruntime/test/platform/windows_telemetry/lifecycle/telemetry_shutdown_test.cc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #include <Windows.h> | ||
|
|
||
| #include <cstdint> | ||
| #include <cstdio> | ||
| #include <cstdlib> | ||
| #include <cwchar> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <string_view> | ||
|
|
||
| namespace { | ||
|
|
||
| struct CloseWin32Handle { | ||
| void operator()(HANDLE handle) const { CloseHandle(handle); } | ||
| }; | ||
| using UniqueHandle = std::unique_ptr<void, CloseWin32Handle>; | ||
|
|
||
| int Fail(const char* message) { | ||
| std::fprintf(stderr, "%s (Win32 error: %lu)\n", message, GetLastError()); | ||
| return EXIT_FAILURE; | ||
| } | ||
|
|
||
| int RunChild(const wchar_t* library_path, std::wstring_view mode, HANDLE verified) { | ||
| if (!SetEnvironmentVariableW(L"ORT_DISABLE_TELEMETRY", L"1")) { | ||
| return Fail("Could not disable network telemetry"); | ||
| } | ||
| const HMODULE library = LoadLibraryW(library_path); | ||
| if (!library) { | ||
| return Fail("LoadLibraryW failed"); | ||
| } | ||
| using RunTest = void (*)(bool, bool, HANDLE); | ||
| const auto run_test = reinterpret_cast<RunTest>(GetProcAddress(library, "OrtTestTelemetryShutdown")); | ||
| if (!run_test) { | ||
| return Fail("GetProcAddress failed"); | ||
| } | ||
| const bool process_exit = mode == L"process_exit"; | ||
| run_test(process_exit, mode == L"normal_destruction", verified); | ||
| const DWORD expected_signal = mode == L"normal_destruction" ? WAIT_OBJECT_0 : WAIT_TIMEOUT; | ||
| if (WaitForSingleObject(verified, 0) != expected_signal) { | ||
| return Fail("Telemetry destruction happened at the wrong lifecycle stage"); | ||
| } | ||
| if (process_exit) { | ||
| ExitProcess(EXIT_SUCCESS); | ||
| } | ||
| if (!FreeLibrary(library)) { | ||
| return Fail("FreeLibrary failed"); | ||
| } | ||
| return EXIT_SUCCESS; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| int wmain(int argc, wchar_t** argv) { | ||
| if (argc == 5 && std::wstring_view(argv[1]) == L"--child") { | ||
| const auto verified = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(std::wcstoull(argv[4], nullptr, 10))); | ||
| return RunChild(argv[2], argv[3], verified); | ||
| } | ||
| if (argc != 3) { | ||
| return Fail("Expected test DLL path and lifecycle mode"); | ||
| } | ||
| const std::wstring_view mode(argv[2]); | ||
| if (mode != L"process_exit" && mode != L"normal_destruction" && mode != L"explicit_unload") { | ||
| return Fail("Unknown lifecycle mode"); | ||
| } | ||
|
|
||
| // The parent checks a signal from the DLL destructor, so an early child exit | ||
| // cannot pass without actually exercising and verifying telemetry teardown. | ||
| SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; | ||
| UniqueHandle verified(CreateEventW(&attributes, TRUE, FALSE, nullptr)); | ||
| if (!verified) { | ||
| return Fail("CreateEventW failed"); | ||
| } | ||
| std::wstring executable(32768, L'\0'); | ||
| const DWORD length = GetModuleFileNameW(nullptr, executable.data(), static_cast<DWORD>(executable.size())); | ||
| if (length == 0 || length >= executable.size()) { | ||
| return Fail("GetModuleFileNameW failed"); | ||
| } | ||
| executable.resize(length); | ||
| std::wstring command = L"\"" + executable + L"\" --child \"" + argv[1] + L"\" " + argv[2] + L" " + | ||
| std::to_wstring(reinterpret_cast<uintptr_t>(verified.get())); | ||
| STARTUPINFOW startup{}; | ||
| startup.cb = sizeof(startup); | ||
| PROCESS_INFORMATION info{}; | ||
| if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, | ||
| nullptr, nullptr, &startup, &info)) { | ||
| return Fail("CreateProcessW failed"); | ||
| } | ||
| UniqueHandle process(info.hProcess); | ||
| UniqueHandle thread(info.hThread); | ||
| if (WaitForSingleObject(process.get(), 10000) != WAIT_OBJECT_0) { | ||
| TerminateProcess(process.get(), EXIT_FAILURE); | ||
| WaitForSingleObject(process.get(), 2000); | ||
| return Fail("Child process did not exit within 10 seconds"); | ||
| } | ||
| DWORD exit_code = EXIT_FAILURE; | ||
| if (!GetExitCodeProcess(process.get(), &exit_code)) { | ||
| return Fail("GetExitCodeProcess failed"); | ||
| } | ||
| if (exit_code != EXIT_SUCCESS) { | ||
| std::fprintf(stderr, "Child process failed with exit code %lu\n", exit_code); | ||
| return EXIT_FAILURE; | ||
| } | ||
| if (WaitForSingleObject(verified.get(), 0) != WAIT_OBJECT_0) { | ||
| return Fail("Child did not verify telemetry destruction"); | ||
| } | ||
| return EXIT_SUCCESS; | ||
| } |
123 changes: 123 additions & 0 deletions
123
onnxruntime/test/platform/windows_telemetry/lifecycle/telemetry_shutdown_test_library.cc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #include <Windows.h> | ||
|
|
||
| #include <cstdio> | ||
| #include <cstdlib> | ||
| #include <memory> | ||
| #include <optional> | ||
|
|
||
| #include <ILogConfiguration.hpp> | ||
| #include <NullObjects.hpp> | ||
|
|
||
| #include "core/platform/telemetry_1ds.h" | ||
| #include "core/platform/telemetry_1ds_platform.h" | ||
|
|
||
| namespace onnxruntime { | ||
| namespace { | ||
|
|
||
| void Check(bool condition, const char* message) { | ||
| if (!condition) { | ||
| std::fprintf(stderr, "%s (Win32 error: %lu)\n", message, GetLastError()); | ||
| std::fflush(stderr); | ||
| // Also fail reliably from a DLL process-detach destructor. | ||
| TerminateProcess(GetCurrentProcess(), EXIT_FAILURE); | ||
| std::abort(); | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| class OneDsTelemetryTestPeer { | ||
| public: | ||
| static void Install(Microsoft::Applications::Events::ILogManager& manager) { | ||
| Check(OneDsTelemetry::global_register_count_ == 1, "Expected exactly one telemetry instance"); | ||
| Check(!OneDsTelemetry::log_manager_ && !OneDsTelemetry::config_, "Real uploader must be disabled"); | ||
| OneDsTelemetry::config_ = std::make_unique<Microsoft::Applications::Events::ILogConfiguration>(); | ||
| OneDsTelemetry::log_manager_ = &manager; | ||
| } | ||
|
|
||
| static bool IsClean() { | ||
| return OneDsTelemetry::global_register_count_ == 0 && | ||
| !OneDsTelemetry::log_manager_ && !OneDsTelemetry::config_; | ||
| } | ||
| }; | ||
|
|
||
| namespace { | ||
|
|
||
| class MockLogManager : public Microsoft::Applications::Events::NullLogManager { | ||
| public: | ||
| ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(MockLogManager); | ||
|
|
||
| explicit MockLogManager(bool pending) : callback_(CreateEventW(nullptr, TRUE, !pending, nullptr)) { | ||
| Check(callback_ != nullptr, "CreateEventW failed"); | ||
| } | ||
|
|
||
| ~MockLogManager() override { CloseHandle(callback_); } | ||
|
|
||
| Microsoft::Applications::Events::status_t Flush() override { | ||
| ++flush_count; | ||
| return Microsoft::Applications::Events::STATUS_SUCCESS; | ||
| } | ||
|
|
||
| void FlushAndTeardown() override { | ||
| Check(flush_count == 1, "Expected Flush before FlushAndTeardown"); | ||
| ++teardown_count; | ||
| // A pending callback cannot arrive after ExitProcess has killed the other | ||
| // threads. Bound the mock's wait so a regression does not leave a stuck DLL. | ||
| Check(WaitForSingleObject(callback_, 2000) == WAIT_OBJECT_0, "Timed out draining the pending uploader"); | ||
| } | ||
|
|
||
| int flush_count = 0; | ||
| int teardown_count = 0; | ||
|
|
||
| private: | ||
| HANDLE callback_; | ||
| }; | ||
|
|
||
| class TelemetryLifetime { | ||
| public: | ||
| ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(TelemetryLifetime); | ||
|
|
||
| TelemetryLifetime(bool process_exit, HANDLE verified) | ||
| : manager_(process_exit), process_exit_(process_exit), verified_(verified) { | ||
| telemetry_.emplace(); | ||
| OneDsTelemetryTestPeer::Install(manager_); | ||
| } | ||
|
|
||
| ~TelemetryLifetime() { | ||
| if (telemetry_) { | ||
| Destroy(); | ||
| } | ||
| } | ||
|
|
||
| void Destroy() { | ||
| Check(telemetry_internal::IsDllShutdownInProgress() == process_exit_, "Unexpected DLL shutdown state"); | ||
| telemetry_.reset(); | ||
| const int expected_calls = process_exit_ ? 0 : 1; | ||
| Check(manager_.flush_count == expected_calls, "Unexpected Flush count"); | ||
| Check(manager_.teardown_count == expected_calls, "Unexpected FlushAndTeardown count"); | ||
| if (!process_exit_) { | ||
| Check(OneDsTelemetryTestPeer::IsClean(), "Telemetry state was not cleaned up"); | ||
| } | ||
| Check(SetEvent(verified_) != FALSE, "Could not signal verified destruction"); | ||
| } | ||
|
|
||
| private: | ||
| MockLogManager manager_; | ||
| std::optional<OneDsTelemetry> telemetry_; | ||
| bool process_exit_; | ||
| HANDLE verified_; | ||
| }; | ||
|
|
||
| } // namespace | ||
| } // namespace onnxruntime | ||
|
|
||
| extern "C" void OrtTestTelemetryShutdown(bool process_exit, bool destroy_now, HANDLE verified) { | ||
| // A DLL-owned static exercises CRT destruction during real Windows shutdown. | ||
| static onnxruntime::TelemetryLifetime lifetime(process_exit, verified); | ||
| if (destroy_now) { | ||
| lifetime.Destroy(); | ||
| } | ||
| } |
3 changes: 3 additions & 0 deletions
3
onnxruntime/test/platform/windows_telemetry/lifecycle/telemetry_shutdown_test_library.def
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| LIBRARY onnxruntime_telemetry_shutdown_test_library | ||
| EXPORTS | ||
| OrtTestTelemetryShutdown |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.