From f46b80649b2c71634bb722100b1b70ee938850dc Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Thu, 14 Aug 2025 14:36:58 +0200 Subject: [PATCH 1/4] add tests for EResource --- test/CMakeLists.txt | 1 + test/EResourceTest.cpp | 228 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 test/EResourceTest.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9ae8d47..a79a745 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,6 +53,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL VariableSizeStructTest.cpp ScopeFailureTest.cpp Base64Test.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..de9cfbc --- /dev/null +++ b/test/EResourceTest.cpp @@ -0,0 +1,228 @@ +#include "pch.h" +#include +#include + +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); + } + + WHEN("The resource is acquired exclusively") + { + bool acquired = resource.acquireExclusive(); + + //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() > 0); + } + + 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; + + WHEN("The resource is converted to shared") + { + bool exclusiveAcquired = resource.acquireExclusive(); + REQUIRE(exclusiveAcquired == true); + + resource.convertExclusiveToShared(); + + THEN("The lock is converted to shared") + { + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() > 0); + } + + 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() > 0); + } + + 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 2 threads") + { + constexpr int kOneSecond = 10'000'000; // 1 second in 100-nanosecond intervals + struct Context + { + kf::EResource* resource; + bool wasAcquiredByFirst = false; + bool wasAcquiredBySecond = false; + }; + + WHEN("Multiple threads attempt to acquire exclusive the resource") + { + kf::EResource resource; + kf::Thread thread1; + kf::Thread thread2; + + Context context{ &resource }; + + thread1.start([](void* context) { + auto res = static_cast(context); + res->wasAcquiredByFirst = res->resource->acquireExclusive(); + res->resource->release(); + }, + &context); + + thread2.start([](void* context) { + auto res = static_cast(context); + res->wasAcquiredBySecond = res->resource->acquireExclusive(); + res->resource->release(); + }, + &context); + + thread1.join(); + thread2.join(); + + THEN("Both threads can acquire and release the resource without deadlock") + { + REQUIRE(context.wasAcquiredByFirst); + REQUIRE(context.wasAcquiredBySecond); + REQUIRE(resource.isAcquiredExclusive() == false); + REQUIRE(resource.isAcquiredShared() == 0); + } + } + + WHEN("Multiple threads attempt to acquire shared the resource") + { + kf::EResource resource; + kf::Thread thread1; + kf::Thread thread2; + + Context context{ &resource }; + + thread1.start([](void* context) { + auto res = static_cast(context); + res->wasAcquiredByFirst = res->resource->acquireShared(); + + LARGE_INTEGER interval; + interval.QuadPart = -kOneSecond; + KeDelayExecutionThread(KernelMode, FALSE, &interval); + + res->resource->release(); + }, + &context); + + thread2.start([](void* context) { + auto res = static_cast(context); + + res->wasAcquiredBySecond = res->resource->acquireShared(); + LARGE_INTEGER interval; + interval.QuadPart = -kOneSecond; + KeDelayExecutionThread(KernelMode, FALSE, &interval); + + res->resource->release(); + }, + &context); + + THEN("Both threads can acquire and release the resource without deadlock") + { + thread1.join(); + thread2.join(); + REQUIRE(context.wasAcquiredByFirst); + REQUIRE(context.wasAcquiredBySecond); + } + } + } +} From 60abff1f931532bf5c30d0f143f2832ba17615f9 Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Fri, 15 Aug 2025 16:57:41 +0200 Subject: [PATCH 2/4] refactoring --- test/EResourceTest.cpp | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/test/EResourceTest.cpp b/test/EResourceTest.cpp index de9cfbc..917bd25 100644 --- a/test/EResourceTest.cpp +++ b/test/EResourceTest.cpp @@ -2,6 +2,18 @@ #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") @@ -49,7 +61,7 @@ SCENARIO("kf::EResource") { REQUIRE(acquired == true); REQUIRE(resource.isAcquiredExclusive() == false); - REQUIRE(resource.isAcquiredShared() > 0); + REQUIRE(resource.isAcquiredShared() == 1); } WHEN("The shared lock is released") @@ -67,18 +79,17 @@ SCENARIO("kf::EResource") GIVEN("The EResource acquired exclusive") { kf::EResource resource; + bool exclusiveAcquired = resource.acquireExclusive(); + REQUIRE(exclusiveAcquired == true); WHEN("The resource is converted to shared") { - bool exclusiveAcquired = resource.acquireExclusive(); - REQUIRE(exclusiveAcquired == true); - resource.convertExclusiveToShared(); THEN("The lock is converted to shared") { REQUIRE(resource.isAcquiredExclusive() == false); - REQUIRE(resource.isAcquiredShared() > 0); + REQUIRE(resource.isAcquiredShared() == 1); } WHEN("The shared lock is released") @@ -125,7 +136,7 @@ SCENARIO("kf::EResource") THEN("The resource is acquired shared") { REQUIRE(resource.isAcquiredExclusive() == false); - REQUIRE(resource.isAcquiredShared() > 0); + REQUIRE(resource.isAcquiredShared() == 1); } WHEN("The resource is unlocked shared") @@ -142,7 +153,7 @@ SCENARIO("kf::EResource") GIVEN("The EResource and 2 threads") { - constexpr int kOneSecond = 10'000'000; // 1 second in 100-nanosecond intervals + struct Context { kf::EResource* resource; @@ -161,6 +172,7 @@ SCENARIO("kf::EResource") thread1.start([](void* context) { auto res = static_cast(context); res->wasAcquiredByFirst = res->resource->acquireExclusive(); + delay(); res->resource->release(); }, &context); @@ -168,6 +180,7 @@ SCENARIO("kf::EResource") thread2.start([](void* context) { auto res = static_cast(context); res->wasAcquiredBySecond = res->resource->acquireExclusive(); + delay(); res->resource->release(); }, &context); @@ -195,11 +208,7 @@ SCENARIO("kf::EResource") thread1.start([](void* context) { auto res = static_cast(context); res->wasAcquiredByFirst = res->resource->acquireShared(); - - LARGE_INTEGER interval; - interval.QuadPart = -kOneSecond; - KeDelayExecutionThread(KernelMode, FALSE, &interval); - + delay(); res->resource->release(); }, &context); @@ -208,10 +217,7 @@ SCENARIO("kf::EResource") auto res = static_cast(context); res->wasAcquiredBySecond = res->resource->acquireShared(); - LARGE_INTEGER interval; - interval.QuadPart = -kOneSecond; - KeDelayExecutionThread(KernelMode, FALSE, &interval); - + delay(); res->resource->release(); }, &context); From 5bce8feb857b5b8178acca23b2ab3487a38e0c32 Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Tue, 19 Aug 2025 14:24:16 +0200 Subject: [PATCH 3/4] add more threads in tests --- include/kf/ThreadPool.h | 2 +- test/EResourceTest.cpp | 98 ++++++++++++++++++----------------------- test/pch.h | 13 ++++++ 3 files changed, 57 insertions(+), 56 deletions(-) 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/EResourceTest.cpp b/test/EResourceTest.cpp index 917bd25..1b0e94e 100644 --- a/test/EResourceTest.cpp +++ b/test/EResourceTest.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include -#include - +#include +#include namespace { constexpr int kOneMillisecond = 10'000; // 1 ms in 100-nanosecond intervals @@ -25,11 +25,15 @@ SCENARIO("kf::EResource") 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") { - bool acquired = resource.acquireExclusive(); - //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") @@ -151,47 +155,40 @@ SCENARIO("kf::EResource") } } - GIVEN("The EResource and 2 threads") + GIVEN("The EResource and multiple threads") { - + constexpr int kMaxThreadsCount = 64; + const ULONG numLogicalProcessors = KeQueryActiveProcessorCount(nullptr); struct Context { - kf::EResource* resource; - bool wasAcquiredByFirst = false; - bool wasAcquiredBySecond = false; + 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") { - kf::EResource resource; - kf::Thread thread1; - kf::Thread thread2; - - Context context{ &resource }; - - thread1.start([](void* context) { + threadPool.start([](void* context) { auto res = static_cast(context); - res->wasAcquiredByFirst = res->resource->acquireExclusive(); + res->resource->acquireExclusive(); + res->acquired->at(res->counter++) = true; delay(); res->resource->release(); - }, - &context); + }, &context); - thread2.start([](void* context) { - auto res = static_cast(context); - res->wasAcquiredBySecond = res->resource->acquireExclusive(); - delay(); - res->resource->release(); - }, - &context); + threadPool.join(); - thread1.join(); - thread2.join(); - - THEN("Both threads can acquire and release the resource without deadlock") + THEN("All threads can acquire and release the resource without deadlock") { - REQUIRE(context.wasAcquiredByFirst); - REQUIRE(context.wasAcquiredBySecond); + 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); } @@ -199,35 +196,26 @@ SCENARIO("kf::EResource") WHEN("Multiple threads attempt to acquire shared the resource") { - kf::EResource resource; - kf::Thread thread1; - kf::Thread thread2; - - Context context{ &resource }; - - thread1.start([](void* context) { + threadPool.start([](void* context) { auto res = static_cast(context); - res->wasAcquiredByFirst = res->resource->acquireShared(); + res->resource->acquireShared(); + auto index = InterlockedIncrement(&res->counter) - 1; + res->acquired->at(index) = true; delay(); res->resource->release(); - }, - &context); + }, &context); - thread2.start([](void* context) { - auto res = static_cast(context); + threadPool.join(); - res->wasAcquiredBySecond = res->resource->acquireShared(); - delay(); - res->resource->release(); - }, - &context); - - THEN("Both threads can acquire and release the resource without deadlock") + THEN("All threads can acquire and release the resource without deadlock") { - thread1.join(); - thread2.join(); - REQUIRE(context.wasAcquiredByFirst); - REQUIRE(context.wasAcquiredBySecond); + 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*/) From 6e05e152cb8a81b41b3870e8fba4c30c7528635b Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Wed, 20 Aug 2025 17:32:24 +0200 Subject: [PATCH 4/4] refactoring --- test/EResourceTest.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/EResourceTest.cpp b/test/EResourceTest.cpp index 1b0e94e..6044779 100644 --- a/test/EResourceTest.cpp +++ b/test/EResourceTest.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include #include -#include + namespace { constexpr int kOneMillisecond = 10'000; // 1 ms in 100-nanosecond intervals @@ -159,6 +159,8 @@ SCENARIO("kf::EResource") { constexpr int kMaxThreadsCount = 64; const ULONG numLogicalProcessors = KeQueryActiveProcessorCount(nullptr); + REQUIRE(numLogicalProcessors <= kMaxThreadsCount); + struct Context { kf::EResource* resource = nullptr;