| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/iwlwifi/platform/rcu-manager.h" |
| |
| #include <lib/async-testing/test_loop.h> |
| #include <lib/sync/completion.h> |
| #include <zircon/time.h> |
| |
| #include <memory> |
| #include <thread> |
| |
| #include <gtest/gtest.h> |
| |
| namespace wlan::testing { |
| namespace { |
| |
| TEST(RcuManagerTest, NestedReadLock) { |
| auto test_loop = std::make_unique<::async::TestLoop>(); |
| auto manager = std::make_unique<::wlan::iwlwifi::RcuManager>(test_loop->dispatcher()); |
| |
| manager->InitForThread(); |
| manager->ReadLock(); |
| |
| // Call RcuManager::Sync() on another thread. |
| bool synced = false; |
| std::thread sync_thread([&]() { |
| manager->InitForThread(); |
| manager->Sync(); |
| synced = true; |
| }); |
| EXPECT_FALSE(synced); |
| |
| // Nesting a lock and then unlocking it (without unlocking the outer lock) should not cause the |
| // Sync() call to complete yet. |
| manager->ReadLock(); |
| EXPECT_FALSE(synced); |
| manager->ReadUnlock(); |
| EXPECT_FALSE(synced); |
| |
| // Unlocking the lock a final time should allow the Sync() call to finish. |
| manager->ReadUnlock(); |
| sync_thread.join(); |
| EXPECT_TRUE(synced); |
| } |
| |
| TEST(RcuManagerTest, ThreadedReadLock) { |
| auto test_loop = std::make_unique<::async::TestLoop>(); |
| auto manager = std::make_unique<::wlan::iwlwifi::RcuManager>(test_loop->dispatcher()); |
| |
| manager->InitForThread(); |
| manager->ReadLock(); |
| |
| // Perform a read-side lock on another thread. |
| sync_completion_t thread_started = {}; |
| sync_completion_t thread_continue = {}; |
| std::thread lock_thread([&]() { |
| manager->InitForThread(); |
| manager->ReadLock(); |
| sync_completion_signal(&thread_started); |
| sync_completion_wait(&thread_continue, ZX_TIME_INFINITE); |
| manager->ReadUnlock(); |
| }); |
| |
| // Wait for the other thread to read-side lock. |
| sync_completion_wait(&thread_started, ZX_TIME_INFINITE); |
| |
| // Start a thread that waits for a sync. |
| bool synced = false; |
| std::thread sync_thread([&]() { |
| manager->InitForThread(); |
| manager->Sync(); |
| synced = true; |
| }); |
| EXPECT_FALSE(synced); |
| |
| // Unlocking this thread still means another thread is holding the read-side lock. |
| manager->ReadUnlock(); |
| EXPECT_FALSE(synced); |
| |
| // Unlocking the other thread finally allows the Sync() to complete. |
| sync_completion_signal(&thread_continue); |
| lock_thread.join(); |
| sync_thread.join(); |
| EXPECT_TRUE(synced); |
| } |
| |
| TEST(RcuManagerTest, CallSync) { |
| auto test_loop = std::make_unique<::async::TestLoop>(); |
| auto manager = std::make_unique<::wlan::iwlwifi::RcuManager>(test_loop->dispatcher()); |
| |
| manager->InitForThread(); |
| manager->ReadLock(); |
| |
| // Prepare a call that will execute after all read-side locks are unlocked. |
| bool called = false; |
| manager->CallSync([](void* data) { *reinterpret_cast<bool*>(data) = true; }, &called); |
| std::thread loop_thread([&]() { test_loop->RunUntilIdle(); }); |
| EXPECT_FALSE(called); |
| |
| // A nested lock will not cause the call to executed. |
| manager->ReadLock(); |
| EXPECT_FALSE(called); |
| manager->ReadUnlock(); |
| EXPECT_FALSE(called); |
| |
| // Unlocking the last lock allows the call to proceed. |
| manager->ReadUnlock(); |
| loop_thread.join(); |
| EXPECT_TRUE(called); |
| } |
| |
| } // namespace |
| } // namespace wlan::testing |