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 }