Commit graph

11 commits

Author SHA1 Message Date
stasoid
17fcbce324 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-04-10 19:02:03 -06:00
stasoid
2bfed2e417 LibCore: Check for all events in EventLoopImplementationWindows::pump
This fixes the problem when none of the timers or notifiers get
executed if wake() is called frequently.

Note that calling WaitForMultipleObjects repeatedly until it fails
will not work because rapidly firing timer can get all the attention.
That's why I check every event individually with WaitForSingleObject.

This behavior matches EventLoopImplementationUnix.
2025-04-10 19:02:03 -06:00
stasoid
5ea4d26458 LibCore: Don't wait in WaitForMultipleObjects if thread event queue
has pending events in EventLoopImplementationWindows

This matches the behavior of the Linux version:
911cd4aefd/Libraries/LibCore/EventLoopImplementationUnix.cpp (L371)
2025-04-10 19:02:03 -06:00
stasoid
33457f389d LibCore: Check for null ThreadData in unregister_notifier
and unregister_timer in EventLoopManagerWindows

Destructors for thread local objects are called before destructors of
global not thread local objects.

This is a partial stack of the problem, thread_data is already
destroyed at this point:

>WebContent.exe!Core::ThreadData::the
 WebContent.exe!Core::EventLoopManagerWindows::unregister_notifier
 WebContent.exe!Core::EventLoop::unregister_notifier
 WebContent.exe!Core::Notifier::set_enabled
 WebContent.exe!Core::LocalSocket::~LocalSocket
 WebContent.exe!Requests::RequestClient::~RequestClient
 WebContent.exe!Web::`dynamic atexit destructor for 's_resource_loader'
2025-04-10 19:02:03 -06:00
Andrew Kaster
25f00b2486 LibCore: Add missing HashMap include to Windows EventLoop 2025-02-11 01:41:46 -07:00
stasoid
26825b5865 LibCore: Properly shutdown a socket on Windows
It fixes a bug in which ImageDecoder and RequestServer
do not exit because their connections don't close.

This makes the shutdown behavior match the Linux version,
which receives FD_READ | FD_HANGUP on socket close, and
TransportSocket::read_as_much_as_possible_without_blocking calls
schedule_shutdown when read from a socket returns 0 bytes.

On Windows, we have to explicitly call WIN32 shutdown to receive
notification FD_CLOSE.
2025-02-10 12:46:25 -07:00
stasoid
b77016cc34 LibCore: Consistently treat file descriptors as handles on Windows
Also:
 * implement dup and is_socket
 * implement and call init_crt_and_wsa
2025-02-05 19:27:47 -07:00
Lucas CHOLLET
e015a43b51 LibCore: Remove unused methods from EventLoop 2025-01-30 15:34:02 -07:00
stasoid
49bdda1475 AK: Simplify usage of windows.h and winsock2.h
Use <AK/Windows.h> instead of windows.h/winsock2.h
to avoid timeval-related errors.

Note: winsock2.h includes windows.h
2025-01-02 10:17:51 -07:00
R-Goc
579484e065 LibCore: Fix compilation of EventLoopImplementationWindows.cpp 2024-12-20 13:57:43 +01:00
stasoid
1c77135948 LibCore: Port EventLoop to Windows 2024-11-14 11:18:38 -07:00