ladybird/Libraries/LibCore/EventLoopImplementationWindows.cpp

378 lines
12 KiB
C++
Raw Normal View History

2024-10-31 12:44:19 +05:00
/*
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024-2025, stasoid <stasoid@yahoo.com>
* Copyright (c) 2025, ayeteadoe <ayeteadoe@gmail.com>
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
2024-10-31 12:44:19 +05:00
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Diagnostics.h>
#include <AK/HashMap.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Windows.h>
2024-10-31 12:44:19 +05:00
#include <LibCore/EventLoopImplementationWindows.h>
#include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h>
LibCore: Make single-shot timer objects manually reset on Windows This fixes a really nasty EventLoop bug which I debugged for 2 weeks. The spin_until([&]{return completed_tasks == total_tasks;}) in TraversableNavigable::check_if_unloading_is_canceled spins forever. Cause of the bug: check_if_unloading_is_canceled is called deferred check_if_unloading_is_canceled creates a task: queue_global_task(..., [&] { ... completed_tasks++; })); This task is never executed. queue_global_task calls TaskQueue::add void TaskQueue::add(task) { m_tasks.append(task); m_event_loop->schedule(); } void HTML::EventLoop::schedule() { if (!m_system_event_loop_timer) m_system_event_loop_timer = Timer::create_single_shot( 0, // delay [&] { process(); }); if (!m_system_event_loop_timer->is_active()) m_system_event_loop_timer->restart(); } EventLoop::process executes one task from task queue and calls schedule again if there are more tasks. So task processing relies on one single-shot zero-delay timer, m_system_event_loop_timer. Timers and other notification events are handled by Core::EventLoop and Core::ThreadEventQueue, these are different from HTML::EventLoop and HTML::TaskQueue mentioned above. check_if_unloading_is_canceled is called using deferred_invoke mechanism, different from m_system_event_loop_timer, see Navigable::navigate and Core::EventLoop::deferred_invoke. The core of the problem is that Core::EventLoop::pump is called again (from spin_until) after timer fired but before its handler is executed. In ThreadEventQueue::process events are moved into local variable before executing. The first of those events is check_if_unloading_is_canceled. One of the rest events is Web::HTML::EventLoop::process sheduled in EventLoop::schedule using m_system_event_loop_timer. When check_if_unloading_is_canceled calls queue_global_task its m_system_event_loop_timer is still active because Timer::timer_event was not yet called, so the timer is not restarted. But Timer::timer_event (and hence EventLoop::process) will never execute because check_if_unloading_is_canceled calls spin_until after queue_global_task, and EventLoop::process is no longer in event_queue.m_private->queued_events. By making a single-shot timer manually-reset we are allowing it to fire several times. So when spin_until is executed m_system_event_loop_timer is fired again. Not an ideal solution, but this is the best I could come up with. This commit makes the behavior match EventLoopImplUnix, in which single-shot timer can also fire several times. Adding event_queue.process(); at the start of pump like in EvtLoopImplQt doesn't fix the problem. Note: Timer::start calls EventReceiver::start_timer, which calls EventLoop::register_timer with should_reload always set to true (single-shot vs periodic are handled in Timer::timer_event instead), so I use static_cast<Timer&>(object).is_single_shot() instead of !should_reload.
2025-02-19 22:36:05 +05:00
#include <LibCore/Timer.h>
#include <LibThreading/Mutex.h>
struct OwnHandle {
2024-10-31 12:44:19 +05:00
HANDLE handle = NULL;
explicit OwnHandle(HANDLE h = NULL)
2024-10-31 12:44:19 +05:00
: handle(h)
{
}
OwnHandle(OwnHandle&& h)
2024-10-31 12:44:19 +05:00
{
handle = h.handle;
h.handle = NULL;
}
// This operation can only be done when handle is NULL
OwnHandle& operator=(OwnHandle&& other)
2024-10-31 12:44:19 +05:00
{
VERIFY(!handle);
if (this == &other)
return *this;
handle = other.handle;
other.handle = NULL;
return *this;
2024-10-31 12:44:19 +05:00
}
~OwnHandle()
2024-10-31 12:44:19 +05:00
{
if (handle)
CloseHandle(handle);
}
bool operator==(OwnHandle const& h) const { return handle == h.handle; }
2024-10-31 12:44:19 +05:00
bool operator==(HANDLE h) const { return handle == h; }
};
template<>
struct Traits<OwnHandle> : DefaultTraits<OwnHandle> {
static unsigned hash(OwnHandle const& h) { return Traits<HANDLE>::hash(h.handle); }
2024-10-31 12:44:19 +05:00
};
template<>
constexpr bool IsHashCompatible<HANDLE, OwnHandle> = true;
2024-10-31 12:44:19 +05:00
namespace Core {
enum class CompletionType : u8 {
Wake,
Timer,
Notifer,
};
struct CompletionPacket {
CompletionType type;
};
struct EventLoopWake final : CompletionPacket {
OwnHandle wait_packet;
OwnHandle wait_event;
};
struct EventLoopTimer final : CompletionPacket {
~EventLoopTimer()
{
CancelWaitableTimer(timer.handle);
}
OwnHandle timer;
OwnHandle wait_packet;
bool is_periodic;
2024-10-31 12:44:19 +05:00
WeakPtr<EventReceiver> owner;
};
struct EventLoopNotifier final : CompletionPacket {
~EventLoopNotifier()
{
}
Notifier::Type notifier_type() const { return m_notifier_type; }
int notifier_fd() const { return m_notifier_fd; }
// These are a space tradeoff for avoiding a double indirection through the notifier*.
Notifier* notifier;
Notifier::Type m_notifier_type;
int m_notifier_fd { -1 };
OwnHandle wait_packet;
OwnHandle wait_event;
};
2024-10-31 12:44:19 +05:00
struct ThreadData {
static ThreadData* the()
2024-10-31 12:44:19 +05:00
{
thread_local OwnPtr<ThreadData> thread_data = make<ThreadData>();
if (thread_data)
return &*thread_data;
return nullptr;
2024-10-31 12:44:19 +05:00
}
ThreadData()
: wake_data(make<EventLoopWake>())
2024-10-31 12:44:19 +05:00
{
wake_data->type = CompletionType::Wake;
wake_data->wait_event.handle = CreateEvent(NULL, FALSE, FALSE, NULL);
// Consider a way for different event loops to have a different number of threads
iocp.handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
VERIFY(iocp.handle);
NTSTATUS status = g_system.NtCreateWaitCompletionPacket(&wake_data->wait_packet.handle, GENERIC_READ | GENERIC_WRITE, NULL);
VERIFY(NT_SUCCESS(status));
status = g_system.NtAssociateWaitCompletionPacket(wake_data->wait_packet.handle, iocp.handle, wake_data->wait_event.handle, wake_data.ptr(), NULL, 0, 0, NULL);
VERIFY(NT_SUCCESS(status));
}
~ThreadData()
{
NTSTATUS status = g_system.NtCancelWaitCompletionPacket(wake_data->wait_packet.handle, TRUE);
VERIFY(NT_SUCCESS(status));
2024-10-31 12:44:19 +05:00
}
OwnHandle iocp;
// These are only used to register and unregister. The event loop doesn't access these.
HashMap<intptr_t, NonnullOwnPtr<EventLoopTimer>> timers;
HashMap<Notifier*, NonnullOwnPtr<EventLoopNotifier>> notifiers;
2024-10-31 12:44:19 +05:00
// The wake completion packet is posted to the thread's event loop to wake it.
NonnullOwnPtr<EventLoopWake> wake_data;
2024-10-31 12:44:19 +05:00
};
EventLoopImplementationWindows::EventLoopImplementationWindows()
: m_wake_event(ThreadData::the()->wake_data->wait_event.handle)
{
}
EventLoopImplementationWindows::~EventLoopImplementationWindows()
2024-10-31 12:44:19 +05:00
{
}
int EventLoopImplementationWindows::exec()
{
for (;;) {
if (m_exit_requested)
return m_exit_code;
pump(PumpMode::WaitForEvents);
}
VERIFY_NOT_REACHED();
}
static constexpr bool debug_event_loop = false;
size_t EventLoopImplementationWindows::pump(PumpMode pump_mode)
2024-10-31 12:44:19 +05:00
{
auto& event_queue = ThreadEventQueue::current();
auto* thread_data = ThreadData::the();
2024-10-31 12:44:19 +05:00
// NOTE: The number of entries to dequeue is to be optimized. Ideally we always dequeue all outstanding packets,
// but we don't want to increase the cost of each pump unnecessarily. If more than one entry is never dequeued
// at once, we could switch to using GetQueuedCompletionStatus which directly returns the values.
constexpr ULONG entry_count = 32;
OVERLAPPED_ENTRY entries[entry_count];
ULONG entries_removed = 0;
2024-10-31 12:44:19 +05:00
bool has_pending_events = event_queue.has_pending_events();
DWORD timeout = 0;
if (!has_pending_events && pump_mode == PumpMode::WaitForEvents)
timeout = INFINITE;
BOOL success = GetQueuedCompletionStatusEx(thread_data->iocp.handle, entries, entry_count, &entries_removed, timeout, FALSE);
dbgln_if(debug_event_loop, "Event loop dequed {} events", entries_removed);
if (success) {
for (ULONG i = 0; i < entries_removed; i++) {
auto& entry = entries[i];
auto* packet = reinterpret_cast<CompletionPacket*>(entry.lpCompletionKey);
if (packet->type == CompletionType::Wake) {
auto* wake_data = static_cast<EventLoopWake*>(packet);
NTSTATUS status = g_system.NtAssociateWaitCompletionPacket(wake_data->wait_packet.handle, thread_data->iocp.handle, wake_data->wait_event.handle, wake_data, NULL, 0, 0, NULL);
VERIFY(NT_SUCCESS(status));
continue;
}
if (packet->type == CompletionType::Timer) {
auto* timer = static_cast<EventLoopTimer*>(packet);
if (auto owner = timer->owner.strong_ref())
event_queue.post_event(*owner, make<TimerEvent>());
if (timer->is_periodic) {
NTSTATUS status = g_system.NtAssociateWaitCompletionPacket(timer->wait_packet.handle, thread_data->iocp.handle, timer->timer.handle, timer, NULL, 0, 0, NULL);
VERIFY(NT_SUCCESS(status));
}
continue;
}
if (packet->type == CompletionType::Notifer) {
auto* notifier_data = static_cast<EventLoopNotifier*>(packet);
event_queue.post_event(*notifier_data->notifier, make<NotifierActivationEvent>(notifier_data->notifier_fd(), notifier_data->notifier_type()));
NTSTATUS status = g_system.NtAssociateWaitCompletionPacket(notifier_data->wait_packet.handle, thread_data->iocp.handle, notifier_data->wait_event.handle, notifier_data, NULL, 0, 0, NULL);
VERIFY(NT_SUCCESS(status));
continue;
}
VERIFY_NOT_REACHED();
}
} else {
DWORD error = GetLastError();
switch (error) {
case WAIT_TIMEOUT:
break;
default:
dbgln("GetQueuedCompletionStatusEx failed with unexpected error: {}", Error::from_windows_error(error));
VERIFY_NOT_REACHED();
2024-10-31 12:44:19 +05:00
}
}
return event_queue.process();
2024-10-31 12:44:19 +05:00
}
void EventLoopImplementationWindows::quit(int code)
{
m_exit_requested = true;
m_exit_code = code;
}
void EventLoopImplementationWindows::post_event(EventReceiver& receiver, NonnullOwnPtr<Event>&& event)
{
m_thread_event_queue.post_event(receiver, move(event));
if (&m_thread_event_queue != &ThreadEventQueue::current())
wake();
}
void EventLoopImplementationWindows::wake()
{
SetEvent(m_wake_event);
2024-10-31 12:44:19 +05:00
}
static int notifier_type_to_network_event(NotificationType type)
{
switch (type) {
case NotificationType::Read:
return FD_READ | FD_CLOSE | FD_ACCEPT;
2024-10-31 12:44:19 +05:00
case NotificationType::Write:
return FD_WRITE;
default:
dbgln("This notification type is not implemented: {}", (int)type);
VERIFY_NOT_REACHED();
}
}
void EventLoopManagerWindows::register_notifier(Notifier& notifier)
{
auto* thread_data = ThreadData::the();
auto& notifiers = thread_data->notifiers;
if (notifiers.contains(&notifier))
return;
2024-10-31 12:44:19 +05:00
HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL);
VERIFY(event);
int rc = WSAEventSelect(notifier.fd(), event, notifier_type_to_network_event(notifier.type()));
VERIFY(!rc);
2024-10-31 12:44:19 +05:00
auto notifier_data = make<EventLoopNotifier>();
notifier_data->type = CompletionType::Notifer;
notifier_data->notifier = &notifier;
notifier_data->m_notifier_type = notifier.type();
notifier_data->wait_event.handle = event;
NTSTATUS status = g_system.NtCreateWaitCompletionPacket(&notifier_data->wait_packet.handle, GENERIC_READ | GENERIC_WRITE, NULL);
VERIFY(NT_SUCCESS(status));
status = g_system.NtAssociateWaitCompletionPacket(notifier_data->wait_packet.handle, thread_data->iocp.handle, event, notifier_data.ptr(), NULL, 0, 0, NULL);
VERIFY(NT_SUCCESS(status));
notifiers.set(&notifier, move(notifier_data));
2024-10-31 12:44:19 +05:00
}
void EventLoopManagerWindows::unregister_notifier(Notifier& notifier)
{
auto* thread_data = ThreadData::the();
VERIFY(thread_data);
auto& notifiers = thread_data->notifiers;
auto maybe_notifier_data = notifiers.take(&notifier);
if (!maybe_notifier_data.has_value())
return;
auto notifier_data = move(maybe_notifier_data.value());
// We are removing the signalled packets since the caller no longer expects them
NTSTATUS status = g_system.NtCancelWaitCompletionPacket(notifier_data->wait_packet.handle, TRUE);
VERIFY(NT_SUCCESS(status));
// TODO: Reuse the data structure
2024-10-31 12:44:19 +05:00
}
intptr_t EventLoopManagerWindows::register_timer(EventReceiver& object, int milliseconds, bool should_reload)
2024-10-31 12:44:19 +05:00
{
VERIFY(milliseconds >= 0);
auto* thread_data = ThreadData::the();
VERIFY(thread_data);
auto& timers = thread_data->timers;
// FIXME: This is a temporary fix for issue #3641
bool manual_reset = static_cast<Timer&>(object).is_single_shot();
HANDLE timer = CreateWaitableTimer(NULL, manual_reset, NULL);
VERIFY(timer);
auto timer_data = make<EventLoopTimer>();
timer_data->type = CompletionType::Timer;
timer_data->timer.handle = timer;
timer_data->owner = object.make_weak_ptr();
timer_data->is_periodic = should_reload;
VERIFY(timer_data->timer.handle);
NTSTATUS status = g_system.NtCreateWaitCompletionPacket(&timer_data->wait_packet.handle, GENERIC_READ | GENERIC_WRITE, NULL);
VERIFY(NT_SUCCESS(status));
2024-10-31 12:44:19 +05:00
LARGE_INTEGER first_time = {};
// Measured in 0.1μs intervals, negative means starting from now
first_time.QuadPart = -10'000LL * milliseconds;
BOOL succeeded = SetWaitableTimer(timer_data->timer.handle, &first_time, should_reload ? milliseconds : 0, NULL, NULL, FALSE);
VERIFY(succeeded);
status = g_system.NtAssociateWaitCompletionPacket(timer_data->wait_packet.handle, thread_data->iocp.handle, timer_data->timer.handle, timer_data.ptr(), NULL, 0, 0, NULL);
VERIFY(NT_SUCCESS(status));
2024-10-31 12:44:19 +05:00
auto timer_id = reinterpret_cast<intptr_t>(timer_data.ptr());
VERIFY(!timers.get(timer_id).has_value());
timers.set(timer_id, move(timer_data));
return timer_id;
2024-10-31 12:44:19 +05:00
}
void EventLoopManagerWindows::unregister_timer(intptr_t timer_id)
{
if (auto* thread_data = ThreadData::the()) {
auto maybe_timer = thread_data->timers.take(timer_id);
if (!maybe_timer.has_value())
return;
auto timer = move(maybe_timer.value());
NTSTATUS status = g_system.NtCancelWaitCompletionPacket(timer->wait_packet.handle, TRUE);
VERIFY(NT_SUCCESS(status));
}
2024-10-31 12:44:19 +05:00
}
int EventLoopManagerWindows::register_signal([[maybe_unused]] int signal_number, [[maybe_unused]] Function<void(int)> handler)
2024-10-31 12:44:19 +05:00
{
dbgln("Core::EventLoopManagerWindows::register_signal() is not implemented");
VERIFY_NOT_REACHED();
}
void EventLoopManagerWindows::unregister_signal([[maybe_unused]] int handler_id)
2024-10-31 12:44:19 +05:00
{
dbgln("Core::EventLoopManagerWindows::unregister_signal() is not implemented");
VERIFY_NOT_REACHED();
}
void EventLoopManagerWindows::did_post_event()
{
}
NonnullOwnPtr<EventLoopImplementation> EventLoopManagerWindows::make_implementation()
{
return make<EventLoopImplementationWindows>();
}
}