winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }