forked from microsoft/winget-pkgs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create cross process reader writer lock for source use (microsoft#41)
- Loading branch information
Showing
13 changed files
with
243 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
#include "pch.h" | ||
#include "TestCommon.h" | ||
|
||
#include <AppInstallerSynchronization.h> | ||
|
||
using namespace AppInstaller::Synchronization; | ||
|
||
TEST_CASE("CPRWL_MultipleReaders", "[CrossProcessReaderWriteLock]") | ||
{ | ||
std::string name = "AppInstCPRWLTests"; | ||
|
||
wil::unique_event signal; | ||
signal.create(); | ||
|
||
CrossProcessReaderWriteLock mainThreadLock = CrossProcessReaderWriteLock::LockForRead(name); | ||
|
||
std::thread otherThread([&name, &signal]() { | ||
CrossProcessReaderWriteLock otherThreadLock = CrossProcessReaderWriteLock::LockForRead(name); | ||
signal.SetEvent(); | ||
}); | ||
// In the event of bugs, we don't want to block the test waiting forever | ||
otherThread.detach(); | ||
|
||
// Wait up to a second for the other thread to do one thing... | ||
REQUIRE(signal.wait(1000)); | ||
} | ||
|
||
TEST_CASE("CPRWL_WriterBlocksReader", "[CrossProcessReaderWriteLock]") | ||
{ | ||
std::string name = "AppInstCPRWLTests"; | ||
|
||
wil::unique_event signal; | ||
signal.create(); | ||
|
||
{ | ||
CrossProcessReaderWriteLock mainThreadLock = CrossProcessReaderWriteLock::LockForWrite(name); | ||
|
||
std::thread otherThread([&name, &signal]() { | ||
CrossProcessReaderWriteLock otherThreadLock = CrossProcessReaderWriteLock::LockForRead(name); | ||
signal.SetEvent(); | ||
}); | ||
// In the event of bugs, we don't want to block the test waiting forever | ||
otherThread.detach(); | ||
|
||
REQUIRE(!signal.wait(1000)); | ||
} | ||
|
||
// Upon release of the writer, the other thread should signal | ||
REQUIRE(signal.wait(1000)); | ||
} | ||
|
||
TEST_CASE("CPRWL_ReaderBlocksWriter", "[CrossProcessReaderWriteLock]") | ||
{ | ||
std::string name = "AppInstCPRWLTests"; | ||
|
||
wil::unique_event signal; | ||
signal.create(); | ||
|
||
{ | ||
CrossProcessReaderWriteLock mainThreadLock = CrossProcessReaderWriteLock::LockForRead(name); | ||
|
||
std::thread otherThread([&name, &signal]() { | ||
CrossProcessReaderWriteLock otherThreadLock = CrossProcessReaderWriteLock::LockForWrite(name); | ||
signal.SetEvent(); | ||
}); | ||
// In the event of bugs, we don't want to block the test waiting forever | ||
otherThread.detach(); | ||
|
||
REQUIRE(!signal.wait(1000)); | ||
} | ||
|
||
// Upon release of the writer, the other thread should signal | ||
REQUIRE(signal.wait(1000)); | ||
} | ||
|
||
TEST_CASE("CPRWL_WriterBlocksWriter", "[CrossProcessReaderWriteLock]") | ||
{ | ||
std::string name = "AppInstCPRWLTests"; | ||
|
||
wil::unique_event signal; | ||
signal.create(); | ||
|
||
{ | ||
CrossProcessReaderWriteLock mainThreadLock = CrossProcessReaderWriteLock::LockForWrite(name); | ||
|
||
std::thread otherThread([&name, &signal]() { | ||
CrossProcessReaderWriteLock otherThreadLock = CrossProcessReaderWriteLock::LockForWrite(name); | ||
signal.SetEvent(); | ||
}); | ||
// In the event of bugs, we don't want to block the test waiting forever | ||
otherThread.detach(); | ||
|
||
REQUIRE(!signal.wait(1000)); | ||
} | ||
|
||
// Upon release of the writer, the other thread should signal | ||
REQUIRE(signal.wait(1000)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
#pragma once | ||
|
||
#include <vector> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/AppInstallerCommonCore/Public/AppInstallerSynchronization.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
#pragma once | ||
#include <AppInstallerLanguageUtilities.h> | ||
#include <wil/resource.h> | ||
|
||
#include <string_view> | ||
|
||
|
||
namespace AppInstaller::Synchronization | ||
{ | ||
// A fairly simple cross process (same session) reader-writer lock. | ||
// The primary purpose is for sources to control access to their backing stores. | ||
// Due to this design goal, these limitations exist: | ||
// - Starves readers when a writer comes in. | ||
// - Readers are limited to an arbitrarily chosen limit. | ||
// - Not re-entrant (although repeated read locking will work, it will consume additional slots). | ||
// - No upgrade from reader to writer. | ||
struct CrossProcessReaderWriteLock | ||
{ | ||
~CrossProcessReaderWriteLock(); | ||
|
||
CrossProcessReaderWriteLock(const CrossProcessReaderWriteLock&) = delete; | ||
CrossProcessReaderWriteLock& operator=(const CrossProcessReaderWriteLock&) = delete; | ||
|
||
CrossProcessReaderWriteLock(CrossProcessReaderWriteLock&&) = default; | ||
CrossProcessReaderWriteLock& operator=(CrossProcessReaderWriteLock&&) = default; | ||
|
||
static CrossProcessReaderWriteLock LockForRead(std::string_view name); | ||
|
||
static CrossProcessReaderWriteLock LockForWrite(std::string_view name); | ||
|
||
private: | ||
CrossProcessReaderWriteLock(std::string_view name); | ||
|
||
wil::unique_mutex m_mutex; | ||
wil::unique_semaphore m_semaphore; | ||
ResetWhenMovedFrom<LONG> m_semaphoreReleases{ 0 }; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
#pragma once | ||
#include "pch.h" | ||
#include <AppInstallerSynchronization.h> | ||
#include <AppInstallerStrings.h> | ||
|
||
|
||
namespace AppInstaller::Synchronization | ||
{ | ||
using namespace std::string_view_literals; | ||
|
||
constexpr std::wstring_view s_CrossProcessReaderWriteLock_MutexSuffix = L".mutex"sv; | ||
constexpr std::wstring_view s_CrossProcessReaderWriteLock_SemaphoreSuffix = L".sem"sv; | ||
|
||
// Arbitrary limit that should not ever cause a problem (theoretically 1 per process) | ||
constexpr LONG s_CrossProcessReaderWriteLock_MaxReaders = 16; | ||
|
||
CrossProcessReaderWriteLock::~CrossProcessReaderWriteLock() | ||
{ | ||
for (LONG i = 0; i < m_semaphoreReleases; ++i) | ||
{ | ||
m_semaphore.ReleaseSemaphore(); | ||
} | ||
} | ||
|
||
CrossProcessReaderWriteLock CrossProcessReaderWriteLock::LockForRead(std::string_view name) | ||
{ | ||
CrossProcessReaderWriteLock result(name); | ||
|
||
DWORD status = 0; | ||
auto lock = result.m_mutex.acquire(&status); | ||
THROW_HR_IF(E_UNEXPECTED, status != WAIT_OBJECT_0); | ||
|
||
// We are taking ownership of releasing this in the destructor | ||
status = ::WaitForSingleObjectEx(result.m_semaphore.get(), INFINITE, FALSE); | ||
THROW_HR_IF(E_UNEXPECTED, status != WAIT_OBJECT_0); | ||
|
||
result.m_semaphoreReleases = 1; | ||
return result; | ||
} | ||
|
||
CrossProcessReaderWriteLock CrossProcessReaderWriteLock::LockForWrite(std::string_view name) | ||
{ | ||
CrossProcessReaderWriteLock result(name); | ||
|
||
DWORD status = 0; | ||
auto lock = result.m_mutex.acquire(&status); | ||
THROW_HR_IF(E_UNEXPECTED, status != WAIT_OBJECT_0); | ||
|
||
for (LONG i = 0; i < s_CrossProcessReaderWriteLock_MaxReaders; ++i) | ||
{ | ||
// We are taking ownership of releasing these in the destructor | ||
status = ::WaitForSingleObjectEx(result.m_semaphore.get(), INFINITE, FALSE); | ||
THROW_HR_IF(E_UNEXPECTED, status != WAIT_OBJECT_0); | ||
result.m_semaphoreReleases = i + 1; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
CrossProcessReaderWriteLock::CrossProcessReaderWriteLock(std::string_view name) | ||
{ | ||
THROW_HR_IF(E_INVALIDARG, name.find('\\') != std::string::npos); | ||
|
||
std::wstring mutexName = Utility::ConvertToUTF16(name); | ||
std::wstring semName = mutexName; | ||
|
||
mutexName += s_CrossProcessReaderWriteLock_MutexSuffix; | ||
semName += s_CrossProcessReaderWriteLock_SemaphoreSuffix; | ||
|
||
m_mutex.create(mutexName.c_str(), 0, SYNCHRONIZE); | ||
m_semaphore.create(s_CrossProcessReaderWriteLock_MaxReaders, s_CrossProcessReaderWriteLock_MaxReaders, semName.c_str(), SYNCHRONIZE | SEMAPHORE_MODIFY_STATE); | ||
} | ||
} |