diff --git a/source/utils/mostlyharmless_TaskThread.cpp b/source/utils/mostlyharmless_TaskThread.cpp index c4e5be8..ce02d4e 100644 --- a/source/utils/mostlyharmless_TaskThread.cpp +++ b/source/utils/mostlyharmless_TaskThread.cpp @@ -5,13 +5,22 @@ #include #include namespace mostly_harmless::utils { + TaskThread::~TaskThread() noexcept { + while (m_threadAboutToStart || isThreadRunning()) { + signalThreadShouldExit(); + wake(); + } + } + void TaskThread::perform() { if (m_isThreadRunning) return; + m_threadAboutToStart = true; if (!action) { assert(false); return; } auto actionWrapper = [this]() -> void { + m_threadAboutToStart.store(false); m_isThreadRunning.store(true); action(); m_isThreadRunning.store(false); @@ -28,8 +37,8 @@ namespace mostly_harmless::utils { } void TaskThread::wake() { - std::lock_guard guard{ m_mutex }; m_canWakeUp = true; + std::lock_guard guard{ m_mutex }; m_conditionVariable.notify_one(); } diff --git a/tests/utils/mostlyharmless_TaskThreadTests.cpp b/tests/utils/mostlyharmless_TaskThreadTests.cpp index c3b693e..3f7b217 100644 --- a/tests/utils/mostlyharmless_TaskThreadTests.cpp +++ b/tests/utils/mostlyharmless_TaskThreadTests.cpp @@ -5,53 +5,72 @@ #include #include #include +#include namespace mostly_harmless::testing { - // TEST_CASE("Test TaskThread") { - // std::mutex mutex; - // mostly_harmless::utils::TaskThread taskThread; - // SECTION("Wait for lock") { - // auto x{ false }; - // auto task = [&mutex, &x]() -> void { - // std::scoped_lock sl{ mutex }; - // std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // x = true; - // }; - // taskThread.action = std::move(task); - // taskThread.perform(); - // // give it a bit of time to spin up.. (remember that this is a syscall) - // std::this_thread::sleep_for(std::chrono::milliseconds(5)); - // REQUIRE(taskThread.isThreadRunning()); - // // Sleep for a ms so the task has a chance to acquire the mutex.. - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // std::scoped_lock sl{ mutex }; - // REQUIRE(x); - // REQUIRE(!taskThread.isThreadRunning()); - // } - // - // SECTION("Kill") { - // auto task = [&taskThread]() -> void { - // while(!taskThread.threadShouldExit()); - // }; - // taskThread.action = std::move(task); - // taskThread.perform(); - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // REQUIRE(taskThread.isThreadRunning()); - // taskThread.signalThreadShouldExit(); - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // REQUIRE(!taskThread.isThreadRunning()); - // } - // - // SECTION("Sleep/Wake") { - // auto task = [&taskThread]() -> void { - // taskThread.sleep(); - // }; - // taskThread.action = std::move(task); - // taskThread.perform(); - // std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // REQUIRE(taskThread.isThreadRunning()); - // taskThread.wake(); - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // REQUIRE(!taskThread.isThreadRunning()); - // } - // } -} + TEST_CASE("Test TaskThread") { + std::mutex mutex; + mostly_harmless::utils::TaskThread taskThread; + SECTION("Wait for lock") { + auto x{ false }; + auto task = [&mutex, &x]() -> void { + std::scoped_lock sl{ mutex }; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + x = true; + }; + taskThread.action = std::move(task); + taskThread.perform(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + REQUIRE(taskThread.isThreadRunning()); + // Sleep for a ms so the task has a chance to acquire the mutex.. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::scoped_lock sl{ mutex }; + REQUIRE(x); + REQUIRE(!taskThread.isThreadRunning()); + } + + SECTION("Kill") { + auto task = [&taskThread]() -> void { + while (!taskThread.threadShouldExit()) + ; + }; + taskThread.action = std::move(task); + taskThread.perform(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + REQUIRE(taskThread.isThreadRunning()); + taskThread.signalThreadShouldExit(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + REQUIRE(!taskThread.isThreadRunning()); + } + + SECTION("Sleep/Wake") { + auto task = [&taskThread]() -> void { + taskThread.sleep(); + }; + taskThread.action = std::move(task); + taskThread.perform(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + REQUIRE(taskThread.isThreadRunning()); + taskThread.wake(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + REQUIRE(!taskThread.isThreadRunning()); + } + + SECTION("Out of scope") { + std::chrono::time_point start; + { + utils::TaskThread scopedThread; + auto task = [&scopedThread]() -> void { + while (!scopedThread.threadShouldExit()) { + scopedThread.sleep(); + } + }; + scopedThread.action = std::move(task); + scopedThread.perform(); + start = std::chrono::steady_clock::now(); + } + const auto end = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast(end - start); + REQUIRE(duration < std::chrono::milliseconds(5)); + } + } +} // namespace mostly_harmless::testing diff --git a/tests/utils/mostlyharmless_TimerTests.cpp b/tests/utils/mostlyharmless_TimerTests.cpp index 3aa9d0a..fe7d67b 100644 --- a/tests/utils/mostlyharmless_TimerTests.cpp +++ b/tests/utils/mostlyharmless_TimerTests.cpp @@ -10,26 +10,27 @@ namespace mostly_harmless::tests { TEST_CASE("Test Timer") { mostly_harmless::utils::Timer timer; - // SECTION("Test calls") { - // std::atomic callCount{ 0 }; - // auto start = std::chrono::steady_clock::now(); - // auto timerCallback = [&callCount, &start]() -> void { - // const auto now = std::chrono::steady_clock::now(); - // const auto delta = std::chrono::duration_cast(now - start); - // // normalise, and truncate.. - // const auto normalisedDelta = static_cast(delta.count()) / 100.0; - // const auto truncatedDelta = std::round(normalisedDelta); - // REQUIRE(truncatedDelta == 1); - // start = std::chrono::steady_clock::now(); - // ++callCount; - // }; - // timer.setAction(std::move(timerCallback)); - // timer.run(static_cast(100)); - // while (callCount < 5) - // ; - // timer.stop(); - // REQUIRE(callCount >= 5); - // } + SECTION("Test calls") { + std::atomic callCount{ 0 }; + auto start = std::chrono::steady_clock::now(); + auto timerCallback = [&callCount, &start]() -> void { + const auto now = std::chrono::steady_clock::now(); + const auto delta = std::chrono::duration_cast(now - start); + // normalise, and truncate.. + const auto normalisedDelta = static_cast(delta.count()) / 100.0; + const auto truncatedDelta = std::round(normalisedDelta); + REQUIRE(truncatedDelta == 1); + start = std::chrono::steady_clock::now(); + ++callCount; + }; + timer.action = std::move(timerCallback); + timer.run(static_cast(100)); + while (callCount < 5) + ; + timer.stop(); + REQUIRE(callCount >= 5); + } + SECTION("Test out-of-scope timer") { std::atomic count{ 0 }; { @@ -37,11 +38,11 @@ namespace mostly_harmless::tests { auto task = [&count]() -> void { ++count; }; - scopedTimer.setAction(std::move(task)); + scopedTimer.action = std::move(task); scopedTimer.run(1); } std::this_thread::sleep_for(std::chrono::milliseconds(10)); - REQUIRE(count != 0); + REQUIRE(count == 0); } } } // namespace mostly_harmless::tests \ No newline at end of file