Files
ladybird/Tests/LibThreading/TestBackgroundAction.cpp
Zaggy1024 1c7aca7e07 LibThreading: Simplify BackgroundAction callback invocation
Using a Promise in BackgroundAction was not doing anything since the
change to use a weak reference to the event loop, so let's just drop
that.

The thread will now always move itself (and therefore its callbacks)
over to the originating thread before completing, regardless of the
presence of callbacks. This ensures that ref counting remains on the
main thread.

In addition, BackgroundAction's completion callback can no longer
return errors. This functionality wasn't actually used anywhere, it was
a holdover from the behavior of Core::Promise.
2026-03-02 17:06:39 -06:00

120 lines
4.0 KiB
C++

/*
* Copyright (c) 2026, The Ladybird developers
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Atomic.h>
#include <AK/Error.h>
#include <AK/Function.h>
#include <AK/Time.h>
#include <AK/Types.h>
#include <LibCore/EventLoop.h>
#include <LibCore/System.h>
#include <LibTest/TestCase.h>
#include <LibThreading/BackgroundAction.h>
#include <pthread.h>
using namespace AK::TimeLiterals;
static void spin_until(Core::EventLoop& loop, Function<bool()> condition, AK::Duration timeout = 2000_ms)
{
i64 const timeout_ms = timeout.to_milliseconds();
for (i64 elapsed_ms = 0; elapsed_ms < timeout_ms; elapsed_ms += 5) {
(void)loop.pump(Core::EventLoop::WaitMode::PollForEvents);
if (condition())
return;
MUST(Core::System::sleep_ms(5));
}
FAIL("Timed out waiting for condition");
}
TEST_CASE(background_action_on_error_called_on_action_failure_and_on_origin_thread)
{
Core::EventLoop loop;
pthread_t const origin_thread_id = pthread_self();
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> action_ran = false;
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> on_error_called = false;
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> on_complete_called = false;
Optional<pthread_t> action_thread_id;
auto background_action = Threading::BackgroundAction<int>::construct(
[&](auto&) -> ErrorOr<int> {
action_thread_id = pthread_self();
action_ran.store(true, AK::MemoryOrder::memory_order_relaxed);
return Error::from_string_literal("action failed");
},
[&](int) {
on_complete_called.store(true, AK::MemoryOrder::memory_order_relaxed);
loop.quit(1);
},
[&](Error error) {
EXPECT(pthread_equal(origin_thread_id, pthread_self()));
EXPECT_EQ(error.string_literal(), "action failed"sv);
on_error_called.store(true, AK::MemoryOrder::memory_order_relaxed);
loop.quit(0);
});
loop.exec();
EXPECT(action_ran.load(AK::MemoryOrder::memory_order_relaxed));
EXPECT(action_thread_id.has_value());
EXPECT(!pthread_equal(action_thread_id.value(), origin_thread_id));
EXPECT(on_error_called.load(AK::MemoryOrder::memory_order_relaxed));
EXPECT(!on_complete_called.load(AK::MemoryOrder::memory_order_relaxed));
(void)background_action;
}
TEST_CASE(background_action_cancel_suppresses_on_error_and_on_complete)
{
Core::EventLoop loop;
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> started = false;
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<bool> finished = false;
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<int> on_error_count = 0;
IGNORE_USE_IN_ESCAPING_LAMBDA Atomic<int> on_complete_count = 0;
auto background_action = Threading::BackgroundAction<int>::construct(
[&](auto& action) -> ErrorOr<int> {
started.store(true, AK::MemoryOrder::memory_order_relaxed);
while (!action.is_canceled())
MUST(Core::System::sleep_ms(1));
finished.store(true, AK::MemoryOrder::memory_order_relaxed);
return Error::from_string_literal("error after cancel");
},
[&](int) {
on_complete_count.fetch_add(1, AK::MemoryOrder::memory_order_relaxed);
},
[&](Error) {
on_error_count.fetch_add(1, AK::MemoryOrder::memory_order_relaxed);
});
spin_until(loop, [&] {
return started.load(AK::MemoryOrder::memory_order_relaxed);
});
background_action->cancel();
spin_until(loop, [&] {
return finished.load(AK::MemoryOrder::memory_order_relaxed);
});
// Run the loop a bit more to ensure any incorrectly-posted callbacks would execute.
for (size_t i = 0; i < 50; ++i) {
(void)loop.pump(Core::EventLoop::WaitMode::PollForEvents);
MUST(Core::System::sleep_ms(1));
}
EXPECT(background_action->is_canceled());
EXPECT_EQ(on_complete_count.load(AK::MemoryOrder::memory_order_relaxed), 0);
EXPECT_EQ(on_error_count.load(AK::MemoryOrder::memory_order_relaxed), 0);
}