/* * Copyright (c) 2026, The Ladybird developers * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include using namespace AK::TimeLiterals; static void spin_until(Core::EventLoop& loop, Function 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 action_ran = false; IGNORE_USE_IN_ESCAPING_LAMBDA Atomic on_error_called = false; IGNORE_USE_IN_ESCAPING_LAMBDA Atomic on_complete_called = false; Optional action_thread_id; auto background_action = Threading::BackgroundAction::construct( [&](auto&) -> ErrorOr { 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 started = false; IGNORE_USE_IN_ESCAPING_LAMBDA Atomic finished = false; IGNORE_USE_IN_ESCAPING_LAMBDA Atomic on_error_count = 0; IGNORE_USE_IN_ESCAPING_LAMBDA Atomic on_complete_count = 0; auto background_action = Threading::BackgroundAction::construct( [&](auto& action) -> ErrorOr { 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); }