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'
It's generally considered a security issue to use non-format string
literals. We would likely just crash in practice, but let's avoid the
issue altogether.
This is a homegrown implementation that wasn't actually used in
dependent classes. If this is needed in the future, using OpenSSL would
probably be a better option.
LibCore's list of ignored header files for Swift was missing the Apple
only files on non-Apple platforms. Additionally, any generic glue code
cannot use -fobjc-arc, so we need to rely on -fblocks only.
Previously, we only returned the first result that looked like an IPv6
or IPv4 address.
This cropped up when attempting to connect to https://cxbyte.me/ whilst
IPv6 on the server wasn't working. Since we only returned the first
result, which happened to be the IPv6 address, we wasn't able to
connect.
Returning all results allows curl to attempt to connect to a different
IP if one of them isn't working, and potentially make a successful
connection.
This removes the use of StringBuilder::OutputType (which was ByteString,
and only used by the JSON classes). And it removes the StringBuilder
template parameter from the serialization methods; this was only ever
used with StringBuilder, so a template is pretty overkill here.
Before this commit, LibCore/System.h exposed only part of
System::stat API on Windows. Namely, users of Core::System::stat
had to #include <dirent.h> in order to check the return value of stat.
It is OK for low-level libs like LibCore/LibFileSystem, but
S_ISDIR is also used in LibWeb\Loader\GeneratedPagesLoader.cpp.
We want to avoid platform #ifdefs in LibWeb.
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.
Windows flavor of non-blocking IO, overlapped IO, differs from that on
Linux. On Windows, the OS handles writing to overlapped buffer, while
on Linux user must do it manually.
Additionally, we can only have overlapped sockets because it is the
requirement to be able to wait on them - WSAEventSelect automatically
sets socket to nonblocking mode.
So we end up emulating Linux-nonblocking sockets with
Windows-nonblocking sockets.
Pending IO state (ERROR_IO_PENDING) must not escape read/write
functions. If that happens, all synchronization like WSAPoll and
WaitForMultipleObjects stops working (WaitForMultipleObjects stops
working because with overlapped IO you are supposed to wait on an event
in OVERLAPPED structure, while we are waiting on WSA Event, see
EventLoopImplementationWindows.cpp).
Incorrect behavior of CreateProcess:
CreateProcess("a.exe", "b c", ...) ->
a.exe::main::argv == ["b", "c"] (wrong)
CreateProcess(0, "a.exe b c", ...) ->
a.exe::main::argv == ["a.exe", "b", "c"] (right)
https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args
"If you use both the first and second arguments (lpApplicationName and
lpCommandLine), argv[0] may not be the executable name."
This means first argument of CreateProcess should never be used.
-----------------------------------------------------------------------
Searching for executable in path is suppressed now by prepending "./"
Additional bonus of the new code: .exe extension does not need to be
specified.
_O_OBTAIN_DIR flag makes _open use FILE_FLAG_BACKUP_SEMANTICS in
CreateFile call.
FILE_FLAG_BACKUP_SEMANTICS is required to open directory handles.
For ordinary files FILE_FLAG_BACKUP_SEMANTICS overrides file security
checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME
privileges, see https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
Fonts on Windows are stored only in %WINDIR%\Fonts and
%LOCALAPPDATA%\Microsoft\Windows\Fonts, see https://stackoverflow.com/a/67078786
And system_data_directories() is not implemented on Windows yet.