Ladybird: Add CFEventLoop implementation of register/unregister_signal

This commit is contained in:
Andrew Kaster 2024-06-27 18:25:27 -06:00 committed by Alexander Kalenik
parent a4eb46fcca
commit 99f4fce12b
Notes: sideshowbarker 2024-07-17 08:25:15 +09:00
2 changed files with 195 additions and 3 deletions

View file

@ -24,9 +24,8 @@ public:
virtual void did_post_event() override;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual int register_signal(int, Function<void(int)>) override { return 0; }
virtual void unregister_signal(int) override { }
virtual int register_signal(int, Function<void(int)>) override;
virtual void unregister_signal(int) override;
};
class CFEventLoopImplementation final : public Core::EventLoopImplementation {

View file

@ -6,6 +6,8 @@
#include <AK/Assertions.h>
#include <AK/IDAllocator.h>
#include <AK/Singleton.h>
#include <AK/TemporaryChange.h>
#include <LibCore/Event.h>
#include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h>
@ -14,6 +16,10 @@
#import <System/Cocoa.h>
#import <System/CoreFoundation.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
namespace Ladybird {
struct ThreadData {
@ -39,6 +45,147 @@ struct ThreadData {
HashMap<Core::Notifier*, CFRunLoopSourceRef> notifiers;
};
class SignalHandlers : public RefCounted<SignalHandlers> {
AK_MAKE_NONCOPYABLE(SignalHandlers);
AK_MAKE_NONMOVABLE(SignalHandlers);
public:
SignalHandlers(int signal_number, CFFileDescriptorCallBack);
~SignalHandlers();
void dispatch();
int add(Function<void(int)>&& handler);
bool remove(int handler_id);
bool is_empty() const
{
if (m_calling_handlers) {
for (auto const& handler : m_handlers_pending) {
if (handler.value)
return false; // an add is pending
}
}
return m_handlers.is_empty();
}
bool have(int handler_id) const
{
if (m_calling_handlers) {
auto it = m_handlers_pending.find(handler_id);
if (it != m_handlers_pending.end()) {
if (!it->value)
return false; // a deletion is pending
}
}
return m_handlers.contains(handler_id);
}
int m_signal_number;
void (*m_original_handler)(int);
HashMap<int, Function<void(int)>> m_handlers;
HashMap<int, Function<void(int)>> m_handlers_pending;
bool m_calling_handlers { false };
CFRunLoopSourceRef m_source { nullptr };
int m_kevent_fd = { -1 };
};
SignalHandlers::SignalHandlers(int signal_number, CFFileDescriptorCallBack handle_signal)
: m_signal_number(signal_number)
, m_original_handler(signal(signal_number, [](int) {}))
{
m_kevent_fd = kqueue();
if (m_kevent_fd < 0) {
dbgln("Unable to create kqueue to register signal {}: {}", signal_number, strerror(errno));
VERIFY_NOT_REACHED();
}
struct kevent changes = {};
EV_SET(&changes, signal_number, EVFILT_SIGNAL, EV_ADD | EV_RECEIPT, 0, 0, nullptr);
if (auto res = kevent(m_kevent_fd, &changes, 1, &changes, 1, NULL); res < 0) {
dbgln("Unable to register signal {}: {}", signal_number, strerror(errno));
VERIFY_NOT_REACHED();
}
CFFileDescriptorContext context = { 0, this, nullptr, nullptr, nullptr };
CFFileDescriptorRef kq_ref = CFFileDescriptorCreate(kCFAllocatorDefault, m_kevent_fd, FALSE, handle_signal, &context);
m_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, kq_ref, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
CFFileDescriptorEnableCallBacks(kq_ref, kCFFileDescriptorReadCallBack);
CFRelease(kq_ref);
}
SignalHandlers::~SignalHandlers()
{
CFRunLoopRemoveSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
CFRelease(m_source);
(void)::signal(m_signal_number, m_original_handler);
::close(m_kevent_fd);
}
struct SignalHandlersInfo {
HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
int next_signal_id { 0 };
};
static Singleton<SignalHandlersInfo> s_signals;
SignalHandlersInfo* signals_info()
{
return s_signals.ptr();
}
void SignalHandlers::dispatch()
{
TemporaryChange change(m_calling_handlers, true);
for (auto& handler : m_handlers)
handler.value(m_signal_number);
if (!m_handlers_pending.is_empty()) {
// Apply pending adds/removes
for (auto& handler : m_handlers_pending) {
if (handler.value) {
auto result = m_handlers.set(handler.key, move(handler.value));
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
} else {
m_handlers.remove(handler.key);
}
}
m_handlers_pending.clear();
}
}
int SignalHandlers::add(Function<void(int)>&& handler)
{
int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
if (m_calling_handlers)
m_handlers_pending.set(id, move(handler));
else
m_handlers.set(id, move(handler));
return id;
}
bool SignalHandlers::remove(int handler_id)
{
VERIFY(handler_id != 0);
if (m_calling_handlers) {
auto it = m_handlers.find(handler_id);
if (it != m_handlers.end()) {
// Mark pending remove
m_handlers_pending.set(handler_id, {});
return true;
}
it = m_handlers_pending.find(handler_id);
if (it != m_handlers_pending.end()) {
if (!it->value)
return false; // already was marked as deleted
it->value = nullptr;
return true;
}
return false;
}
return m_handlers.remove(handler_id);
}
static void post_application_event()
{
auto* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
@ -167,6 +314,52 @@ void CFEventLoopManager::did_post_event()
post_application_event();
}
static void handle_signal(CFFileDescriptorRef f, CFOptionFlags callback_types, void* info)
{
VERIFY(callback_types & kCFFileDescriptorReadCallBack);
auto* signal_handlers = static_cast<SignalHandlers*>(info);
struct kevent event { };
// returns number of events that have occurred since last call
(void)::kevent(CFFileDescriptorGetNativeDescriptor(f), nullptr, 0, &event, 1, nullptr);
CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack);
signal_handlers->dispatch();
}
int CFEventLoopManager::register_signal(int signal_number, Function<void(int)> handler)
{
VERIFY(signal_number != 0);
auto& info = *signals_info();
auto handlers = info.signal_handlers.find(signal_number);
if (handlers == info.signal_handlers.end()) {
auto signal_handlers = adopt_ref(*new SignalHandlers(signal_number, &handle_signal));
auto handler_id = signal_handlers->add(move(handler));
info.signal_handlers.set(signal_number, move(signal_handlers));
return handler_id;
} else {
return handlers->value->add(move(handler));
}
}
void CFEventLoopManager::unregister_signal(int handler_id)
{
VERIFY(handler_id != 0);
int remove_signal_number = 0;
auto& info = *signals_info();
for (auto& h : info.signal_handlers) {
auto& handlers = *h.value;
if (handlers.remove(handler_id)) {
if (handlers.is_empty())
remove_signal_number = handlers.m_signal_number;
break;
}
}
if (remove_signal_number != 0)
info.signal_handlers.remove(remove_signal_number);
}
NonnullOwnPtr<CFEventLoopImplementation> CFEventLoopImplementation::create()
{
return adopt_own(*new CFEventLoopImplementation);