winget-cli

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

commit 39034e1e0bff692c40e964c1f1d5b177edef693b
parent 7d178a61e732e820bb562269ce0b59c982dcf905
Author: Chacón <lechacon@users.noreply.github.com>
Date:   Tue, 16 Nov 2021 18:57:58 -0800

Implement parallel downloads for COM scenarios (#1588)


Diffstat:
M.github/actions/spelling/allow.txt | 2+-
Msrc/AppInstallerCLICore/Commands/COMInstallCommand.h | 6++++--
Msrc/AppInstallerCLICore/ContextOrchestrator.cpp | 255++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/AppInstallerCLICore/ContextOrchestrator.h | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp | 13+++++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h | 1+
7 files changed, 281 insertions(+), 79 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -532,7 +532,7 @@ Testrun testsettingname TEXTFORMAT TEXTINCLUDE -there're +Threadpool Timeline todo tokenizer diff --git a/src/AppInstallerCLICore/Commands/COMInstallCommand.h b/src/AppInstallerCLICore/Commands/COMInstallCommand.h @@ -8,7 +8,8 @@ namespace AppInstaller::CLI // IMPORTANT: To use this command, the caller should have already retrieved the package manifest (GetManifest()) and added it to the Context Data struct COMDownloadCommand final : public Command { - COMDownloadCommand(std::string_view parent) : Command("download", parent) {} + constexpr static std::string_view CommandName = "download"sv; + COMDownloadCommand(std::string_view parent) : Command(CommandName, parent) {} protected: void ExecuteInternal(Execution::Context& context) const override; @@ -17,7 +18,8 @@ namespace AppInstaller::CLI // IMPORTANT: To use this command, the caller should have already retrieved the package manifest (GetManifest()) and added it to the Context Data struct COMInstallCommand final : public Command { - COMInstallCommand(std::string_view parent) : Command("install", parent) {} + constexpr static std::string_view CommandName = "install"sv; + COMInstallCommand(std::string_view parent) : Command(CommandName, parent) {} protected: void ExecuteInternal(Execution::Context& context) const override; diff --git a/src/AppInstallerCLICore/ContextOrchestrator.cpp b/src/AppInstallerCLICore/ContextOrchestrator.cpp @@ -10,6 +10,21 @@ namespace AppInstaller::CLI::Execution { + namespace + { + // Callback function used by worker threads in the queue. + // context must be a pointer to a queue item. + void CALLBACK OrchestratorQueueWorkCallback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WORK) + { + auto queueItem = reinterpret_cast<OrchestratorQueueItem*>(context); + auto queue = queueItem->GetCurrentQueue(); + if (queue) + { + queue->RunItem(queueItem->GetId()); + } + } + } + ContextOrchestrator& ContextOrchestrator::Instance() { static ContextOrchestrator s_instance; @@ -21,90 +36,195 @@ namespace AppInstaller::CLI::Execution ProgressCallback progress; m_installingWriteableSource = Repository::Source(Repository::PredefinedSource::Installing); m_installingWriteableSource.Open(progress); + + // Decide how many threads to use for each command. + // We always allow only one install at a time. + // For download, if we can find the number of supported concurrent threads, + // use that as the maximum (up to 3); otherwise use a single thread. + const auto supportedConcurrentThreads = std::thread::hardware_concurrency(); + const UINT32 maxDownloadThreads = 3; + const UINT32 installThreads = 1; + const UINT32 downloadThreads = std::min(supportedConcurrentThreads ? supportedConcurrentThreads - 1 : 1, maxDownloadThreads); + + AddCommandQueue(COMDownloadCommand::CommandName, downloadThreads); + AddCommandQueue(COMInstallCommand::CommandName, installThreads); } - _Requires_lock_held_(m_queueLock) - std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator ContextOrchestrator::FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId) + void ContextOrchestrator::AddCommandQueue(std::string_view commandName, UINT32 allowedThreads) { - return std::find_if(m_queueItems.begin(), m_queueItems.end(), [&comparisonQueueItemId](const std::shared_ptr<OrchestratorQueueItem>& item) {return (item->GetId().IsSame(comparisonQueueItemId)); }); - + m_commandQueues.emplace(commandName, std::make_unique<OrchestratorQueue>(commandName, allowedThreads)); } + _Requires_lock_held_(m_queueLock) std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) { - auto itr = FindIteratorById(comparisonQueueItemId); - if (itr != m_queueItems.end()) + for (const auto& queue : m_commandQueues) { - return *itr; + auto item = queue.second->FindById(comparisonQueueItemId); + if (item) + { + return item; + } } + return {}; } - - void ContextOrchestrator::EnqueueItem(std::shared_ptr<OrchestratorQueueItem> item) + + void ContextOrchestrator::EnqueueAndRunItem(std::shared_ptr<OrchestratorQueueItem> item) { - { - std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + if (item->IsOnFirstCommand()) + { THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING), FindById(item->GetId())); - m_queueItems.push_back(item); } - // Add the package to the Installing source so that it can be queried using the Source interface. - const auto& manifest = item->GetContext().Get<Execution::Data::Manifest>(); - m_installingWriteableSource.AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + m_commandQueues.at(std::string(item->GetNextCommand().Name()))->EnqueueAndRunItem(item); + } + void ContextOrchestrator::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state) + { + std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + for (const auto& queue : m_commandQueues) { - std::lock_guard<std::mutex> lockQueue{ m_queueLock }; - item->SetState(OrchestratorQueueItemState::Queued); + if (queue.second->RemoveItemInState(item, state, true)) + { + return; + } } } - void ContextOrchestrator::RequeueItem(OrchestratorQueueItem& item) + void ContextOrchestrator::CancelQueueItem(const OrchestratorQueueItem& item) { - std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + // Always cancel the item, even if it isn't running yet, to get the terminationHR set correctly. + item.GetContext().Cancel(false, true); - item.SetState(OrchestratorQueueItemState::Queued); + RemoveItemInState(item, OrchestratorQueueItemState::Queued); } - void ContextOrchestrator::EnqueueAndRunItem(std::shared_ptr<OrchestratorQueueItem> item) + std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::GetQueueItem(const OrchestratorQueueItemId& queueItemId) { - EnqueueItem(item); + std::lock_guard<std::mutex> lock{ m_queueLock }; - std::thread runnerThread(&ContextOrchestrator::RunItems, this); - runnerThread.detach(); + return FindById(queueItemId); } - std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::GetNextItem() + void ContextOrchestrator::AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem) { - std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + const auto& manifest = queueItem.GetContext().Get<Execution::Data::Manifest>(); + m_installingWriteableSource.AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + } + + void ContextOrchestrator::RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem) + { + const auto& manifest = queueItem.GetContext().Get<Execution::Data::Manifest>(); + m_installingWriteableSource.RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + } - if (m_queueItems.empty()) + _Requires_lock_held_(m_queueLock) + std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator OrchestratorQueue::FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + return std::find_if(m_queueItems.begin(), m_queueItems.end(), [&comparisonQueueItemId](const std::shared_ptr<OrchestratorQueueItem>& item) {return (item->GetId().IsSame(comparisonQueueItemId)); }); + } + + _Requires_lock_held_(m_queueLock) + std::shared_ptr<OrchestratorQueueItem> OrchestratorQueue::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + auto itr = FindIteratorById(comparisonQueueItemId); + if (itr != m_queueItems.end()) { - return {}; + return *itr; } - std::shared_ptr<OrchestratorQueueItem> item = m_queueItems.front(); + return {}; + } + + void OrchestratorQueue::EnqueueItem(std::shared_ptr<OrchestratorQueueItem> item) + { + { + std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + m_queueItems.push_back(item); + } - // Check if item can be dequeued. - // Since only one item can be installed at a time currently the logic is very simple, - // and can just check if the first item is ready to run. This logic will need to become - // more complicated if multiple operation types (e.g. Download & Install) are added that can - // run simultaneously. - if (item->GetState() != OrchestratorQueueItemState::Queued) + // Add the package to the Installing source so that it can be queried using the Source interface. + // Only do this the first time the item is queued. + if (item->IsOnFirstCommand()) { - return {}; + ContextOrchestrator::Instance().AddItemManifestToInstallingSource(*item); } - // Running state must be set inside the queueLock so that multiple threads don't try to run the same item. - item->SetState(OrchestratorQueueItemState::Running); - return item; + { + std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + item->SetState(OrchestratorQueueItemState::Queued); + } + } + + OrchestratorQueue::OrchestratorQueue(std::string_view commandName, UINT32 allowedThreads) : + m_commandName(commandName), m_allowedThreads(allowedThreads) + { + m_threadPool.reset(CreateThreadpool(nullptr)); + THROW_LAST_ERROR_IF_NULL(m_threadPool); + m_threadPoolCleanupGroup.reset(CreateThreadpoolCleanupGroup()); + THROW_LAST_ERROR_IF_NULL(m_threadPoolCleanupGroup); + InitializeThreadpoolEnvironment(&m_threadPoolCallbackEnviron); + SetThreadpoolCallbackPool(&m_threadPoolCallbackEnviron, m_threadPool.get()); + SetThreadpoolCallbackCleanupGroup(&m_threadPoolCallbackEnviron, m_threadPoolCleanupGroup.get(), nullptr); + + THROW_LAST_ERROR_IF(!SetThreadpoolThreadMinimum(m_threadPool.get(), 1)); + SetThreadpoolThreadMaximum(m_threadPool.get(), m_allowedThreads); + } + + OrchestratorQueue::~OrchestratorQueue() + { + CloseThreadpoolCleanupGroupMembers(m_threadPoolCleanupGroup.get(), false, nullptr); + } + + void OrchestratorQueue::EnqueueAndRunItem(std::shared_ptr<OrchestratorQueueItem> item) + { + EnqueueItem(item); + + item->SetCurrentQueue(this); + auto work = CreateThreadpoolWork(OrchestratorQueueWorkCallback, item.get(), &m_threadPoolCallbackEnviron); + SubmitThreadpoolWork(work); } - void ContextOrchestrator::RunItems() + void OrchestratorQueue::RunItem(const OrchestratorQueueItemId& itemId) { - std::shared_ptr<OrchestratorQueueItem> item = GetNextItem(); - while(item != nullptr) + try { + std::shared_ptr<OrchestratorQueueItem> item; + bool isCancelled = false; + + // Try to find the item in the queue. + { + std::lock_guard<std::mutex> lockQueue{ m_queueLock }; + item = FindById(itemId); + + if (!item) + { + // Item should be in the queue; this shouldn't happen. + return; + } + + // Only run if the item is queued and not cancelled. + if (item->GetState() == OrchestratorQueueItemState::Queued) + { + // Mark it as running so that it cannot be cancelled by other threads. + item->SetState(OrchestratorQueueItemState::Running); + } + else if (item->GetState() == OrchestratorQueueItemState::Cancelled) + { + isCancelled = true; + } + } + + if (isCancelled) + { + // Do this separate from above block as the Remove function needs to manage the lock. + RemoveItemInState(*item, OrchestratorQueueItemState::Cancelled, true); + } + + // Get the item's command and execute it. HRESULT terminationHR = S_OK; try { @@ -133,21 +253,24 @@ namespace AppInstaller::CLI::Execution if (FAILED(terminationHR) || item->IsComplete()) { - RemoveItemInState(*item, OrchestratorQueueItemState::Running); + RemoveItemInState(*item, OrchestratorQueueItemState::Running, true); } else { - RequeueItem(*item); + // Remove item from this queue and add it to the queue for the next command. + RemoveItemInState(*item, OrchestratorQueueItemState::Running, false); + ContextOrchestrator::Instance().EnqueueAndRunItem(item); } - - item = GetNextItem(); + } + catch (...) + { } } - void ContextOrchestrator::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state) + bool OrchestratorQueue::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove) { // OrchestratorQueueItemState::Running items should only be removed by the thread that ran the item. - // Queued items can be removed by any thread. + // Queued items can be removed by any thread. // NotQueued items should not be removed since, if found in the queue, they are in the process of being queued by another thread. bool foundItem = false; @@ -159,32 +282,29 @@ namespace AppInstaller::CLI::Execution if (itr != m_queueItems.end() && (*itr)->GetState() == state) { foundItem = true; - m_queueItems.erase(itr); + + // The item must only be removed from the queue by the thread that runs + // it, because the callback uses it. If any other thread tries to remove + // it, we simply mark it as cancelled. + if (state == OrchestratorQueueItemState::Running || state == OrchestratorQueueItemState::Cancelled) + { + (*itr)->SetCurrentQueue(nullptr); + m_queueItems.erase(itr); + } + else if (state == OrchestratorQueueItemState::Queued) + { + (*itr)->SetState(OrchestratorQueueItemState::Cancelled); + } } } - if (foundItem) + if (foundItem && isGlobalRemove) { - const auto& manifest = item.GetContext().Get<Execution::Data::Manifest>(); - m_installingWriteableSource.RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); - + ContextOrchestrator::Instance().RemoveItemManifestFromInstallingSource(item); item.GetCompletedEvent().SetEvent(); } - } - void ContextOrchestrator::CancelQueueItem(const OrchestratorQueueItem& item) - { - // Always cancel the item, even if it isn't running yet, to get the terminationHR set correctly. - item.GetContext().Cancel(false, true); - - RemoveItemInState(item, OrchestratorQueueItemState::Queued); - } - - std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::GetQueueItem(const OrchestratorQueueItemId& queueItemId) - { - std::lock_guard<std::mutex> lock{ m_queueLock }; - - return FindById(queueItemId); + return foundItem; } bool OrchestratorQueueItemId::IsSame(const OrchestratorQueueItemId& comparedId) const @@ -200,5 +320,4 @@ namespace AppInstaller::CLI::Execution item->AddCommand(std::make_unique<::AppInstaller::CLI::COMInstallCommand>(RootCommand::CommandName)); return item; } - } diff --git a/src/AppInstallerCLICore/ContextOrchestrator.h b/src/AppInstallerCLICore/ContextOrchestrator.h @@ -16,9 +16,14 @@ namespace AppInstaller::CLI::Execution { enum class OrchestratorQueueItemState { + // Created but not yet queued NotQueued, + // Queued and waiting to be run Queued, - Running + // Running in the thread pool + Running, + // Cancelled before it was run; will be deleted when we try to run it + Cancelled }; struct OrchestratorQueueItemId @@ -33,29 +38,43 @@ namespace AppInstaller::CLI::Execution std::wstring m_sourceId; }; + struct OrchestratorQueue; + struct OrchestratorQueueItem { OrchestratorQueueItem(OrchestratorQueueItemId id, std::unique_ptr<COMContext> context) : m_id(std::move(id)), m_context(std::move(context)) {} OrchestratorQueueItemState GetState() const { return m_state; } void SetState(OrchestratorQueueItemState state) { m_state = state; } + + OrchestratorQueue* GetCurrentQueue() const { return m_currentQueue; } + void SetCurrentQueue(OrchestratorQueue* currentQueue) { m_currentQueue = currentQueue; } + COMContext& GetContext() const { return *m_context; } const wil::unique_event& GetCompletedEvent() const { return m_completedEvent; } const OrchestratorQueueItemId& GetId() const { return m_id; } + void AddCommand(std::unique_ptr<Command> command) { m_commands.push_back(std::move(command)); } + const Command& GetNextCommand() const { return *m_commands.front(); } std::unique_ptr<Command> PopNextCommand() { + m_isOnFirstCommand = false; std::unique_ptr<Command> command = std::move(m_commands.front()); m_commands.pop_front(); return command; } + + bool IsOnFirstCommand() const { return m_isOnFirstCommand; } bool IsComplete() const { return m_commands.empty(); } + private: OrchestratorQueueItemState m_state = OrchestratorQueueItemState::NotQueued; std::unique_ptr<COMContext> m_context; wil::unique_event m_completedEvent{ wil::EventOptions::ManualReset }; OrchestratorQueueItemId m_id; std::deque<std::unique_ptr<Command>> m_commands; + bool m_isOnFirstCommand = true; + OrchestratorQueue* m_currentQueue = nullptr; }; struct OrchestratorQueueItemFactory @@ -73,20 +92,68 @@ namespace AppInstaller::CLI::Execution std::shared_ptr<OrchestratorQueueItem> GetQueueItem(const OrchestratorQueueItemId& queueItemId); + void AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem); + void RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem); + private: std::mutex m_queueLock; - void RunItems(); - std::shared_ptr<OrchestratorQueueItem> GetNextItem(); - void EnqueueItem(std::shared_ptr<OrchestratorQueueItem> item); - void RequeueItem(OrchestratorQueueItem& item); + void AddCommandQueue(std::string_view commandName, UINT32 allowedThreads); void RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state); _Requires_lock_held_(m_queueLock) - std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator FindIteratorById(const OrchestratorQueueItemId& queueItemId); - _Requires_lock_held_(m_queueLock) std::shared_ptr<OrchestratorQueueItem> FindById(const OrchestratorQueueItemId& queueItemId); Repository::Source m_installingWriteableSource; + std::map<std::string, std::unique_ptr<OrchestratorQueue>> m_commandQueues; + }; + + // One of the queues used by the orchestrator. + // All items in the queue execute the same command. + // The queue allows multiple items to run at the same time, up to a limit. + struct OrchestratorQueue + { + OrchestratorQueue(std::string_view commandName, UINT32 allowedThreads); + ~OrchestratorQueue(); + + // Name of the command this queue can execute + std::string_view CommandName() const { return m_commandName; } + + // Enqueues an item to be run when there are threads available. + void EnqueueAndRunItem(std::shared_ptr<OrchestratorQueueItem> item); + + // Removes an item by id, provided that it is in the given state. + // Returns true if an item was removed. + // The item can be removed globally from the orchestrator, or from just this queue. + bool RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove); + + // Finds an item by id, if it is in the queue. + _Requires_lock_held_(m_queueLock) + std::shared_ptr<OrchestratorQueueItem> FindById(const OrchestratorQueueItemId& queueItemId); + + // Runs a single item from the queue. + void RunItem(const OrchestratorQueueItemId& itemId); + + private: + // Enqueues an item. + void EnqueueItem(std::shared_ptr<OrchestratorQueueItem> item); + + _Requires_lock_held_(m_queueLock) + std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId); + + std::string_view m_commandName; + + // Number of threads allowed to run items in this queue. + const UINT32 m_allowedThreads; + + // Thread pool for this queue, and associated objects. + // All work items will be added to the callback environment, and the cleanup group + // will manage their closing. + // See https://docs.microsoft.com/windows/win32/procthread/using-the-thread-pool-functions + TP_CALLBACK_ENVIRON m_threadPoolCallbackEnviron; + wil::unique_any<PTP_POOL, decltype(CloseThreadpool), CloseThreadpool> m_threadPool; + wil::unique_any<PTP_CLEANUP_GROUP, decltype(CloseThreadpoolCleanupGroup), CloseThreadpoolCleanupGroup> m_threadPoolCleanupGroup; + + std::mutex m_queueLock; std::deque<std::shared_ptr<OrchestratorQueueItem>> m_queueItems; }; } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -1039,7 +1039,7 @@ namespace AppInstaller::CLI::Workflow // If we cannot find a package using PackageFamilyName or ProductId, try manifest Id and Name pair SearchRequest searchRequest; searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id)); - // In case there're same Ids from different sources, filter the result using package name + // In case there are same Ids from different sources, filter the result using package name searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>())); context.Add<Execution::Data::SearchResult>(source.Search(searchRequest)); diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp @@ -151,6 +151,7 @@ namespace AppInstaller::Repository::Microsoft SQLiteIndex::IdType SQLiteIndex::AddManifestInternal(const Manifest::Manifest& manifest, const std::optional<std::filesystem::path>& relativePath) { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Verbose, << "Adding manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_addmanifest"); @@ -184,6 +185,7 @@ namespace AppInstaller::Repository::Microsoft bool SQLiteIndex::UpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional<std::filesystem::path>& relativePath) { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Verbose, << "Updating manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_updatemanifest"); @@ -216,6 +218,7 @@ namespace AppInstaller::Repository::Microsoft void SQLiteIndex::RemoveManifest(const Manifest::Manifest& manifest) { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_removemanifest"); m_interface->RemoveManifest(m_dbconn, manifest); @@ -238,6 +241,7 @@ namespace AppInstaller::Repository::Microsoft void SQLiteIndex::PrepareForPackaging() { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Info, << "Preparing index for packaging"); m_interface->PrepareForPackaging(m_dbconn); @@ -245,6 +249,7 @@ namespace AppInstaller::Repository::Microsoft bool SQLiteIndex::CheckConsistency(bool log) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Info, << "Checking index consistency..."); bool result = m_interface->CheckConsistency(m_dbconn, log); @@ -256,6 +261,7 @@ namespace AppInstaller::Repository::Microsoft Schema::ISQLiteIndex::SearchResult SQLiteIndex::Search(const SearchRequest& request) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; AICLI_LOG(Repo, Verbose, << "Performing search: " << request.ToString()); return m_interface->Search(m_dbconn, request); @@ -263,16 +269,19 @@ namespace AppInstaller::Repository::Microsoft std::optional<std::string> SQLiteIndex::GetPropertyByManifestId(IdType manifestId, PackageVersionProperty property) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; return m_interface->GetPropertyByManifestId(m_dbconn, manifestId, property); } std::vector<std::string> SQLiteIndex::GetMultiPropertyByManifestId(IdType manifestId, PackageVersionMultiProperty property) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; return m_interface->GetMultiPropertyByManifestId(m_dbconn, manifestId, property); } std::optional<SQLiteIndex::IdType> SQLiteIndex::GetManifestIdByKey(IdType id, std::string_view version, std::string_view channel) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; return m_interface->GetManifestIdByKey(m_dbconn, id, version, channel); } @@ -283,21 +292,25 @@ namespace AppInstaller::Repository::Microsoft std::vector<Utility::VersionAndChannel> SQLiteIndex::GetVersionKeysById(IdType id) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; return m_interface->GetVersionKeysById(m_dbconn, id); } SQLiteIndex::MetadataResult SQLiteIndex::GetMetadataByManifestId(SQLite::rowid_t manifestId) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; return m_interface->GetMetadataByManifestId(m_dbconn, manifestId); } void SQLiteIndex::SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value) { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; m_interface->SetMetadataByManifestId(m_dbconn, manifestId, metadata, value); } Utility::NormalizedName SQLiteIndex::NormalizeName(std::string_view name, std::string_view publisher) const { + std::lock_guard<std::mutex> lockInterface{ *m_interfaceLock }; return m_interface->NormalizeName(name, publisher); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h @@ -163,5 +163,6 @@ namespace AppInstaller::Repository::Microsoft SQLite::Connection m_dbconn; Schema::Version m_version; std::unique_ptr<Schema::ISQLiteIndex> m_interface; + std::unique_ptr<std::mutex> m_interfaceLock = std::make_unique<std::mutex>(); }; }