Skip to content

Commit 4a2bf04

Browse files
committed
Refactor current shutdown monitoring and add synchronization system for component attach
1 parent 1d8b9f5 commit 4a2bf04

File tree

10 files changed

+462
-272
lines changed

10 files changed

+462
-272
lines changed

src/AppInstallerCLICore/AppInstallerCLICore.vcxproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@
346346
<ClInclude Include="ContextOrchestrator.h" />
347347
<ClInclude Include="COMContext.h" />
348348
<ClInclude Include="Public\ConfigurationSetProcessorFactoryRemoting.h" />
349+
<ClInclude Include="Public\ShutdownMonitoring.h" />
349350
<ClInclude Include="Sixel.h" />
350351
<ClInclude Include="Workflows\ConfigurationFlow.h" />
351352
<ClInclude Include="Workflows\DependenciesFlow.h" />
@@ -419,6 +420,7 @@
419420
<ClCompile Include="ConfigurationWingetDscModuleUnitValidation.cpp" />
420421
<ClCompile Include="ConfigureExportCommand.cpp" />
421422
<ClCompile Include="ContextOrchestrator.cpp" />
423+
<ClCompile Include="ShutdownMonitoring.cpp" />
422424
<ClCompile Include="Sixel.cpp" />
423425
<ClCompile Include="Workflows\ConfigurationFlow.cpp" />
424426
<ClCompile Include="Workflows\DependenciesFlow.cpp" />
@@ -504,4 +506,4 @@
504506
<Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorTextNuget)', '$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
505507
<Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorTextNuget)', '$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
506508
</Target>
507-
</Project>
509+
</Project>

src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@
293293
<ClInclude Include="Commands\DscAdminSettingsResource.h">
294294
<Filter>Commands\Configuration</Filter>
295295
</ClInclude>
296+
<ClInclude Include="Public\ShutdownMonitoring.h">
297+
<Filter>Public</Filter>
298+
</ClInclude>
296299
</ItemGroup>
297300
<ItemGroup>
298301
<ClCompile Include="pch.cpp">
@@ -553,6 +556,9 @@
553556
<ClCompile Include="Commands\DscAdminSettingsResource.cpp">
554557
<Filter>Commands\Configuration</Filter>
555558
</ClCompile>
559+
<ClCompile Include="ShutdownMonitoring.cpp">
560+
<Filter>Source Files</Filter>
561+
</ClCompile>
556562
</ItemGroup>
557563
<ItemGroup>
558564
<None Include="PropertySheet.props" />

src/AppInstallerCLICore/Commands/TestCommand.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "AppInstallerRuntime.h"
99
#include "TableOutput.h"
1010
#include "Public/ConfigurationSetProcessorFactoryRemoting.h"
11+
#include "Public/ShutdownMonitoring.h"
1112
#include "Workflows/ConfigurationFlow.h"
1213
#include <winrt/Microsoft.Management.Configuration.h>
1314

@@ -27,7 +28,7 @@ namespace AppInstaller::CLI
2728
HRESULT WaitForShutdown(Execution::Context& context)
2829
{
2930
LogAndReport(context, "Waiting for app shutdown event");
30-
if (!Execution::WaitForAppShutdownEvent())
31+
if (!ShutdownMonitoring::TerminationSignalHandler::Instance().WaitForAppShutdownEvent())
3132
{
3233
LogAndReport(context, "Failed getting app shutdown event");
3334
return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR;
@@ -39,7 +40,7 @@ namespace AppInstaller::CLI
3940

4041
HRESULT AppShutdownWindowMessage(Execution::Context& context)
4142
{
42-
auto windowHandle = Execution::GetWindowHandle();
43+
auto windowHandle = ShutdownMonitoring::TerminationSignalHandler::Instance().GetWindowHandle();
4344

4445
if (windowHandle == NULL)
4546
{

src/AppInstallerCLICore/ExecutionContext.cpp

Lines changed: 2 additions & 267 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "COMContext.h"
77
#include "Command.h"
88
#include "ExecutionContext.h"
9+
#include "Public/ShutdownMonitoring.h"
910
#include <winget/Checkpoint.h>
1011
#include <winget/Reboot.h>
1112
#include <winget/UserSettings.h>
@@ -19,262 +20,6 @@ namespace AppInstaller::CLI::Execution
1920

2021
namespace
2122
{
22-
// Type to contain the CTRL signal and window messages handler.
23-
struct SignalTerminationHandler
24-
{
25-
static SignalTerminationHandler& Instance()
26-
{
27-
static SignalTerminationHandler s_instance;
28-
return s_instance;
29-
}
30-
31-
void AddContext(Context* context)
32-
{
33-
std::lock_guard<std::mutex> lock{ m_contextsLock };
34-
35-
auto itr = std::find(m_contexts.begin(), m_contexts.end(), context);
36-
THROW_HR_IF(E_NOT_VALID_STATE, itr != m_contexts.end());
37-
m_contexts.push_back(context);
38-
}
39-
40-
void RemoveContext(Context* context)
41-
{
42-
std::lock_guard<std::mutex> lock{ m_contextsLock };
43-
44-
auto itr = std::find(m_contexts.begin(), m_contexts.end(), context);
45-
THROW_HR_IF(E_NOT_VALID_STATE, itr == m_contexts.end());
46-
m_contexts.erase(itr);
47-
}
48-
49-
void StartAppShutdown()
50-
{
51-
// Lifetime manager sends CTRL-C after the WM_QUERYENDSESSION is processed.
52-
// If we disable the CTRL-C handler, the default handler will kill us.
53-
TerminateContexts(CancelReason::AppShutdown, true);
54-
55-
#ifndef AICLI_DISABLE_TEST_HOOKS
56-
m_appShutdownEvent.SetEvent();
57-
#endif
58-
}
59-
60-
#ifndef AICLI_DISABLE_TEST_HOOKS
61-
HWND GetWindowHandle() { return m_windowHandle.get(); }
62-
63-
bool WaitForAppShutdownEvent()
64-
{
65-
return m_appShutdownEvent.wait(60000);
66-
}
67-
#endif
68-
69-
private:
70-
SignalTerminationHandler()
71-
{
72-
if (Runtime::IsRunningAsAdmin() && Runtime::IsRunningInPackagedContext())
73-
{
74-
m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage();
75-
m_updatingEvent = m_catalog.PackageUpdating(
76-
winrt::auto_revoke, [this](winrt::Windows::ApplicationModel::PackageCatalog, winrt::Windows::ApplicationModel::PackageUpdatingEventArgs args)
77-
{
78-
// There are 3 events being hit with 0%, 1% and 38%
79-
// Typically the window message is received between the first two.
80-
constexpr double minProgress = 0;
81-
auto progress = args.Progress();
82-
if (progress > minProgress)
83-
{
84-
SignalTerminationHandler::Instance().StartAppShutdown();
85-
}
86-
});
87-
}
88-
else
89-
{
90-
// Create message only window.
91-
m_messageQueueReady.create();
92-
m_windowThread = std::thread(&SignalTerminationHandler::CreateWindowAndStartMessageLoop, this);
93-
if (!m_messageQueueReady.wait(100))
94-
{
95-
AICLI_LOG(CLI, Warning, << "Timeout creating winget window");
96-
}
97-
}
98-
99-
// Set up ctrl-c handler.
100-
LOG_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler(StaticCtrlHandlerFunction, TRUE));
101-
102-
#ifndef AICLI_DISABLE_TEST_HOOKS
103-
m_appShutdownEvent.create();
104-
#endif
105-
}
106-
107-
~SignalTerminationHandler()
108-
{
109-
// At this point the thread is gone, but it will get angry
110-
// if there's no call to join.
111-
if (m_windowThread.joinable())
112-
{
113-
m_windowThread.join();
114-
}
115-
}
116-
117-
static BOOL WINAPI StaticCtrlHandlerFunction(DWORD ctrlType)
118-
{
119-
return Instance().CtrlHandlerFunction(ctrlType);
120-
}
121-
122-
static LRESULT WINAPI WindowMessageProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
123-
{
124-
AICLI_LOG(CLI, Verbose, << "Received window message type: " << uMsg);
125-
switch (uMsg)
126-
{
127-
case WM_QUERYENDSESSION:
128-
SignalTerminationHandler::Instance().StartAppShutdown();
129-
return TRUE;
130-
case WM_ENDSESSION:
131-
case WM_CLOSE:
132-
DestroyWindow(hWnd);
133-
break;
134-
case WM_DESTROY:
135-
PostQuitMessage(0);
136-
break;
137-
default:
138-
return DefWindowProc(hWnd, uMsg, wParam, lParam);
139-
}
140-
return FALSE;
141-
}
142-
143-
BOOL CtrlHandlerFunction(DWORD ctrlType)
144-
{
145-
// TODO: Move this to be logged per active context when we have thread static globals
146-
AICLI_LOG(CLI, Info, << "Got CTRL type: " << ctrlType);
147-
148-
switch (ctrlType)
149-
{
150-
case CTRL_C_EVENT:
151-
case CTRL_BREAK_EVENT:
152-
return TerminateContexts(CancelReason::CtrlCSignal, false);
153-
// According to MSDN, we should never receive these due to having gdi32/user32 loaded in our process.
154-
// But handle them as a force terminate anyway.
155-
case CTRL_CLOSE_EVENT:
156-
case CTRL_LOGOFF_EVENT:
157-
case CTRL_SHUTDOWN_EVENT:
158-
return TerminateContexts(CancelReason::CtrlCSignal, true);
159-
default:
160-
return FALSE;
161-
}
162-
}
163-
164-
// Terminates the currently attached contexts.
165-
// Returns FALSE if no contexts attached; TRUE otherwise.
166-
BOOL TerminateContexts(CancelReason reason, bool force)
167-
{
168-
if (m_contexts.empty())
169-
{
170-
return FALSE;
171-
}
172-
173-
{
174-
std::lock_guard<std::mutex> lock{ m_contextsLock };
175-
for (auto& context : m_contexts)
176-
{
177-
context->Cancel(reason, force);
178-
}
179-
}
180-
181-
return TRUE;
182-
}
183-
184-
void CreateWindowAndStartMessageLoop()
185-
{
186-
PCWSTR windowClass = L"wingetWindow";
187-
HINSTANCE hInstance = GetModuleHandle(NULL);
188-
if (hInstance == NULL)
189-
{
190-
LOG_LAST_ERROR_MSG("Failed getting module handle");
191-
return;
192-
}
193-
194-
WNDCLASSEX wcex = {};
195-
wcex.cbSize = sizeof(wcex);
196-
197-
wcex.style = CS_NOCLOSE;
198-
wcex.lpfnWndProc = SignalTerminationHandler::WindowMessageProcedure;
199-
wcex.cbClsExtra = 0;
200-
wcex.cbWndExtra = 0;
201-
wcex.hInstance = hInstance;
202-
wcex.lpszClassName = windowClass;
203-
204-
if (!RegisterClassEx(&wcex))
205-
{
206-
LOG_LAST_ERROR_MSG("Failed registering window class");
207-
return;
208-
}
209-
210-
m_windowHandle = wil::unique_hwnd(CreateWindow(
211-
windowClass,
212-
L"WingetMessageOnlyWindow",
213-
WS_OVERLAPPEDWINDOW,
214-
0, /* x */
215-
0, /* y */
216-
0, /* nWidth */
217-
0, /* nHeight */
218-
NULL, /* hWndParent */
219-
NULL, /* hMenu */
220-
hInstance,
221-
NULL)); /* lpParam */
222-
223-
if (m_windowHandle == nullptr)
224-
{
225-
LOG_LAST_ERROR_MSG("Failed creating window");
226-
return;
227-
}
228-
229-
ShowWindow(m_windowHandle.get(), SW_HIDE);
230-
231-
// Force message queue to be created.
232-
MSG msg;
233-
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
234-
m_messageQueueReady.SetEvent();
235-
236-
// Message loop
237-
BOOL getMessageResult;
238-
while ((getMessageResult = GetMessage(&msg, m_windowHandle.get(), 0, 0)) != 0)
239-
{
240-
if (getMessageResult == -1)
241-
{
242-
LOG_LAST_ERROR();
243-
}
244-
else
245-
{
246-
DispatchMessage(&msg);
247-
}
248-
}
249-
}
250-
251-
#ifndef AICLI_DISABLE_TEST_HOOKS
252-
wil::unique_event m_appShutdownEvent;
253-
#endif
254-
255-
std::mutex m_contextsLock;
256-
std::vector<Context*> m_contexts;
257-
wil::unique_event m_messageQueueReady;
258-
wil::unique_hwnd m_windowHandle;
259-
std::thread m_windowThread;
260-
winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr;
261-
decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageUpdating(winrt::auto_revoke, nullptr)) m_updatingEvent;
262-
};
263-
264-
void SetSignalTerminationHandlerContext(bool add, Context* context)
265-
{
266-
THROW_HR_IF(E_POINTER, context == nullptr);
267-
268-
if (add)
269-
{
270-
SignalTerminationHandler::Instance().AddContext(context);
271-
}
272-
else
273-
{
274-
SignalTerminationHandler::Instance().RemoveContext(context);
275-
}
276-
}
277-
27823
bool ShouldRemoveCheckpointDatabase(HRESULT hr)
27924
{
28025
switch (hr)
@@ -356,7 +101,7 @@ namespace AppInstaller::CLI::Execution
356101

357102
void Context::EnableSignalTerminationHandler(bool enabled)
358103
{
359-
SetSignalTerminationHandlerContext(enabled, this);
104+
ShutdownMonitoring::TerminationSignalHandler::EnableListener(enabled, this);
360105
m_disableSignalTerminationHandlerOnExit = enabled;
361106
}
362107

@@ -487,16 +232,6 @@ namespace AppInstaller::CLI::Execution
487232
{
488233
return (m_shouldExecuteWorkflowTask ? m_shouldExecuteWorkflowTask(task) : true);
489234
}
490-
491-
HWND GetWindowHandle()
492-
{
493-
return SignalTerminationHandler::Instance().GetWindowHandle();
494-
}
495-
496-
bool WaitForAppShutdownEvent()
497-
{
498-
return SignalTerminationHandler::Instance().WaitForAppShutdownEvent();
499-
}
500235
#endif
501236

502237
void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action)

src/AppInstallerCLICore/ExecutionContext.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "ExecutionContextData.h"
88
#include "CompletionData.h"
99
#include "CheckpointManager.h"
10+
#include <AppInstallerProgress.h>
1011
#include <winget/Checkpoint.h>
1112

1213
#include <string_view>
@@ -91,7 +92,7 @@ namespace AppInstaller::CLI::Execution
9192
// The context within which all commands execute.
9293
// Contains input/output via Execution::Reporter and
9394
// arguments via Execution::Args.
94-
struct Context : EnumBasedVariantMap<Data, details::DataMapping, ContextEnumBasedVariantMapActionCallback>
95+
struct Context : EnumBasedVariantMap<Data, details::DataMapping, ContextEnumBasedVariantMapActionCallback>, ICancellable
9596
{
9697
Context() = default;
9798
Context(std::ostream& out, std::istream& in) : Reporter(out, in) {}
@@ -139,7 +140,7 @@ namespace AppInstaller::CLI::Execution
139140
// Cancel the context; this terminates it as well as informing any in progress task to stop cooperatively.
140141
// Multiple attempts with CancelReason::CancelSignal may cause the process to simply exit.
141142
// The bypassUser indicates whether the user should be asked for cancellation (does not currently have any effect).
142-
void Cancel(CancelReason reason, bool bypassUser = false);
143+
void Cancel(CancelReason reason, bool bypassUser = false) override;
143144

144145
// Gets context flags
145146
ContextFlag GetFlags() const

0 commit comments

Comments
 (0)