mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
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.
120 lines
4.0 KiB
C++
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);
|
|
}
|