From 4cb7743915b0a297bc43b98ca5201f8861b04d3b Mon Sep 17 00:00:00 2001 From: ayeteadoe Date: Sat, 22 Nov 2025 15:02:26 -0800 Subject: [PATCH] LibCore: Expose register_process and implement for Windows event loop This is the closest Windows equivalent to integrating process exit handlers into the event loop. Linux could also integrate register_process() into the poll() based Unix event loop via the SYS_pidfd_open syscall; however, macOS requires a kqueue. So for now register_process will only be used by Windows to implement WebView::ProcessMonitor. Co-authored-by: Aliaksandr Kalenik --- Libraries/LibCore/EventLoop.cpp | 10 +++ Libraries/LibCore/EventLoop.h | 4 + Libraries/LibCore/EventLoopImplementation.h | 3 + .../EventLoopImplementationWindows.cpp | 75 +++++++++++++++++++ .../LibCore/EventLoopImplementationWindows.h | 3 + 5 files changed, 95 insertions(+) diff --git a/Libraries/LibCore/EventLoop.cpp b/Libraries/LibCore/EventLoop.cpp index fa4b7a897b..28e9ac4b6c 100644 --- a/Libraries/LibCore/EventLoop.cpp +++ b/Libraries/LibCore/EventLoop.cpp @@ -141,6 +141,16 @@ void EventLoop::unregister_notifier(Badge, Notifier& notifier) EventLoopManager::the().unregister_notifier(notifier); } +void EventLoop::register_process(pid_t pid, ESCAPING Function exit_handler) +{ + EventLoopManager::the().register_process(pid, move(exit_handler)); +} + +void EventLoop::unregister_process(pid_t pid) +{ + EventLoopManager::the().unregister_process(pid); +} + void EventLoop::wake() { m_impl->wake(); diff --git a/Libraries/LibCore/EventLoop.h b/Libraries/LibCore/EventLoop.h index 5e7f71b4c5..02c1d2a594 100644 --- a/Libraries/LibCore/EventLoop.h +++ b/Libraries/LibCore/EventLoop.h @@ -88,6 +88,10 @@ public: static int register_signal(int signo, ESCAPING Function handler); static void unregister_signal(int handler_id); + // Invokes the specified handler when the process exits + static void register_process(pid_t pid, ESCAPING Function exit_handler); + static void unregister_process(pid_t pid); + static bool is_running(); static EventLoop& current(); static NonnullRefPtr current_weak(); diff --git a/Libraries/LibCore/EventLoopImplementation.h b/Libraries/LibCore/EventLoopImplementation.h index 80cec06700..32eafea0ce 100644 --- a/Libraries/LibCore/EventLoopImplementation.h +++ b/Libraries/LibCore/EventLoopImplementation.h @@ -36,6 +36,9 @@ public: virtual int register_signal(int signal_number, Function handler) = 0; virtual void unregister_signal(int handler_id) = 0; + virtual void register_process([[maybe_unused]] pid_t pid, [[maybe_unused]] ESCAPING Function exit_handler) { VERIFY_NOT_REACHED(); } + virtual void unregister_process([[maybe_unused]] pid_t pid) { VERIFY_NOT_REACHED(); } + protected: EventLoopManager(); }; diff --git a/Libraries/LibCore/EventLoopImplementationWindows.cpp b/Libraries/LibCore/EventLoopImplementationWindows.cpp index 3064605fdb..57d011ea96 100644 --- a/Libraries/LibCore/EventLoopImplementationWindows.cpp +++ b/Libraries/LibCore/EventLoopImplementationWindows.cpp @@ -17,6 +17,7 @@ #include #include #include +#include struct OwnHandle { HANDLE handle = NULL; @@ -66,6 +67,7 @@ enum class CompletionType : u8 { Wake, Timer, Notifer, + Process, }; struct CompletionPacket { @@ -101,6 +103,15 @@ struct EventLoopNotifier final : CompletionPacket { OwnHandle wait_event; }; +struct EventLoopProcess final : CompletionPacket { + ~EventLoopProcess() = default; + + OwnHandle process; + pid_t pid; + Function exit_handler; + OwnHandle jobobject; +}; + struct ThreadData { static ThreadData* the() { @@ -141,6 +152,8 @@ struct ThreadData { NonnullOwnPtr wake_data; }; +static Threading::MutexProtected>> s_processes; + EventLoopImplementationWindows::EventLoopImplementationWindows() : m_wake_event(ThreadData::the()->wake_data->wait_event.handle) { @@ -211,6 +224,21 @@ size_t EventLoopImplementationWindows::pump(PumpMode pump_mode) VERIFY(NT_SUCCESS(status)); continue; } + if (packet->type == CompletionType::Process) { + auto* process_data = static_cast(packet); + pid_t const process_id = process_data->pid; + // NOTE: This may seem like the incorrect parameter, but https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_associate_completion_port + // states that this field represents the event type indicator + DWORD const event_type = entry.dwNumberOfBytesTransferred; + if (reinterpret_cast(entry.lpOverlapped) == process_id && (event_type == JOB_OBJECT_MSG_EXIT_PROCESS || event_type == JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)) { + Optional> owned_process = s_processes.with_locked([&](auto& processes) { + return processes.take(process_id); + }); + if (owned_process.has_value()) + owned_process.release_value()->exit_handler(process_id); + } + continue; + } VERIFY_NOT_REACHED(); } } else { @@ -352,6 +380,53 @@ void EventLoopManagerWindows::unregister_signal([[maybe_unused]] int handler_id) VERIFY_NOT_REACHED(); } +void EventLoopManagerWindows::register_process(pid_t pid, ESCAPING Function exit_handler) +{ + auto* thread_data = ThreadData::the(); + VERIFY(thread_data); + + s_processes.with_locked([&](auto& processes) { + if (processes.contains(pid)) + return; + + HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + VERIFY(process_handle); + + HANDLE job_object_handle = CreateJobObject(nullptr, nullptr); + VERIFY(job_object_handle); + + BOOL succeeded = AssignProcessToJobObject(job_object_handle, process_handle); + VERIFY(succeeded); + + auto process_data = make(); + process_data->type = CompletionType::Process; + process_data->process.handle = process_handle; + process_data->pid = pid; + process_data->exit_handler = move(exit_handler); + process_data->jobobject.handle = job_object_handle; + + JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp = { .CompletionKey = process_data.ptr(), .CompletionPort = thread_data->iocp.handle }; + succeeded = SetInformationJobObject(job_object_handle, JobObjectAssociateCompletionPortInformation, &joacp, sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT)); + VERIFY(succeeded); + + processes.set(pid, move(process_data)); + }); +} + +void EventLoopManagerWindows::unregister_process(pid_t pid) +{ + auto maybe_process = s_processes.with_locked([&](auto& processes) { + return processes.take(pid); + }); + if (!maybe_process.has_value()) + return; + + auto process_data = maybe_process.release_value(); + JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp = { .CompletionKey = process_data, .CompletionPort = nullptr }; + BOOL succeeded = SetInformationJobObject(process_data->jobobject.handle, JobObjectAssociateCompletionPortInformation, &joacp, sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT)); + VERIFY(succeeded); +} + void EventLoopManagerWindows::did_post_event() { } diff --git a/Libraries/LibCore/EventLoopImplementationWindows.h b/Libraries/LibCore/EventLoopImplementationWindows.h index 5402c663cc..7dfce8a47a 100644 --- a/Libraries/LibCore/EventLoopImplementationWindows.h +++ b/Libraries/LibCore/EventLoopImplementationWindows.h @@ -27,6 +27,9 @@ public: virtual int register_signal(int signal_number, Function handler) override; virtual void unregister_signal(int handler_id) override; + + virtual void register_process(pid_t pid, ESCAPING Function exit_handler) override; + virtual void unregister_process(pid_t pid) override; }; class EventLoopImplementationWindows final : public EventLoopImplementation {