LibCore: Add thread-safe weak deferred_invoke()

Add a thread-safe deferred_invoke() API on WeakEventLoopReference that
queues work onto the owning thread's event queue and wakes that thread
via EventLoopManager hooks. This avoids calling wake() from foreign
threads during teardown.

Implement current_thread_handle()/wake_thread() in each backend and
track per-thread data so handles are validated before waking:

- Unix: wake via per-thread wake pipe
- Windows: wake via thread wake event
- macOS: wake via stored CFRunLoopRef
- Qt: wake via event target or QEventLoop::wakeUp()
- Android: wake via stored ALooper
This commit is contained in:
Andreas Kling 2026-01-23 17:16:40 +01:00 committed by Andreas Kling
parent 367296fce6
commit 96ce468b60
13 changed files with 220 additions and 0 deletions

View file

@ -36,6 +36,8 @@ Vector<EventLoop&>& event_loop_stack()
EventLoop::EventLoop()
: m_impl(EventLoopManager::the().make_implementation())
{
m_thread_event_queue = &ThreadEventQueue::current();
m_thread_handle = EventLoopManager::the().current_thread_handle();
if (event_loop_stack().is_empty()) {
event_loop_stack().append(*this);
}
@ -165,12 +167,16 @@ void deferred_invoke(Function<void()> invokee)
WeakEventLoopReference::WeakEventLoopReference(EventLoop& event_loop)
: m_event_loop(&event_loop)
{
m_thread_event_queue = event_loop.m_thread_event_queue;
m_thread_handle = event_loop.m_thread_handle;
}
void WeakEventLoopReference::revoke()
{
Threading::RWLockLocker<Threading::LockMode::Write> locker { m_lock };
m_event_loop = nullptr;
m_thread_event_queue = nullptr;
m_thread_handle = 0;
}
StrongEventLoopReference WeakEventLoopReference::take()
@ -178,6 +184,20 @@ StrongEventLoopReference WeakEventLoopReference::take()
return StrongEventLoopReference(*this);
}
bool WeakEventLoopReference::deferred_invoke(Function<void()> invokee)
{
Threading::RWLockLocker<Threading::LockMode::Read> locker { m_lock };
if (!m_event_loop || !m_thread_event_queue || m_thread_handle == 0) {
// Catch use-after-revoke bugs early in debug builds.
VERIFY(m_event_loop != nullptr);
return false;
}
m_thread_event_queue->deferred_invoke(move(invokee));
EventLoopManager::the().wake_thread(m_thread_handle);
return true;
}
StrongEventLoopReference::StrongEventLoopReference(WeakEventLoopReference& event_loop_weak)
{
event_loop_weak.m_lock.lock_read();

View file

@ -16,6 +16,7 @@
#include <AK/Swift.h>
#include <AK/Time.h>
#include <LibCore/Event.h>
#include <LibCore/EventLoopImplementation.h>
#include <LibCore/Forward.h>
#include <LibThreading/RWLock.h>
@ -49,6 +50,7 @@ class EventLoop {
private:
friend struct EventLoopPusher;
friend class WeakEventLoopReference;
public:
enum class WaitMode {
@ -99,6 +101,8 @@ public:
private:
NonnullOwnPtr<EventLoopImplementation> m_impl;
RefPtr<WeakEventLoopReference> m_weak;
ThreadEventQueue* m_thread_event_queue { nullptr };
EventLoopThreadHandle m_thread_handle { 0 };
} SWIFT_UNSAFE_REFERENCE;
class StrongEventLoopReference;
@ -106,6 +110,7 @@ class StrongEventLoopReference;
class WeakEventLoopReference : public AtomicRefCounted<WeakEventLoopReference> {
public:
StrongEventLoopReference take();
bool deferred_invoke(ESCAPING Function<void()>);
private:
friend class EventLoop;
@ -117,6 +122,8 @@ private:
EventLoop* m_event_loop;
Threading::RWLock m_lock;
ThreadEventQueue* m_thread_event_queue { nullptr };
EventLoopThreadHandle m_thread_handle { 0 };
};
class StrongEventLoopReference {

View file

@ -7,12 +7,14 @@
#pragma once
#include <AK/Function.h>
#include <AK/Types.h>
#include <LibCore/Forward.h>
namespace Core {
class EventLoopImplementation;
class ThreadEventQueue;
using EventLoopThreadHandle = FlatPtr;
class EventLoopManager {
public:
@ -30,6 +32,8 @@ public:
virtual void unregister_notifier(Notifier&) = 0;
virtual void did_post_event() = 0;
virtual EventLoopThreadHandle current_thread_handle() = 0;
virtual void wake_thread(EventLoopThreadHandle) = 0;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual int register_signal(int signal_number, Function<void(int)> handler) = 0;

View file

@ -5,6 +5,7 @@
*/
#include <AK/BinaryHeap.h>
#include <AK/HashTable.h>
#include <AK/Singleton.h>
#include <AK/TemporaryChange.h>
#include <AK/Time.h>
@ -30,6 +31,7 @@ struct ThreadData;
class TimeoutSet;
HashMap<pthread_t, ThreadData*> s_thread_data;
HashTable<ThreadData*> s_thread_data_by_ptr;
Threading::RWLock s_thread_data_lock;
thread_local pthread_t s_thread_id;
thread_local OwnPtr<ThreadData> s_this_thread_data;
@ -232,6 +234,7 @@ struct ThreadData {
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data.set(s_thread_id, s_this_thread_data.ptr());
s_thread_data_by_ptr.set(s_this_thread_data.ptr());
} else {
data = s_this_thread_data.ptr();
}
@ -244,6 +247,17 @@ struct ThreadData {
return s_thread_data.get(thread_id).value_or(nullptr);
}
static ThreadData* for_handle(EventLoopThreadHandle handle)
{
if (handle == 0)
return nullptr;
auto* ptr = reinterpret_cast<ThreadData*>(handle);
Threading::RWLockLocker<Threading::LockMode::Read> locker(s_thread_data_lock);
if (!s_thread_data_by_ptr.contains(ptr))
return nullptr;
return ptr;
}
ThreadData()
{
pid = getpid();
@ -265,6 +279,7 @@ struct ThreadData {
{
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data.remove(s_thread_id);
s_thread_data_by_ptr.remove(this);
}
Threading::Mutex mutex;
@ -676,6 +691,20 @@ void EventLoopManagerUnix::did_post_event()
{
}
EventLoopThreadHandle EventLoopManagerUnix::current_thread_handle()
{
return reinterpret_cast<EventLoopThreadHandle>(&ThreadData::the());
}
void EventLoopManagerUnix::wake_thread(EventLoopThreadHandle handle)
{
auto* thread_data = ThreadData::for_handle(handle);
if (!thread_data)
return;
int wake_event = 0;
(void)Core::System::write(thread_data->wake_pipe_fds[1], { &wake_event, sizeof(wake_event) });
}
EventLoopManagerUnix::~EventLoopManagerUnix() = default;
NonnullOwnPtr<EventLoopImplementation> EventLoopManagerUnix::make_implementation()

View file

@ -25,6 +25,8 @@ public:
virtual void unregister_notifier(Notifier&) override;
virtual void did_post_event() override;
virtual EventLoopThreadHandle current_thread_handle() override;
virtual void wake_thread(EventLoopThreadHandle) override;
virtual int register_signal(int signal_number, Function<void(int)> handler) override;
virtual void unregister_signal(int handler_id) override;

View file

@ -10,6 +10,7 @@
#include <AK/Assertions.h>
#include <AK/Diagnostics.h>
#include <AK/HashMap.h>
#include <AK/HashTable.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Windows.h>
#include <LibCore/EventLoopImplementationWindows.h>
@ -17,6 +18,7 @@
#include <LibCore/ThreadEventQueue.h>
#include <LibCore/Timer.h>
#include <LibThreading/Mutex.h>
#include <LibThreading/RWLock.h>
struct OwnHandle {
HANDLE handle = NULL;
@ -62,6 +64,10 @@ constexpr bool IsHashCompatible<HANDLE, OwnHandle> = true;
namespace Core {
struct ThreadData;
static HashTable<ThreadData*> s_thread_data_by_ptr;
static Threading::RWLock s_thread_data_lock;
enum class CompletionType : u8 {
Wake,
Timer,
@ -110,9 +116,23 @@ struct ThreadData {
return nullptr;
}
static ThreadData* for_handle(EventLoopThreadHandle handle)
{
if (handle == 0)
return nullptr;
auto* ptr = reinterpret_cast<ThreadData*>(handle);
Threading::RWLockLocker<Threading::LockMode::Read> locker(s_thread_data_lock);
if (!s_thread_data_by_ptr.contains(ptr))
return nullptr;
return ptr;
}
ThreadData()
: wake_data(make<EventLoopWake>())
{
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data_by_ptr.set(this);
wake_data->type = CompletionType::Wake;
wake_data->wait_event.handle = CreateEvent(NULL, FALSE, FALSE, NULL);
@ -129,6 +149,8 @@ struct ThreadData {
{
NTSTATUS status = g_system.NtCancelWaitCompletionPacket(wake_data->wait_packet.handle, TRUE);
VERIFY(NT_SUCCESS(status));
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data_by_ptr.remove(this);
}
OwnHandle iocp;
@ -355,6 +377,20 @@ void EventLoopManagerWindows::did_post_event()
{
}
EventLoopThreadHandle EventLoopManagerWindows::current_thread_handle()
{
auto* thread_data = ThreadData::the();
return reinterpret_cast<EventLoopThreadHandle>(thread_data);
}
void EventLoopManagerWindows::wake_thread(EventLoopThreadHandle handle)
{
auto* thread_data = ThreadData::for_handle(handle);
if (!thread_data)
return;
SetEvent(thread_data->wake_data->wait_event.handle);
}
NonnullOwnPtr<EventLoopImplementation> EventLoopManagerWindows::make_implementation()
{
return make<EventLoopImplementationWindows>();

View file

@ -24,6 +24,8 @@ public:
virtual void unregister_notifier(Notifier&) override;
virtual void did_post_event() override;
virtual EventLoopThreadHandle current_thread_handle() override;
virtual void wake_thread(EventLoopThreadHandle) override;
virtual int register_signal(int signal_number, Function<void(int)> handler) override;
virtual void unregister_signal(int handler_id) override;

View file

@ -6,9 +6,11 @@
#include "ALooperEventLoopImplementation.h"
#include "JNIHelpers.h"
#include <AK/HashTable.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h>
#include <LibThreading/RWLock.h>
#include <android/log.h>
#include <android/looper.h>
#include <fcntl.h>
@ -16,12 +18,34 @@
namespace Ladybird {
static Threading::RWLock s_thread_data_lock;
static HashTable<EventLoopThreadData*> s_thread_data_by_ptr;
EventLoopThreadData& EventLoopThreadData::the()
{
static thread_local EventLoopThreadData s_thread_data { {}, {}, &Core::ThreadEventQueue::current() };
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data_by_ptr.set(&s_thread_data);
return s_thread_data;
}
EventLoopThreadData::~EventLoopThreadData()
{
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data_by_ptr.remove(this);
}
EventLoopThreadData* EventLoopThreadData::for_handle(Core::EventLoopThreadHandle handle)
{
if (handle == 0)
return nullptr;
auto* ptr = reinterpret_cast<EventLoopThreadData*>(handle);
Threading::RWLockLocker<Threading::LockMode::Read> locker(s_thread_data_lock);
if (!s_thread_data_by_ptr.contains(ptr))
return nullptr;
return ptr;
}
static ALooperEventLoopImplementation& current_impl()
{
return as<ALooperEventLoopImplementation>(Core::EventLoop::current().impl());
@ -127,6 +151,19 @@ void ALooperEventLoopManager::did_post_event()
(void)write(m_pipe[1], &msg, sizeof(msg));
}
Core::EventLoopThreadHandle ALooperEventLoopManager::current_thread_handle()
{
return reinterpret_cast<Core::EventLoopThreadHandle>(&EventLoopThreadData::the());
}
void ALooperEventLoopManager::wake_thread(Core::EventLoopThreadHandle handle)
{
auto* thread_data = EventLoopThreadData::for_handle(handle);
if (!thread_data || !thread_data->looper)
return;
ALooper_wake(thread_data->looper);
}
int looper_callback(int fd, int events, void* data)
{
auto& manager = *static_cast<ALooperEventLoopManager*>(data);
@ -146,6 +183,7 @@ ALooperEventLoopImplementation::ALooperEventLoopImplementation()
, m_thread_data(&EventLoopThreadData::the())
{
ALooper_acquire(m_event_loop);
m_thread_data->looper = m_event_loop;
}
ALooperEventLoopImplementation::~ALooperEventLoopImplementation()

View file

@ -30,6 +30,8 @@ public:
virtual void unregister_notifier(Core::Notifier&) override;
virtual void did_post_event() override;
virtual Core::EventLoopThreadHandle current_thread_handle() override;
virtual void wake_thread(Core::EventLoopThreadHandle) override;
Function<void()> on_did_post_event;
@ -53,10 +55,13 @@ struct TimerData {
struct EventLoopThreadData {
static EventLoopThreadData& the();
static EventLoopThreadData* for_handle(Core::EventLoopThreadHandle);
~EventLoopThreadData();
HashMap<long, TimerData> timers;
HashTable<Core::Notifier*> notifiers;
Core::ThreadEventQueue* thread_queue = nullptr;
ALooper* looper { nullptr };
};
class ALooperEventLoopImplementation : public Core::EventLoopImplementation {

View file

@ -23,6 +23,8 @@ public:
virtual void unregister_notifier(Core::Notifier&) override;
virtual void did_post_event() override;
virtual Core::EventLoopThreadHandle current_thread_handle() override;
virtual void wake_thread(Core::EventLoopThreadHandle) override;
virtual int register_signal(int, Function<void(int)>) override;
virtual void unregister_signal(int) override;

View file

@ -6,6 +6,7 @@
#include <AK/Assertions.h>
#include <AK/HashMap.h>
#include <AK/HashTable.h>
#include <AK/IDAllocator.h>
#include <AK/Singleton.h>
#include <AK/TemporaryChange.h>
@ -27,6 +28,7 @@ namespace Ladybird {
struct ThreadData;
static thread_local OwnPtr<ThreadData> s_this_thread_data;
static HashMap<pthread_t, ThreadData*> s_thread_data;
static HashTable<ThreadData*> s_thread_data_by_ptr;
static thread_local pthread_t s_thread_id;
static Threading::RWLock s_thread_data_lock;
@ -37,8 +39,10 @@ struct ThreadData {
s_thread_id = pthread_self();
if (!s_this_thread_data) {
s_this_thread_data = make<ThreadData>();
s_this_thread_data->run_loop = CFRunLoopGetCurrent();
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data.set(s_thread_id, s_this_thread_data);
s_thread_data_by_ptr.set(s_this_thread_data.ptr());
}
return *s_this_thread_data;
}
@ -49,10 +53,22 @@ struct ThreadData {
return s_thread_data.get(thread_id).value_or(nullptr);
}
static ThreadData* for_handle(Core::EventLoopThreadHandle handle)
{
if (handle == 0)
return nullptr;
auto* ptr = reinterpret_cast<ThreadData*>(handle);
Threading::RWLockLocker<Threading::LockMode::Read> locker(s_thread_data_lock);
if (!s_thread_data_by_ptr.contains(ptr))
return nullptr;
return ptr;
}
~ThreadData()
{
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data.remove(s_thread_id);
s_thread_data_by_ptr.remove(this);
}
IDAllocator timer_id_allocator;
@ -63,6 +79,8 @@ struct ThreadData {
CFRunLoopRef run_loop { nullptr };
};
HashMap<Core::Notifier*, NotifierState> notifiers;
CFRunLoopRef run_loop { nullptr };
CFRunLoopSourceRef deferred_source { nullptr };
};
class SignalHandlers : public RefCounted<SignalHandlers> {
@ -251,6 +269,7 @@ struct EventLoopImplementationMacOS::Impl {
EventLoopImplementationMacOS::EventLoopImplementationMacOS()
: m_impl(make<Impl>(*this))
{
ThreadData::the().deferred_source = m_impl->deferred_source;
}
EventLoopImplementationMacOS::~EventLoopImplementationMacOS() = default;
@ -391,6 +410,21 @@ void EventLoopManagerMacOS::did_post_event()
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}
Core::EventLoopThreadHandle EventLoopManagerMacOS::current_thread_handle()
{
return reinterpret_cast<Core::EventLoopThreadHandle>(&ThreadData::the());
}
void EventLoopManagerMacOS::wake_thread(Core::EventLoopThreadHandle handle)
{
auto* thread_data = ThreadData::for_handle(handle);
if (!thread_data || !thread_data->run_loop)
return;
if (thread_data->deferred_source)
CFRunLoopSourceSignal(thread_data->deferred_source);
CFRunLoopWakeUp(thread_data->run_loop);
}
static void handle_signal(CFFileDescriptorRef f, CFOptionFlags callback_types, void* info)
{
VERIFY(callback_types & kCFFileDescriptorReadCallBack);

View file

@ -5,6 +5,7 @@
*/
#include <AK/HashMap.h>
#include <AK/HashTable.h>
#include <AK/IDAllocator.h>
#include <AK/Singleton.h>
#include <AK/TemporaryChange.h>
@ -31,6 +32,7 @@ namespace Ladybird {
struct ThreadData;
static thread_local OwnPtr<ThreadData> s_this_thread_data;
static HashMap<pthread_t, ThreadData*> s_thread_data;
static HashTable<ThreadData*> s_thread_data_by_ptr;
static Threading::RWLock s_thread_data_lock;
static thread_local Optional<pthread_t> s_thread_id;
@ -43,6 +45,7 @@ struct ThreadData {
s_this_thread_data = make<ThreadData>();
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data.set(s_thread_id.value(), s_this_thread_data.ptr());
s_thread_data_by_ptr.set(s_this_thread_data.ptr());
}
return *s_this_thread_data;
}
@ -53,14 +56,28 @@ struct ThreadData {
return s_thread_data.get(thread_id).value_or(nullptr);
}
static ThreadData* for_handle(Core::EventLoopThreadHandle handle)
{
if (handle == 0)
return nullptr;
auto* ptr = reinterpret_cast<ThreadData*>(handle);
Threading::RWLockLocker<Threading::LockMode::Read> locker(s_thread_data_lock);
if (!s_thread_data_by_ptr.contains(ptr))
return nullptr;
return ptr;
}
~ThreadData()
{
Threading::RWLockLocker<Threading::LockMode::Write> locker(s_thread_data_lock);
s_thread_data.remove(s_thread_id.value());
s_thread_data_by_ptr.remove(this);
}
Threading::Mutex mutex;
HashMap<Core::Notifier*, NonnullOwnPtr<QSocketNotifier>> notifiers;
QEventLoop* event_loop { nullptr };
bool is_main_loop { false };
};
class QtEventLoopManagerEvent final : public QEvent {
@ -208,6 +225,7 @@ static void dispatch_signal(int signal_number)
EventLoopImplementationQt::EventLoopImplementationQt()
: m_event_loop(make<QEventLoop>())
{
ThreadData::the().event_loop = m_event_loop.ptr();
}
EventLoopImplementationQt::~EventLoopImplementationQt() = default;
@ -258,6 +276,9 @@ void EventLoopImplementationQt::set_main_loop()
auto& event_loop_manager = static_cast<EventLoopManagerQt&>(Core::EventLoopManager::the());
event_loop_manager.set_main_loop_signal_notifiers({});
auto& thread_data = ThreadData::the();
thread_data.is_main_loop = true;
}
static void qt_timer_fired(Core::EventReceiver& object)
@ -381,6 +402,24 @@ void EventLoopManagerQt::did_post_event()
QCoreApplication::postEvent(m_main_thread_event_target.ptr(), new QtEventLoopManagerEvent(QtEventLoopManagerEvent::process_event_queue_event_type()));
}
Core::EventLoopThreadHandle EventLoopManagerQt::current_thread_handle()
{
return reinterpret_cast<Core::EventLoopThreadHandle>(&ThreadData::the());
}
void EventLoopManagerQt::wake_thread(Core::EventLoopThreadHandle handle)
{
auto* thread_data = ThreadData::for_handle(handle);
if (!thread_data)
return;
if (thread_data->is_main_loop) {
QCoreApplication::postEvent(m_main_thread_event_target.ptr(), new QtEventLoopManagerEvent(QtEventLoopManagerEvent::process_event_queue_event_type()));
return;
}
if (thread_data->event_loop)
thread_data->event_loop->wakeUp();
}
bool EventLoopManagerQt::event_target_received_event(Badge<EventLoopImplementationQtEventTarget>, QEvent* event)
{
if (event->type() == QtEventLoopManagerEvent::process_event_queue_event_type()) {

View file

@ -32,6 +32,8 @@ public:
virtual void unregister_notifier(Core::Notifier&) override;
virtual void did_post_event() override;
virtual Core::EventLoopThreadHandle current_thread_handle() override;
virtual void wake_thread(Core::EventLoopThreadHandle) override;
static bool event_target_received_event(Badge<EventLoopImplementationQtEventTarget>, QEvent* event);
virtual int register_signal(int, Function<void(int)>) override;