diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index ae8cb218a68..f6a3cf17aa2 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -514,6 +514,7 @@ if (BUILD_TESTING) LibCompress LibTest LibTextCodec + LibThreading LibUnicode LibURL LibXML diff --git a/Meta/gn/secondary/Tests/BUILD.gn b/Meta/gn/secondary/Tests/BUILD.gn index e28f7cd26ca..71b30a7e7cd 100644 --- a/Meta/gn/secondary/Tests/BUILD.gn +++ b/Meta/gn/secondary/Tests/BUILD.gn @@ -2,6 +2,7 @@ group("Tests") { deps = [ "//Tests/AK", "//Tests/LibJS", + "//Tests/LibThreading", "//Tests/LibURL", "//Tests/LibWeb", ] diff --git a/Tests/LibThreading/CMakeLists.txt b/Tests/LibThreading/CMakeLists.txt index 5f4914ab4fd..b505bfe17d5 100644 --- a/Tests/LibThreading/CMakeLists.txt +++ b/Tests/LibThreading/CMakeLists.txt @@ -1,5 +1,6 @@ set(TEST_SOURCES TestThread.cpp + TestThreadPool.cpp ) foreach(source IN LISTS TEST_SOURCES) diff --git a/Tests/LibThreading/TestThreadPool.cpp b/Tests/LibThreading/TestThreadPool.cpp new file mode 100644 index 00000000000..2d12d0970fb --- /dev/null +++ b/Tests/LibThreading/TestThreadPool.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, Braydn Moore + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +using namespace AK::TimeLiterals; + +TEST_CASE(thread_pool_deadlock) +{ + static constexpr auto RUN_TIMEOUT = 120_sec; + static constexpr u64 NUM_RUNS = 1000; + static constexpr u64 MAX_VALUE = 1 << 15; + + for (u64 i = 0; i < NUM_RUNS; ++i) { + u64 expected_value = (MAX_VALUE * (MAX_VALUE + 1)) / 2; + Atomic sum; + + // heap allocate the ThreadPool in case it deadlocks. Exiting in the + // case of a deadlock will purposefully leak memory to avoid calling the + // destructor and hanging the test + auto* thread_pool = new Threading::ThreadPool( + [&sum](u64 current_val) { + sum += current_val; + }); + + for (u64 j = 0; j <= MAX_VALUE; ++j) { + thread_pool->submit(j); + } + + auto join_thread = Threading::Thread::construct([thread_pool]() -> intptr_t { + thread_pool->wait_for_all(); + delete thread_pool; + return 0; + }); + + join_thread->start(); + auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise); + while (!join_thread->has_exited() && timer.elapsed_milliseconds() < RUN_TIMEOUT.to_milliseconds()) + ; + EXPECT(join_thread->has_exited()); + // exit since the current pool is deadlocked and we have no way of + // unblocking the pool other than having the OS teardown the process + // struct + if (!join_thread->has_exited()) { + return; + } + + (void)join_thread->join(); + EXPECT_EQ(sum.load(), expected_value); + } +}