Blog

ext4 After the Stable Label

I have been testing ext4 on disposable filesystems for a while, but I have avoided recommending it for ordinary machines. Linux 2.6.28 was released on December 24 and removed ext4’s experimental designation. Only now am I comfortable calling it stable in the kernel sense, which is not the same as volunteering the only copy of my photographs.

The most visible change from ext3 is the use of extents. Rather than describing a large contiguous file as a long list of individual blocks, an extent describes a range. Large files need less metadata, and allocation can stay contiguous more easily. The on-disk format can also support much larger filesystems and files than ext3’s older structures comfortably allow.

Delayed allocation is another important piece. The filesystem can postpone the final block placement until dirty data is written, when it knows more about the size and neighboring writes. Combined with multiblock allocation, this can reduce fragmentation and improve throughput. The tradeoff is that allocation and write timing differ from ext3, so applications that confuse close() with durable storage may have their assumptions exposed.

The journal still protects filesystem metadata, but it is not a magical backup and does not promise that every recently written byte survives a crash. Applications that require ordering and durability need to use the proper synchronization operations and safe replacement patterns. A stable filesystem cannot repair an application protocol that never asked data to reach the disk.

Migration is possible because ext4 grew from ext3, but enabling new features changes what older kernels and tools can understand. I prefer creating a fresh ext4 filesystem for serious evaluation rather than turning an existing one into an archaeology project. Current e2fsprogs support is as important as kernel support, especially when the machine fails to boot and the rescue environment suddenly becomes relevant.

My cautious plan is to use ext4 first for build trees and other replaceable data, with backups and a recent kernel. If that remains boring, it can graduate to less disposable machines. I strongly prefer its extent-based direction over stretching ext3 forever, but five days with a stable label is not decades of field experience.

So ext4 is stable now, after December 24, 2008. It is also new, complex software in the least amusing place to discover a bug. Both statements can be true, and my backup disk does not mind the apparent indecision.

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.

Solid Hardware Without Device File Archaeology

I left an import dialog open, pulled the card reader, and clicked Import. The dialog still had a pointer to a Solid::StorageAccess interface from the device it had displayed. The hardware was gone; my object graph had not heard the news.

Hotplug makes lifetime the main problem, not discovery. A device can disappear between filling a menu and handling its action. A mount can vanish while a job is queued. Keeping /dev/sdb1 does not fix that, and keeping a frontend interface pointer around is worse because it looks usable right up to the point that it is not.

The dialog now stores the device’s unique device identifier, not a borrowed interface object. When the user starts an import, it asks Solid for the device again, checks that it is still valid, obtains the storage interface for that operation, and then checks the current accessibility state. No lookup, no import.

I also listen for deviceRemoved(QString). If the identifier matches the open dialog, the UI disables Import and says the device was removed. Any queued work receives the same cancellation instead of continuing with a path that happened to exist a moment ago.

There is a small race even with the fresh lookup: unplugging is allowed to win after the check. The operation still needs to handle mount or I/O failure. The lookup keeps stale application objects out of the way; it cannot promise that a human will leave the cable alone.

deviceAdded gets less special treatment now. It gives me an identifier to inspect, and I query capabilities when I need them. The removal signal is the one that tears down assumptions.

This fixed a second oddity too: unplugging and reinserting the same reader could produce a new frontend object while the dialog held the old one. Keying the UI by identifier and reacquiring the device made that boring, which is exactly what hotplug code should be.

Phonon and the Boring Audio Button

My sound preview worked with the Xine Phonon backend and failed as soon as I switched the machine to GStreamer. Same file, same button, no sound. So much for my five-line multimedia triumph.

The application called play() and immediately checked state(). Xine reached PlayingState quickly on my machine. GStreamer was still in LoadingState, which my code treated as failure and answered with stop(). I had accidentally made backend speed part of the program’s contract.

Phonon state changes are asynchronous. The fix was to let the MediaObject report stateChanged() and handle ErrorState there instead of demanding synchronous playback:

connect(media, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
        this, SLOT(mediaStateChanged(Phonon::State, Phonon::State)));
media->setCurrentSource(fileName);
media->play();

The slot updates the button when playback really starts and displays errorString() if the object enters ErrorState. LoadingState is just progress, not proof that the backend rejected the file.

Switching backends was useful precisely because it broke this. The common API kept Xine and GStreamer details out of the application, but it did not turn asynchronous operations into synchronous ones. One backend had merely been fast enough to hide my bad assumption.

The preview button is boring again. I left both backends in the test pass; otherwise the next timing assumption will wait for a user to find it.