winget-cli

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

ConfigurationSequencer.cpp (6089B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ConfigurationSequencer.h"
      5 #include "ConfigurationStatus.h"
      6 #include <AppInstallerStrings.h>
      7 
      8 using namespace std::chrono_literals;
      9 
     10 namespace winrt::Microsoft::Management::Configuration::implementation
     11 {
     12     ConfigurationSequencer::ConfigurationSequencer(ConfigurationDatabase& database) : m_database(database) {}
     13 
     14     ConfigurationSequencer::~ConfigurationSequencer()
     15     {
     16         // Best effort attempt to remove our queue row
     17         try
     18         {
     19             m_database.RemoveQueueItem(m_queueItemObjectName);
     20 
     21             auto status = ConfigurationStatus::Instance();
     22             status->UpdateSetState(m_setInstanceIdentifier, false);
     23         }
     24         CATCH_LOG();
     25     }
     26 
     27     // This function creates necessary objects and records this operation into the table.
     28     // It then performs the equivalent of `Wait` with a timeout of 0.
     29     bool ConfigurationSequencer::Enqueue(const Configuration::ConfigurationSet& configurationSet)
     30     {
     31         m_setInstanceIdentifier = configurationSet.InstanceIdentifier();
     32 
     33         // Create an arbitrarily named object
     34         std::wstring objectName = L"WinGetConfigQueue_" + AppInstaller::Utility::CreateNewGuidNameWString();
     35         m_queueItemObjectName = AppInstaller::Utility::ConvertToUTF8(objectName);
     36         m_queueItemObject.create(wil::EventOptions::None, objectName.c_str());
     37 
     38         m_database.AddQueueItem(configurationSet, m_queueItemObjectName);
     39 
     40         auto statusInstance = ConfigurationStatus::Instance();
     41         statusInstance->UpdateSetState(m_setInstanceIdentifier, true);
     42 
     43         // Create shared mutex
     44         constexpr PCWSTR applyMutexName = L"WinGetConfigQueueApplyMutex";
     45 
     46         for (int i = 0; !m_applyMutex && i < 2; ++i)
     47         {
     48             if (!m_applyMutex.try_create(applyMutexName, 0, SYNCHRONIZE))
     49             {
     50                 m_applyMutex.try_open(applyMutexName, SYNCHRONIZE);
     51             }
     52         }
     53 
     54         THROW_LAST_ERROR_IF(!m_applyMutex);
     55 
     56         // Probe for an empty queue
     57         DWORD status = 0;
     58         m_applyMutexScope = m_applyMutex.acquire(&status, 0);
     59         THROW_LAST_ERROR_IF(status == WAIT_FAILED);
     60 
     61         if (status == WAIT_TIMEOUT)
     62         {
     63             return true;
     64         }
     65 
     66         if (GetQueuePosition() == 0)
     67         {
     68             m_database.SetActiveQueueItem(m_queueItemObjectName);
     69             return false;
     70         }
     71         else
     72         {
     73             m_applyMutexScope.reset();
     74             return true;
     75         }
     76     }
     77 
     78     // The configuration queue consists of a table in the shared database and cooperative handling of said table.
     79     // At any moment, the active processor must be holding a common named mutex.
     80     // Each active queue entry also holds their own arbitrarily named object, recorded in the table.
     81     // 
     82     // The general mechanism to wait is:
     83     //  1. Wait on common named mutex
     84     //  2. Check if first in queue, including probing arbitrary named objects of entries ahead of us
     85     //  3. If not first, wait for X * queue position, where X is sufficiently high to prevent contention on main mutex
     86     void ConfigurationSequencer::Wait(AppInstaller::WinRT::AsyncCancellation& cancellation)
     87     {
     88         THROW_HR_IF(E_NOT_VALID_STATE, !m_applyMutex);
     89 
     90         wil::unique_event cancellationEvent;
     91         cancellationEvent.create();
     92 
     93         HANDLE waitHandles[2];
     94         waitHandles[0] = cancellationEvent.get();
     95         waitHandles[1] = m_applyMutex.get();
     96 
     97         cancellation.Callback([&]() { cancellationEvent.SetEvent(); });
     98         auto clearCancelCallback = wil::scope_exit([&cancellation]() { cancellation.Callback([]() {}); });
     99 
    100         for (;;)
    101         {
    102             DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE);
    103             THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
    104 
    105             if (waitResult == WAIT_OBJECT_0)
    106             {
    107                 // Cancellation
    108                 break;
    109             }
    110             else if (waitResult == WAIT_OBJECT_0 + 1 || waitResult == WAIT_ABANDONED_0 + 1)
    111             {
    112                 // We now hold the apply mutex
    113                 wil::mutex_release_scope_exit applyMutexScope{ m_applyMutex.get() };
    114 
    115                 size_t queuePosition = GetQueuePosition();
    116                 if (queuePosition == 0)
    117                 {
    118                     m_applyMutexScope = std::move(applyMutexScope);
    119                     m_database.SetActiveQueueItem(m_queueItemObjectName);
    120                     break;
    121                 }
    122                 else
    123                 {
    124                     applyMutexScope.reset();
    125                     std::this_thread::sleep_for(queuePosition * 100ms);
    126                 }
    127             }
    128         }
    129     }
    130 
    131     size_t ConfigurationSequencer::GetQueuePosition()
    132     {
    133         auto queueItems = m_database.GetQueueItems();
    134 
    135         // If we get no queue items at all, we assume that the database doesn't support queueing.
    136         if (queueItems.empty())
    137         {
    138             return 0;
    139         }
    140 
    141         size_t result = 0;
    142         bool found = false;
    143 
    144         for (const auto& item : queueItems)
    145         {
    146             if (item.ObjectName == m_queueItemObjectName)
    147             {
    148                 found = true;
    149                 break;
    150             }
    151 
    152             std::wstring objectName = AppInstaller::Utility::ConvertToUTF16(item.ObjectName);
    153             QueueObjectType itemObject;
    154             if (itemObject.try_open(objectName.c_str(), SYNCHRONIZE))
    155             {
    156                 ++result;
    157             }
    158             else
    159             {
    160                 // Best effort attempt to remove the dead queue row
    161                 try
    162                 {
    163                     m_database.RemoveQueueItem(item.ObjectName);
    164                 }
    165                 CATCH_LOG();
    166             }
    167         }
    168 
    169         THROW_HR_IF(E_NOT_SET, !found);
    170 
    171         return result;
    172     }
    173 }