diff --git a/include/kf/ThreadPool.h b/include/kf/ThreadPool.h index ce64b88..3db6b2a 100644 --- a/include/kf/ThreadPool.h +++ b/include/kf/ThreadPool.h @@ -53,7 +53,7 @@ namespace kf } private: - Thread m_threads[kMaxCount]; + Thread m_threads[kMaxCount]{}; int m_count; }; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6dccd52..aa07440 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,6 +59,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL AutoSpinLockTest.cpp EResourceSharedLockTest.cpp RecursiveAutoSpinLockTest.cpp + EResourceTest.cpp ) target_link_libraries(kf-test kf::kf kmtest::kmtest) diff --git a/test/EResourceTest.cpp b/test/EResourceTest.cpp new file mode 100644 index 0000000..6044779 --- /dev/null +++ b/test/EResourceTest.cpp @@ -0,0 +1,224 @@ +#include "pch.h" +#include +#include + +namespace +{ + constexpr int kOneMillisecond = 10'000; // 1 ms in 100-nanosecond intervals + + void delay() + { + LARGE_INTEGER interval; + interval.QuadPart = -kOneMillisecond; + KeDelayExecutionThread(KernelMode, FALSE, &interval); + } +} + +SCENARIO("kf::EResource") +{ + GIVEN("An EResource object") + { + kf::EResource resource; + + THEN("The resource is initialized and not acquired immediately") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + + GIVEN("The EResource acquired exclusive") + { + kf::EResource resource; + bool acquired = resource.acquireExclusive(); + + WHEN("The resource is acquired exclusively") + { + //The system considers exclusive access to be a subset of shared access. + //Therefore, a thread that has exclusive access to a resource also has shared access to the resource. + THEN("The exclusive and shared locks are acquired") + { + REQUIRE(acquired == true); + REQUIRE(resource.isAcquiredExclusive() == true); + REQUIRE(resource.isAcquiredShared() == 1); + } + + WHEN("The exclusive lock is released") + { + resource.release(); + + THEN("The resource is no longer locked") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + } + } + + GIVEN("The EResource is acquired shared") + { + kf::EResource resource; + bool acquired = resource.acquireShared(); + + THEN("The only shared lock is acquired") + { + REQUIRE(acquired == true); + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 1); + } + + WHEN("The shared lock is released") + { + resource.release(); + + THEN("The resource is no longer locked") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + } + + GIVEN("The EResource acquired exclusive") + { + kf::EResource resource; + bool exclusiveAcquired = resource.acquireExclusive(); + REQUIRE(exclusiveAcquired == true); + + WHEN("The resource is converted to shared") + { + resource.convertExclusiveToShared(); + + THEN("The lock is converted to shared") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 1); + } + + WHEN("The shared lock is released") + { + resource.release(); + + THEN("The resource is no longer locked") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + } + } + + GIVEN("The EResource locked using the lock function") + { + kf::EResource resource; + resource.lock(); + + THEN("The resource is acquired exclusively and shared") + { + REQUIRE(resource.isAcquiredExclusive() == true); + REQUIRE(resource.isAcquiredShared() == 1); + } + + WHEN("The resource is unlocked") + { + resource.unlock(); + + THEN("The resource is no longer locked") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + } + + GIVEN("The EResource is locked shared using the lock_shared function") + { + kf::EResource resource; + resource.lock_shared(); + + THEN("The resource is acquired shared") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 1); + } + + WHEN("The resource is unlocked shared") + { + resource.unlock_shared(); + + THEN("The resource is no longer locked") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + } + + GIVEN("The EResource and multiple threads") + { + constexpr int kMaxThreadsCount = 64; + const ULONG numLogicalProcessors = KeQueryActiveProcessorCount(nullptr); + REQUIRE(numLogicalProcessors <= kMaxThreadsCount); + + struct Context + { + kf::EResource* resource = nullptr; + std::array* acquired = nullptr; + LONG counter = 0; + }; + kf::EResource resource; + std::array acquired; + Context context{ &resource, &acquired }; + kf::ThreadPool threadPool(numLogicalProcessors); + + WHEN("Multiple threads attempt to acquire exclusive the resource") + { + threadPool.start([](void* context) { + auto res = static_cast(context); + res->resource->acquireExclusive(); + res->acquired->at(res->counter++) = true; + delay(); + res->resource->release(); + }, &context); + + threadPool.join(); + + THEN("All threads can acquire and release the resource without deadlock") + { + auto size = min(kMaxThreadsCount, numLogicalProcessors); + for (size_t i = 0; i < size; i++) + { + REQUIRE(acquired.at(i) == true); + } + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + + WHEN("Multiple threads attempt to acquire shared the resource") + { + threadPool.start([](void* context) { + auto res = static_cast(context); + res->resource->acquireShared(); + auto index = InterlockedIncrement(&res->counter) - 1; + res->acquired->at(index) = true; + delay(); + res->resource->release(); + }, &context); + + threadPool.join(); + + THEN("All threads can acquire and release the resource without deadlock") + { + auto size = min(kMaxThreadsCount, numLogicalProcessors); + for (size_t i = 0; i < size; i++) + { + REQUIRE(acquired.at(i) == true); + } + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + } +} diff --git a/test/pch.h b/test/pch.h index b5c57a3..396a36d 100644 --- a/test/pch.h +++ b/test/pch.h @@ -21,6 +21,19 @@ extern "C" inline int _CrtDbgReport( KeBugCheckEx(KERNEL_SECURITY_CHECK_FAILURE, 0, 0, 0, 0); } +inline void __ehvec_dtor( + void* ptr, + unsigned __int64 size, + unsigned __int64 count, + void(__cdecl* dtor)(void*) +) +{ + UNREFERENCED_PARAMETER(ptr); + UNREFERENCED_PARAMETER(size); + UNREFERENCED_PARAMETER(count); + UNREFERENCED_PARAMETER(dtor); +} + namespace std { [[noreturn]] inline void __cdecl _Xinvalid_argument(_In_z_ const char* /*What*/)