LibCore: Remove nested EventLoop support

Replace the per-thread EventLoop stack with a single thread-local
pointer. Constructing an EventLoop now requires that the current thread
does not already have one, and exec() and spin_until() operate only on
that current loop instead of pushing a temporary nested loop.

LibLine was the last user that needed nested Core event loops, so the
stack allocation and pthread cleanup machinery can go away.
This commit is contained in:
Andreas Kling 2026-06-04 18:58:06 +02:00 committed by Andreas Kling
parent dfb2656d9c
commit 02b205361d
2 changed files with 13 additions and 69 deletions

View file

@ -9,56 +9,20 @@
#include <AK/Assertions.h>
#include <AK/Badge.h>
#include <AK/Platform.h>
#include <AK/Vector.h>
#include <LibCore/EventLoop.h>
#include <LibCore/EventLoopImplementation.h>
#include <LibCore/EventReceiver.h>
#include <LibCore/Promise.h>
#include <LibCore/ThreadEventQueue.h>
#ifndef AK_OS_WINDOWS
# include <pthread.h>
#endif
namespace Core {
namespace {
#ifndef AK_OS_WINDOWS
static pthread_key_t s_event_loop_stack_key;
static pthread_once_t s_event_loop_stack_key_once = PTHREAD_ONCE_INIT;
static void destroy_event_loop_stack(void* value)
EventLoop*& current_event_loop()
{
delete static_cast<Vector<EventLoop&>*>(value);
}
static void initialize_event_loop_stack_key()
{
VERIFY(pthread_key_create(&s_event_loop_stack_key, destroy_event_loop_stack) == 0);
}
static void ensure_event_loop_stack_key()
{
VERIFY(pthread_once(&s_event_loop_stack_key_once, initialize_event_loop_stack_key) == 0);
}
#endif
Vector<EventLoop&>*& event_loop_stack_uninitialized()
{
thread_local Vector<EventLoop&>* s_event_loop_stack = nullptr;
return s_event_loop_stack;
}
Vector<EventLoop&>& event_loop_stack()
{
auto& the_stack = event_loop_stack_uninitialized();
if (the_stack == nullptr) {
the_stack = new Vector<EventLoop&>();
#ifndef AK_OS_WINDOWS
ensure_event_loop_stack_key();
VERIFY(pthread_setspecific(s_event_loop_stack_key, the_stack) == 0);
#endif
}
return *the_stack;
thread_local EventLoop* s_current_event_loop = nullptr;
return s_current_event_loop;
}
}
@ -66,31 +30,28 @@ Vector<EventLoop&>& event_loop_stack()
EventLoop::EventLoop()
: m_impl(EventLoopManager::the().make_implementation())
{
if (event_loop_stack().is_empty()) {
event_loop_stack().append(*this);
}
VERIFY(!current_event_loop());
current_event_loop() = this;
}
EventLoop::~EventLoop()
{
if (m_weak)
m_weak->revoke();
if (!event_loop_stack().is_empty() && &event_loop_stack().last() == this) {
event_loop_stack().take_last();
}
if (current_event_loop() == this)
current_event_loop() = nullptr;
}
bool EventLoop::is_running()
{
auto& stack = event_loop_stack_uninitialized();
return stack != nullptr && !stack->is_empty();
return current_event_loop() != nullptr;
}
EventLoop& EventLoop::current()
{
if (event_loop_stack().is_empty())
if (!current_event_loop())
dbgln("No EventLoop is present, unable to return current one!");
return event_loop_stack().last();
return *current_event_loop();
}
NonnullRefPtr<WeakEventLoopReference> EventLoop::current_weak()
@ -111,27 +72,15 @@ bool EventLoop::was_exit_requested()
return m_impl->was_exit_requested();
}
struct EventLoopPusher {
public:
EventLoopPusher(EventLoop& event_loop)
{
event_loop_stack().append(event_loop);
}
~EventLoopPusher()
{
event_loop_stack().take_last();
}
};
int EventLoop::exec()
{
EventLoopPusher pusher(*this);
VERIFY(current_event_loop() == this);
return m_impl->exec();
}
void EventLoop::spin_until(Function<bool()> goal_condition)
{
EventLoopPusher pusher(*this);
VERIFY(current_event_loop() == this);
while (!goal_condition())
pump();
}

View file

@ -31,10 +31,7 @@ class WeakEventLoopReference;
// Event loops, through select(), allow programs to "go to sleep" for most of their runtime until some event happens.
// EventLoop is too expensive to use in realtime scenarios (read: audio) where even the time required by a single select() system call is too large and unpredictable.
//
// There is at most one running event loop per thread.
// Another event loop can be started while another event loop is already running; that new event loop will take over for the other event loop.
// This is mainly used in LibGUI, where each modal window stacks another event loop until it is closed.
// However, that means you need to be careful with storing the current event loop, as it might already be gone at the time of use.
// There is at most one event loop per thread.
// Event loops currently handle these kinds of events:
// - Deferred invocations caused by various objects. These are just a generic way of telling the EventLoop to run some function as soon as possible at a later point.
// - Timers, which repeatedly (or once after a delay) run a function on the EventLoop. Note that timers are not super accurate.
@ -48,8 +45,6 @@ class CORE_API EventLoop {
AK_MAKE_NONCOPYABLE(EventLoop);
private:
friend struct EventLoopPusher;
public:
enum class WaitMode {
WaitForEvents,