ShutdownMonitoring.cpp (11580B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/ShutdownMonitoring.h" 5 #include <AppInstallerErrors.h> 6 #include <AppInstallerLogging.h> 7 #include <AppInstallerRuntime.h> 8 #include <winget/COMStaticStorage.h> 9 10 namespace AppInstaller::ShutdownMonitoring 11 { 12 std::shared_ptr<TerminationSignalHandler> TerminationSignalHandler::Instance() 13 { 14 struct Singleton : public WinRT::COMStaticStorageBase<TerminationSignalHandler> 15 { 16 Singleton() : COMStaticStorageBase(L"WindowsPackageManager.TerminationSignalHandler") {} 17 }; 18 19 static Singleton s_instance; 20 return s_instance.Get(); 21 } 22 23 void TerminationSignalHandler::AddListener(ICancellable* cancellable) 24 { 25 std::lock_guard<std::mutex> lock{ m_listenersLock }; 26 27 auto itr = std::find(m_listeners.begin(), m_listeners.end(), cancellable); 28 THROW_HR_IF(E_NOT_VALID_STATE, itr != m_listeners.end()); 29 m_listeners.push_back(cancellable); 30 } 31 32 void TerminationSignalHandler::RemoveListener(ICancellable* cancellable) 33 { 34 std::lock_guard<std::mutex> lock{ m_listenersLock }; 35 36 auto itr = std::find(m_listeners.begin(), m_listeners.end(), cancellable); 37 if (itr == m_listeners.end()) 38 { 39 AICLI_LOG(CLI, Warning, << "TerminationSignalHandler::RemoveListener did not find requested object"); 40 } 41 else 42 { 43 m_listeners.erase(itr); 44 } 45 } 46 47 void TerminationSignalHandler::EnableListener(bool enabled, ICancellable* cancellable) 48 { 49 if (enabled) 50 { 51 Instance()->AddListener(cancellable); 52 } 53 else 54 { 55 Instance()->RemoveListener(cancellable); 56 } 57 } 58 59 #ifndef AICLI_DISABLE_TEST_HOOKS 60 HWND TerminationSignalHandler::GetWindowHandle() const 61 { 62 return m_windowHandle.get(); 63 } 64 65 bool TerminationSignalHandler::WaitForAppShutdownEvent() const 66 { 67 return m_appShutdownEvent.wait(60000); 68 } 69 #endif 70 71 TerminationSignalHandler::TerminationSignalHandler() 72 { 73 #ifndef AICLI_DISABLE_TEST_HOOKS 74 m_appShutdownEvent.create(); 75 #endif 76 77 if (Runtime::IsRunningInPackagedContext()) 78 { 79 // Create package update listener 80 m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage(); 81 m_updatingEvent = m_catalog.PackageUpdating( 82 winrt::auto_revoke, [this](winrt::Windows::ApplicationModel::PackageCatalog, winrt::Windows::ApplicationModel::PackageUpdatingEventArgs) 83 { 84 this->StartAppShutdown(); 85 }); 86 } 87 88 // Create message only window. 89 m_messageQueueReady.create(); 90 m_windowThread = std::thread(&TerminationSignalHandler::CreateWindowAndStartMessageLoop, this); 91 if (!m_messageQueueReady.wait(100)) 92 { 93 AICLI_LOG(CLI, Warning, << "Timeout creating winget window"); 94 } 95 96 // Set up ctrl-c handler. 97 LOG_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler(StaticCtrlHandlerFunction, TRUE)); 98 } 99 100 TerminationSignalHandler::~TerminationSignalHandler() 101 { 102 // std::thread requires that any managed thread (joinable) be joined or detached before destructing 103 if (m_windowThread.joinable()) 104 { 105 m_windowThread.detach(); 106 } 107 } 108 109 void TerminationSignalHandler::StartAppShutdown() 110 { 111 AICLI_LOG(CLI, Info, << "Initiating shutdown procedure"); 112 113 #ifndef AICLI_DISABLE_TEST_HOOKS 114 m_appShutdownEvent.SetEvent(); 115 #endif 116 117 // Lifetime manager sends CTRL-C after the WM_QUERYENDSESSION is processed. 118 // If we disable the CTRL-C handler, the default handler will kill us. 119 InformListeners(CancelReason::AppShutdown, true); 120 } 121 122 BOOL WINAPI TerminationSignalHandler::StaticCtrlHandlerFunction(DWORD ctrlType) 123 { 124 return Instance()->CtrlHandlerFunction(ctrlType); 125 } 126 127 LRESULT WINAPI TerminationSignalHandler::WindowMessageProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 128 { 129 switch (uMsg) 130 { 131 case WM_QUERYENDSESSION: 132 AICLI_LOG(CLI, Verbose, << "Received WM_QUERYENDSESSION"); 133 Instance()->StartAppShutdown(); 134 return TRUE; 135 case WM_ENDSESSION: 136 case WM_CLOSE: 137 AICLI_LOG(CLI, Verbose, << "Received window message type: " << uMsg); 138 // We delay as long as needed during the WM_ENDSESSION as we will be terminated on return. 139 ServerShutdownSynchronization::WaitForShutdown(); 140 DestroyWindow(hWnd); 141 break; 142 case WM_DESTROY: 143 PostQuitMessage(0); 144 break; 145 default: 146 return DefWindowProc(hWnd, uMsg, wParam, lParam); 147 } 148 return FALSE; 149 } 150 151 BOOL TerminationSignalHandler::CtrlHandlerFunction(DWORD ctrlType) 152 { 153 // TODO: Move this to be logged per active context when we have thread static globals 154 AICLI_LOG(CLI, Info, << "Got CTRL type: " << ctrlType); 155 156 switch (ctrlType) 157 { 158 case CTRL_C_EVENT: 159 case CTRL_BREAK_EVENT: 160 return InformListeners(CancelReason::CtrlCSignal, false); 161 // According to MSDN, we should never receive these due to having gdi32/user32 loaded in our process. 162 // But handle them as a force terminate anyway. 163 case CTRL_CLOSE_EVENT: 164 case CTRL_LOGOFF_EVENT: 165 case CTRL_SHUTDOWN_EVENT: 166 return InformListeners(CancelReason::CtrlCSignal, true); 167 default: 168 return FALSE; 169 } 170 } 171 172 // Terminates the currently attached contexts. 173 // Returns FALSE if no contexts attached; TRUE otherwise. 174 BOOL TerminationSignalHandler::InformListeners(CancelReason reason, bool force) 175 { 176 std::lock_guard<std::mutex> lock{ m_listenersLock }; 177 178 if (m_listeners.empty()) 179 { 180 return FALSE; 181 } 182 183 for (auto& listener : m_listeners) 184 { 185 listener->Cancel(reason, force); 186 } 187 188 return TRUE; 189 } 190 191 void TerminationSignalHandler::CreateWindowAndStartMessageLoop() 192 { 193 PCWSTR windowClass = L"wingetWindow"; 194 HINSTANCE hInstance = GetModuleHandle(NULL); 195 if (hInstance == NULL) 196 { 197 LOG_LAST_ERROR_MSG("Failed getting module handle"); 198 return; 199 } 200 201 WNDCLASSEX wcex = {}; 202 wcex.cbSize = sizeof(wcex); 203 204 wcex.style = CS_NOCLOSE; 205 wcex.lpfnWndProc = TerminationSignalHandler::WindowMessageProcedure; 206 wcex.cbClsExtra = 0; 207 wcex.cbWndExtra = 0; 208 wcex.hInstance = hInstance; 209 wcex.lpszClassName = windowClass; 210 211 if (!RegisterClassEx(&wcex)) 212 { 213 LOG_LAST_ERROR_MSG("Failed registering window class"); 214 return; 215 } 216 217 m_windowHandle = wil::unique_hwnd(CreateWindow( 218 windowClass, 219 L"WingetMessageOnlyWindow", 220 WS_OVERLAPPEDWINDOW, 221 0, /* x */ 222 0, /* y */ 223 0, /* nWidth */ 224 0, /* nHeight */ 225 NULL, /* hWndParent */ 226 NULL, /* hMenu */ 227 hInstance, 228 NULL)); /* lpParam */ 229 230 if (m_windowHandle == nullptr) 231 { 232 LOG_LAST_ERROR_MSG("Failed creating window"); 233 return; 234 } 235 236 ShowWindow(m_windowHandle.get(), SW_HIDE); 237 238 // Force message queue to be created. 239 MSG msg; 240 PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); 241 m_messageQueueReady.SetEvent(); 242 243 // Message loop 244 BOOL getMessageResult; 245 while ((getMessageResult = GetMessage(&msg, m_windowHandle.get(), 0, 0)) != 0) 246 { 247 if (getMessageResult == -1) 248 { 249 LOG_LAST_ERROR(); 250 } 251 else 252 { 253 DispatchMessage(&msg); 254 } 255 } 256 } 257 258 void ServerShutdownSynchronization::Initialize(ShutdownCompleteCallback callback) 259 { 260 Instance().m_callback = callback; 261 } 262 263 void ServerShutdownSynchronization::AddComponent(const ComponentSystem& component) 264 { 265 ServerShutdownSynchronization& instance = Instance(); 266 std::lock_guard<std::mutex> lock{ instance.m_componentsLock }; 267 268 for (const auto& item : instance.m_components) 269 { 270 if (item.BlockNewWork == component.BlockNewWork || 271 item.BeginShutdown == component.BeginShutdown || 272 item.Wait == component.Wait) 273 { 274 return; 275 } 276 } 277 278 instance.m_components.push_back(component); 279 } 280 281 void ServerShutdownSynchronization::WaitForShutdown() 282 { 283 ServerShutdownSynchronization& instance = Instance(); 284 285 { 286 std::lock_guard<std::mutex> lock{ instance.m_threadLock }; 287 if (!instance.m_shutdownThread.joinable()) 288 { 289 AICLI_LOG(Core, Warning, << "Attempt to wait for shutdown when shutdown has not been initiated."); 290 return; 291 } 292 } 293 294 instance.m_shutdownComplete.wait(); 295 } 296 297 void ServerShutdownSynchronization::Cancel(CancelReason reason, bool) 298 { 299 std::lock_guard<std::mutex> lock{ m_threadLock }; 300 301 if (!m_shutdownThread.joinable()) 302 { 303 m_shutdownThread = std::thread(&ServerShutdownSynchronization::SynchronizeShutdown, this, reason); 304 } 305 } 306 307 ServerShutdownSynchronization::ServerShutdownSynchronization() 308 { 309 TerminationSignalHandler::Instance()->AddListener(this); 310 } 311 312 ServerShutdownSynchronization::~ServerShutdownSynchronization() 313 { 314 TerminationSignalHandler::Instance()->RemoveListener(this); 315 if (m_shutdownThread.joinable()) 316 { 317 m_shutdownThread.detach(); 318 } 319 } 320 321 ServerShutdownSynchronization& ServerShutdownSynchronization::Instance() 322 { 323 static ServerShutdownSynchronization s_instance; 324 return s_instance; 325 } 326 327 void ServerShutdownSynchronization::SynchronizeShutdown(CancelReason reason) try 328 { 329 auto setShutdownComplete = wil::scope_exit([this]() { this->m_shutdownComplete.SetEvent(); }); 330 331 std::vector<ComponentSystem> components; 332 { 333 std::lock_guard<std::mutex> lock{ m_componentsLock }; 334 components = m_components; 335 } 336 337 for (const auto& component : components) 338 { 339 if (component.BlockNewWork) 340 { 341 component.BlockNewWork(reason); 342 } 343 } 344 345 for (const auto& component : components) 346 { 347 if (component.BeginShutdown) 348 { 349 component.BeginShutdown(reason); 350 } 351 } 352 353 for (const auto& component : components) 354 { 355 if (component.Wait) 356 { 357 component.Wait(); 358 } 359 } 360 361 ShutdownCompleteCallback callback = m_callback; 362 if (callback) 363 { 364 callback(); 365 } 366 } 367 CATCH_LOG(); 368 }