Skip to content

Commit 8ea16f8

Browse files
authored
Improve window thread termination (#5781)
## Change Shut down the window thread as part of the `TerminationSignalHandler` destruction. Properly unregister the window class after destroying the window on the thread. Signal the shutdown via a `WM_DESTROY`.
1 parent 15232a7 commit 8ea16f8

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

src/AppInstallerCLICore/Commands/TestCommand.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "Workflows/MSStoreInstallerHandler.h"
1414
#include <winget/RepositorySource.h>
1515
#include <winrt/Microsoft.Management.Configuration.h>
16+
#include <winrt/Windows.ApplicationModel.Core.h>
1617

1718
using namespace AppInstaller::CLI::Workflow;
1819
using namespace AppInstaller::Utility::literals;
@@ -74,7 +75,7 @@ namespace AppInstaller::CLI
7475
NULL,
7576
ENDSESSION_CLOSEAPP,
7677
(SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT),
77-
5000,
78+
10000,
7879
NULL));
7980
}
8081

@@ -277,6 +278,41 @@ namespace AppInstaller::CLI
277278
return module ? module->GetObjectCount() : 0;
278279
}
279280
};
281+
282+
struct TestTerminateTerminationSignalHandler final : public Command
283+
{
284+
TestTerminateTerminationSignalHandler(std::string_view parent) : Command("term-signal-handler", {}, parent, Visibility::Hidden) {}
285+
286+
Resource::LocString ShortDescription() const override
287+
{
288+
return "Test TerminationSignalHandler thread"_lis;
289+
}
290+
291+
Resource::LocString LongDescription() const override
292+
{
293+
return "Forces the TerminationSignalHandler static object to be destroyed so that the thread behavior can be observed."_lis;
294+
}
295+
296+
protected:
297+
void ExecuteInternal(Execution::Context& context) const override
298+
{
299+
// Destroy the one created by standard execution
300+
// We join on the window thread, so if this never exits we have failed the test.
301+
winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(L"WindowsPackageManager.TerminationSignalHandler");
302+
303+
// Create a new instance
304+
auto instance = ShutdownMonitoring::TerminationSignalHandler::Instance();
305+
306+
if (instance->GetWindowHandle() == nullptr)
307+
{
308+
LogAndReport(context, "Didn't get a window handle");
309+
}
310+
else
311+
{
312+
LogAndReport(context, "Got a window handle");
313+
}
314+
}
315+
};
280316
}
281317

282318
std::vector<std::unique_ptr<Command>> TestCommand::GetCommands() const
@@ -287,6 +323,7 @@ namespace AppInstaller::CLI
287323
std::make_unique<TestConfigurationExportCommand>(FullName()),
288324
std::make_unique<TestConfigurationFindUnitProcessorsCommand>(FullName()),
289325
std::make_unique<TestCanUnloadNowCommand>(FullName()),
326+
std::make_unique<TestTerminateTerminationSignalHandler>(FullName()),
290327
});
291328
}
292329

src/AppInstallerCLICore/ShutdownMonitoring.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <AppInstallerRuntime.h>
88
#include <winget/COMStaticStorage.h>
99

10+
using namespace std::chrono_literals;
11+
1012
namespace AppInstaller::ShutdownMonitoring
1113
{
1214
std::shared_ptr<TerminationSignalHandler> TerminationSignalHandler::Instance()
@@ -102,7 +104,13 @@ namespace AppInstaller::ShutdownMonitoring
102104
// std::thread requires that any managed thread (joinable) be joined or detached before destructing
103105
if (m_windowThread.joinable())
104106
{
105-
m_windowThread.detach();
107+
if (m_windowHandle)
108+
{
109+
// Inform the thread that it should stop.
110+
PostMessageW(m_windowHandle.get(), WM_DESTROY, 0, 0);
111+
}
112+
113+
m_windowThread.join();
106114
}
107115
}
108116

@@ -214,6 +222,12 @@ namespace AppInstaller::ShutdownMonitoring
214222
return;
215223
}
216224

225+
// Unregister the window class on exiting the thread
226+
auto classUnregister = wil::scope_exit([&]()
227+
{
228+
UnregisterClassW(windowClass, hInstance);
229+
});
230+
217231
m_windowHandle = wil::unique_hwnd(CreateWindow(
218232
windowClass,
219233
L"WingetMessageOnlyWindow",
@@ -227,26 +241,38 @@ namespace AppInstaller::ShutdownMonitoring
227241
hInstance,
228242
NULL)); /* lpParam */
229243

230-
if (m_windowHandle == nullptr)
244+
HWND windowHandle = m_windowHandle.get();
245+
if (windowHandle == nullptr)
231246
{
232247
LOG_LAST_ERROR_MSG("Failed creating window");
233248
return;
234249
}
235250

236-
ShowWindow(m_windowHandle.get(), SW_HIDE);
251+
// We must destroy the window first so that the class unregister can succeed
252+
auto destroyWindow = wil::scope_exit([&]()
253+
{
254+
DestroyWindow(windowHandle);
255+
});
256+
257+
ShowWindow(windowHandle, SW_HIDE);
237258

238259
// Force message queue to be created.
239260
MSG msg;
240261
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
241262
m_messageQueueReady.SetEvent();
242263

243-
// Message loop
264+
// Message loop, we send WM_DESTROY to terminate it
244265
BOOL getMessageResult;
245-
while ((getMessageResult = GetMessage(&msg, m_windowHandle.get(), 0, 0)) != 0)
266+
while ((getMessageResult = GetMessage(&msg, windowHandle, 0, 0)) != 0)
246267
{
247268
if (getMessageResult == -1)
248269
{
249270
LOG_LAST_ERROR();
271+
break;
272+
}
273+
else if (msg.message == WM_DESTROY)
274+
{
275+
break;
250276
}
251277
else
252278
{

src/AppInstallerCLIE2ETests/AppShutdownTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,15 @@ public void CanUnloadNowTest()
121121
Assert.True(lines[3].Contains("Internal objects: 0"));
122122
Assert.True(lines[4].Contains("External objects: 0"));
123123
}
124+
125+
/// <summary>
126+
/// Runs winget test term-signal-handler to check for proper thread termination.
127+
/// </summary>
128+
[Test]
129+
public void TermSignalHandler()
130+
{
131+
var result = TestCommon.RunAICLICommand("test", "term-signal-handler --verbose");
132+
Assert.True(result.StdOut.Contains("Got a window handle"));
133+
}
124134
}
125135
}

0 commit comments

Comments
 (0)