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.
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.
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'
Bring back d6080d1fdc with a missing check
whether underlying socket is closed, before accessing `fd()` that is
optional and empty in case of closed socket.
This allows us to remove the BoundFunction::m_name field, which we
were initializing with a formatted FlyString on every function binding,
despite never using it for anything.
With this change TransportSocket becomes capable of sending large
messages without relying on workarounds, such as sending the message as
a shared memory file descriptor when it can't fully fit into the socket
buffer.
It's implemented by combining all enqueued messages into two buffers:
one for bytes and another for fds, and repeatedly attempts to write them
in smaller chunks, waiting for the socket to become writable again if
the receiver needs time to consume the data.
Another significant improvement brought by this change is that we no
longer drop messages queued for sending if the socket doesn't become
writable after a 100ms timeout. Instead, we return the message to the
send buffer and continue waiting for the socket to become writable.
FJCVTZS (Floating-point Javascript Convert to Signed fixed-point,
rounding toward Zero) does exactly what we need for ToInt32 in the
JavaScript specification.
This isn't world-changing, but it does give a ~2% boost on compute-
heavy benchmarks like JetStream, so we should obviously use it.
The fast path of to_i32() can be neatly inlined everywhere, and we still
have to_i32_slow_case() for non-trivial conversions.
For to_u32(), it really can just be implemented as a static cast to i32!
See the linked spec issue for more details. The MediaList can be null
internally, and this was upsetting GCC as it meant our bindings code
was dereferencing a null pointer.
The regression in the "conditional-CSSGroupingRule" test is we now fail
the "inserting an `@import`" subtests differently and the subtests
aren't independent. Specifically, we don't yet implement the checks in
`CSSRuleList::insert_a_css_rule()` that reject certain rules from being
inserted. Previously we didn't insert the `@import` rule because we
failed to parse its relative URL. Now we parse it correctly, we end up
inserting it.
When `CSSRuleList::remove_a_css_rule()` is called, the removed rule has
its parent style sheet set to null. We shouldn't try to fetch an import
in this case.
It's possible to parse an `@import` rule that isn't attached to a
document. We only actually need it to have one when fetching the linked
style sheet, and that should only happen when the CSSImportRule is
attached to a document. So, we can just accept a null pointer when
constructing it.
We relied on that Document to get the Realm, so pass that in as a
separate parameter.
This is ad-hoc, and the spec doesn't seem to tell us what to actually do
here. Without this, following the spec steps for loading relative
`@import` URLs from a `<style>` tag always fails, because that uses the
parent style sheet's location as the base URL.