LibCore — OS Abstraction and Event Loop
LibCore — OS Abstraction and Event Loop
LibCore is the POSIX abstraction layer used throughout Ladybird. It wraps file descriptors, processes, sockets, timers, and signals behind C++ classes that integrate with the event loop. It targets Linux, macOS, and (via separate implementation files) Windows and other POSIX systems.
EventLoop
Core::EventLoop is the heart of any LibCore application. It runs a cooperative, single-threaded event dispatcher: exec() enters the loop, repeatedly calling pump(), which blocks in select(2) until an event is ready, then dispatches all pending callbacks before sleeping again.Key operations on EventLoop:exec()— enters the event loop and runs untilquit()is called. Returns an exit code, so the patternreturn app.exec()is conventional in GUI entrypoints.pump(WaitMode)— processes one batch of pending events. Called byexec()in a loop. TheWaitModecontrols whetherpump()usesselect(2)to sleep until an event is ready (WaitForEvents) or polls without blocking (PollForEvents). Under the hood,pump()calls into the platform-specificEventLoopImplementationwhich invokesselect(2)on registered notifier file descriptors and the wake pipe, using the nearest timer deadline as theselect(2)timeout.deferred_invoke(callback)— schedulescallbackto run on the next event loop iteration without blocking the caller.wake()— writes to the event loop’s wake pipe so that a sleepingselect(2)call returns immediately, causing the loop to process any newly posted events. This is the safe way to signal an event loop from another thread.EventLoop::register_signal(signo, handler)— registers a POSIX signal handler that fires as a normal event on the calling thread’s event loop, avoiding the re-entrancy pitfalls of raw POSIX signal handlers.
EventLoop::current() returns the topmost loop on the calling thread.Thread safety and wake pipeEvent loops are per-thread: all global state (notifiers, timers, event loop stack) is stored in thread-local variables. To safely signal an event loop running on another thread, call wake(), which writes to an internal pipe that select(2) is watching. This causes the sleeping loop to wake and process the new event.Core::Timer
Core::Timer wraps the event loop’s timer registration behind a convenient class. Set an interval in milliseconds, attach a callback with on_timeout, and start the timer. Timers can fire once or repeatedly.Core::Notifier
Core::Notifier fires a callback whenever a file descriptor becomes readable or writable. It registers the fd with the event loop’s select(2) watch set so that the loop wakes up and dispatches the on_activation callback as soon as the fd is ready.Other LibCore Utilities
Dos and Don’ts with EventLoop
LibIPC — Type-Safe Inter-Process Communication
LibIPC — Type-Safe Inter-Process Communication
LibIPC enables Ladybird’s separate processes to communicate by exchanging typed messages over UNIX-domain socket pairs. Each service defines its own IPC protocol; the framework handles serialisation, deserialisation, and connection lifecycle.
Architecture
Ladybird uses a multi-process architecture:- Main UI process — coordinates the browser window and user interaction.
- WebContent (one per tab) — renders pages and runs JavaScript in a sandboxed process.
- ImageDecoder — decodes images out-of-process to limit the blast radius of malformed image data.
- RequestServer — performs network connections on behalf of the renderer, keeping raw socket access out of the sandboxed tab process.
IPC::ConnectionFromClient— the server side of a connection, representing one connected client.IPC::ConnectionToServer— the client side, used by the process that initiated the connection.
IPC::Connection base that owns the underlying Core::LocalSocket and integrates with the event loop via a Core::Notifier on the socket’s file descriptor.Message Passing
IPC::Message is the base type for all IPC messages. Each message carries a message ID and a serialised payload. The framework provides IPC::Encoder and IPC::Decoder for converting C++ types to and from the binary wire format.Messages can be:- One-way (fire-and-forget) — the caller sends the message and continues immediately.
- Synchronous (blocking call) — the caller sends a request message and pumps the event loop in a nested fashion until the reply arrives.
Transport and File Descriptor Passing
IPC::Transport abstracts the underlying channel. On POSIX systems this is a socketpair(2) UNIX-domain socket, which also supports sending open file descriptors via SCM_RIGHTS — used to pass AnonymousBuffer handles (shared bitmaps) and other OS resources between processes without data copying.IPC::Attachment encapsulates file descriptors that travel alongside a message.