Programming

epoll and the List That Does Not Grow

After changing a small server to use an event loop, I began with select(). It is portable, familiar and perfectly adequate for a small number of descriptors. It also requires rebuilding descriptor sets and asking the kernel about the whole set on every pass, even when nearly everything is idle.

Linux has a more suitable interface for this shape of workload: epoll. The application creates an epoll instance, registers descriptors with epoll_ctl(), and waits for ready events with epoll_wait(). The interest list remains in the kernel, so the program changes it only when a connection is added, removed or needs different notifications.

The practical difference is that the returned array contains ready items rather than one bit for every descriptor I might care about. With thousands of quiet sockets and a handful of active ones, work follows activity more closely. This does not make the network faster. It removes repeated bookkeeping around sockets that had nothing to say.

I store a pointer or identifier in epoll_event.data so a returned event can find its connection state without another descriptor lookup. Registration then becomes part of the connection lifecycle. A socket enters the interest list after being made nonblocking and leaves before its state is destroyed. Getting that order wrong creates bugs that appear only when descriptors are reused, which is the computer’s way of encouraging humility.

Level-triggered operation resembles select(): readiness continues to be reported while data remains. Edge-triggered operation reports transitions and can reduce repeated notifications, but handlers must drain reads or writes until they return EAGAIN. Forget one partial operation and the connection may wait forever for an edge that already happened.

I started with edge triggering because it sounded faster. I changed back to level triggering while making the protocol correct, then measured. For this service, clarity won. Edge triggering can be appropriate, but it is not a ceremonial badge awarded to serious network programmers.

Errors and hangups require care too. A readiness event may carry several flags, and readable data can coexist with peer shutdown. The handler should consume valid bytes, handle partial writes and close the connection through one well-defined path. epoll reports conditions; it does not design the state machine.

For many Linux sockets, I strongly prefer epoll to stretching select() beyond its pleasant range. I keep the readiness layer small so another platform mechanism could replace it. The valuable architecture is explicit nonblocking state and ownership. epoll is an efficient Linux engine for that architecture, not the architecture itself.

Event Loops Before More Threads

I wrote a small TCP service using one thread per connection. It was the obvious design: accept a socket, start a thread, perform blocking reads and write the response. Each connection looked like a normal sequential program, and the first version was finished before lunch. This is an excellent property in prototypes and an alarming one in architecture.

The service was fine with a few clients. With many mostly idle connections, however, it accumulated threads whose principal activity was owning stacks and waiting. Shutdown required waking them all, shared statistics needed locks, and one slow operation could hold a mutex needed by several innocent connections. I had converted network concurrency into scheduler concurrency whether the work needed it or not.

An event loop starts from a different observation: most connections are not executing. They are waiting for a socket to become readable or writable, a timer to expire, or another operation to complete. One thread can wait for many such events, dispatch a small handler for each ready source, then wait again.

State instead of stacks

The thread-per-connection version stores progress in the thread’s call stack. A function reads a header, calls another function to read a body, and eventually writes a reply. When a read blocks, the stack quietly remembers where execution should continue.

With an event loop, handlers must not block. The connection’s progress becomes explicit state: reading headers, reading a body, preparing a response, or writing remaining bytes. A read handler consumes whatever is available, updates the state and returns. If the message is incomplete, the loop will call it again after the socket becomes readable.

This looks more complicated because it is more explicit. Partial reads and writes were always possible; blocking calls merely kept the unfinished operation on a private stack. The state machine makes those boundaries visible. That visibility is annoying for the first implementation and useful for every timeout, cancellation and protocol error added later.

A connection object in my revised service holds the socket, input and output buffers, parser state and deadlines. The loop owns the objects and invokes them serially. Because one loop thread mutates this state, most of it needs no locking. Ownership becomes a rule I can state rather than a hope expressed through mutexes.

Readiness is not completion

The operating system reports that an operation is likely to make progress. A readable socket may provide only part of a request. A writable socket may accept only part of a response. Handlers therefore loop until the operation would block, preserve what remains, and return control.

Nonblocking mode is essential. If one handler performs a blocking disk lookup or name resolution, every connection assigned to that loop waits behind it. Event-driven code does not eliminate blocking; it makes accidental blocking more expensive and easier to notice when the entire service pauses at once. Very democratic, if not especially helpful.

Timers belong in the same model. The loop computes the next deadline, limits its wait accordingly, then expires idle connections or retries work. I originally used one timer thread that sent messages to connection threads. Replacing that arrangement with a deadline queue removed both threads and a surprising amount of code.

Where threads still fit

An event loop is not an argument for one thread in the entire program. CPU-heavy work can be sent to a bounded worker pool. Blocking interfaces that cannot be integrated with readiness may need workers too. The important constraint is that results return to the owning loop as events, rather than allowing workers to mutate connection state directly.

Multiple loops can run on multiple processors, each owning a set of connections. This keeps the single-owner rule while using more than one core. Distribution and load balancing then become concerns, but they are explicit and usually less tangled than allowing every connection thread to touch global state.

Threads remain attractive when there are few concurrent operations, the blocking flow closely matches the problem, or the platform’s asynchronous facilities are poor. A thread can also be a good unit of isolation for independent subsystems. I do not want to replace clear blocking code with callbacks merely to win a concurrency argument on the Internet.

The event-driven version has its own failure modes. A handler that performs too much work increases latency for everything behind it. Unbounded input or output buffers merely move resource exhaustion from stacks to heaps. Reentrant callbacks can produce transitions the state machine did not expect. Fairness must be designed: a permanently busy socket should not monopolize one pass through the loop.

My current preference

For a service with many mostly waiting connections, I now strongly prefer an event loop with explicit ownership and a small worker pool for unavoidable blocking or CPU work. It uses threads according to available parallelism, not according to the number of clients. It also gives timeouts, cancellation and backpressure natural places in the design.

That preference is qualified. The state machine must be kept small, protocol parsing should be separated from readiness plumbing, and every handler must have a bounded amount of work. If those rules cannot be maintained, a modest number of threads may be safer than one ingenious loop nobody can debug.

My first threaded service was easier to write. The event-loop version is easier to reason about under load, which is a different and more valuable form of easy. It took me several pages of notes to discover that sleeping threads are still resources. The kernel, rather rudely, knew this already.

QtWebKit, One Page, Three Worlds

I needed to show a formatted report inside a Qt application. The report had headings, tables, a few images and links to related objects. My first attempt used a QTextDocument, and it was perfectly respectable until the design acquired a stylesheet and enough layout requirements to qualify as a small web page.

Qt 4.4’s QtWebKit module looked like the easy answer. Put the HTML in a QWebView, connect the link signal and go home. It did work, but the interesting part begins when the page needs to interact with the application. At that point there are three different worlds involved: Qt widgets, WebKit’s page and frame objects, and JavaScript running inside a frame.

The useful layers

QWebView is the widget. It fits into a normal layout and provides the familiar actions such as loading, back, forward and reload. It owns a QWebPage, which holds page-wide behavior: navigation policy, actions, settings and the main frame. The QWebFrame represents a frame’s document and is where HTML, JavaScript and the rendered contents meet.

For a simple report I can call setHtml() on the frame or view, optionally supplying a base URL so relative images and stylesheets resolve properly. That last argument is easy to omit and produces the pleasing result of a beautifully laid-out report with no images. I know this because I conducted the experiment several times.

Loading is asynchronous. A call to load() starts work and returns. Signals report progress and completion. Code that immediately asks for the final document after load() is racing the network and parser, even when the URL points to a local file. This is the same event-loop discipline as the rest of Qt, merely attached to a much more complicated component.

Crossing into JavaScript

QtWebKit can expose a QObject to JavaScript with addToJavaScriptWindowObject(). Slots and properties then become available in the script environment. This is convenient for a report that needs to request an application action. A link can call a narrow bridge object rather than encoding commands into invented URLs and parsing them later.

The narrow part is important. Exposing the main window would give page script an enormous accidental API. I prefer a small object with slots such as showItem(int) or requestPrint(). The bridge validates its arguments and emits signals; application code performs the actual work. The web page gets capabilities, not the keys to the building.

The JavaScript window object can be cleared when the frame’s context changes, so the bridge may need to be added again when notified. Treating injection as a one-time constructor chore works for the first page and mysteriously fails after navigation. Web pages have lifecycles too, because apparently one kind was not enough.

Communication can go in the other direction with evaluateJavaScript(). It is useful for updating a small piece of page state, but assembling large scripts from C++ strings becomes unpleasant immediately. For anything substantial I keep the logic in a JavaScript resource and invoke a defined function with carefully encoded data.

An embedded page should not automatically handle every clicked URL. The application may want internal links to select objects, ordinary HTTP links to open in the user’s browser, and unknown schemes to do nothing. QWebPage provides navigation hooks where that decision belongs.

I initially connected linkClicked() and assumed the job was finished. It was not: navigation behavior depends on the page’s link delegation policy. Once configured, the signal allows the application to decide rather than letting every click replace the report. This is less magical and therefore better.

Content is another policy boundary. For reports generated entirely by the application, exposing a bridge is manageable because the application controls the HTML and script. Doing the same for arbitrary remote pages is a different security proposition. A page that can invoke native slots has left the ordinary browser sandbox through the door I opened for it.

A large tool for a specific job

QtWebKit brings a real browser engine, not merely a richer label. That means capable HTML and CSS rendering, JavaScript, network loading, history and all the state accompanying them. It also means more memory and startup cost than QTextDocument. If the requirement is a few paragraphs with bold text, a web engine is unnecessary furniture.

Printing and exporting deserve testing as well. What looks right in the viewport may paginate badly. External resources may still be loading when the user presses Print. Font choices differ from the surrounding widgets unless the page stylesheet makes them deliberate. Embedding the web does not make it cease being the web.

For complex, interactive documents that already fit the HTML model, I strongly prefer QtWebKit to inventing a private layout engine from labels and painters. It gives designers familiar tools and keeps document structure out of widget code. I qualify that preference heavily: the page should be controlled, the native bridge should be tiny, and navigation should be explicit.

My report ended up simpler than the QTextDocument version, not because QtWebKit itself is simple, but because its complexity matches the problem. The trick is to use the browser as a document component and resist the urge to turn every dialog into a website. We have enough browsers already.

QtConcurrent for the Small Jobs

I had a directory full of images to scale and a progress dialog that froze while doing it. The obvious fix was a worker thread. The less obvious consequence was a new class, a queue, two mutexes and several opportunities to close the dialog at exactly the wrong moment.

Qt 4.4 includes QtConcurrent, which handles this sort of bounded parallel work at a higher level. Instead of managing threads, I can express the operation as a function over a sequence and let mapped() apply it. The result is represented by a QFuture, and QFutureWatcher turns progress and completion into signals suitable for the event-driven parts of the application.

The thread pool is the useful mechanism here. Creating one thread per thumbnail would merely exchange a frozen interface for a small scheduling experiment. QtConcurrent uses a pool and distributes work across it. The application says what can run independently; the library decides which worker executes each item.

There are several forms. run() is convenient for one function call. mapped() transforms each input into an output, while filtered() selects items. The blocking variants are tempting in a command-line program but defeat the point in a GUI thread. I use a watcher and return to the event loop.

This does not make shared state safe. The mapping function must not casually update widgets, append to an unprotected container or rely on execution order. GUI objects remain in their own thread. I pass immutable input into the workers and collect results through the future. If work needs elaborate communication or a long-lived thread with its own event loop, QThread is still the clearer tool.

Cancellation is another qualified promise. A future can request cancellation, but code already running does not vanish. The operation must reach places where cancellation can take effect. For tiny image conversions this is acceptable; for one enormous function it may not be.

I prefer QtConcurrent when the problem really is a collection of independent jobs. It removes thread bookkeeping without hiding that concurrency exists. That is a narrower claim than “threads made easy,” and therefore more believable. My thumbnail dialog now moves smoothly, and the implementation lost enough plumbing that I can almost understand code I wrote last week.

Plasma Is Not Just a Panel

I wanted a tiny display for a script: one number, updated every few seconds, sitting in the panel. My first thought was to write another panel applet, which is exactly the sort of thought one has after years of assuming the panel is a special little kingdom.

Plasma makes that model obsolete. The desktop and panels are containments, and the things placed in them are applets. An applet does not have to know whether it lives beside the clock or floats on the desktop. The containment supplies geometry and constraints; the applet supplies its representation and behavior.

That distinction explained several things I had mistaken for needless abstraction. An applet receives constraint changes when its size or location changes. A wide desktop representation can become a compact panel representation without maintaining two unrelated implementations. It can also publish actions to the shell instead of building its own private collection of buttons.

Data comes through another layer. A Plasma data engine gathers and names information, while applets subscribe to sources. This keeps the code that reads a device, queries a service or runs a command away from the code that paints it. Several applets can consume one engine, and the engine controls update intervals rather than allowing every widget to wake the machine whenever inspiration strikes.

For my number display, this meant writing less code than expected. The engine owned the script result and timer. The applet listened for updates and painted the value. Moving it from desktop to panel changed layout constraints, not the source of the data. I had originally put the timer in the applet, because apparently I enjoy rediscovering coupling by hand.

The package structure is useful too. Metadata describes the applet, resources travel with it, and the shell can discover it without a hard-coded list. Scripted applets lower the cost of small experiments, while C++ remains available when the job needs libraries or tighter control.

There is a price. Debugging a small widget now involves understanding the applet, its containment and perhaps a data engine. Plasma’s vocabulary can sound grandiose when all I want is a number beside the clock. For one fixed panel, the old model was simpler.

I still prefer Plasma’s separation for anything that may grow or be reused. It treats the desktop as a collection of cooperating components rather than a panel plus decorative exceptions. The implementation is young and occasionally reminds me of that fact, but the mechanism is much more interesting than the collection of wobbly widgets suggests.