Upstream

This Week in Pop!

Pop!_OS is a Linux distribution by System76, a Linux-based desktop hardware OEM. It is based on Ubuntu, with many enhancements specific to our vision of the desktop, and provides an optimal platform for our hardware, which is thoroughly tested throughout each release.

This will be the first in a series of weekly updates on progress made in the development of Pop!_OS. Thus, this will only contain content pertaining specifically to Pop!_OS, though at times there may be some overlap with the hardware side of System76.

As the first post, this may contain a few updates from a week or two prior, as well. Some weeks may not have much to report, and others may have a lot to report. These may not include every bit of progress that’s made each week, but it will contain the highlights.

Rust 1.31.0

At System76, all of our desktop projects are written in Rust. However, we’ve always been limited to using the version of Rust that is shipped in the Ubuntu repositories, as these are used by the package-building process on Launchpad. The Ubuntu Mozilla team has a PPA which includes newer versions of Rust for building Firefox, with backports available to bionic and cosmic, so we are now copying these packages into our repo to build from. As a result, both rustc and cargo in each of our supported repositories have been updated to Rust 1.31.0.

This fixes a large number of compatibility issues with new Rust crates, and newer versions of crates that we rely on, and enables us to switch our current projects to the 2018 edition of Rust. This brings many useful features to the Rust language that weren’t available on the 1.24.0 version of Rust that bionic had, and the 1.28 version of Rust that cosmic shipped. Among these are the stabilization of:

  • cargo-fix, which can automatically fix some errors and ease migration to newer editions of Rust.
  • cargo-clippy, which provides hundreds of additional code quality checks
  • cargo-fmt, which can automatically format an entire project based on a rustfmt.toml file
  • non-lexical lifetimes, which improves the borrow checker to allow more valid patterns to be permitted
  • procedural macros,
  • global allocators, which also allows changing the default allocator to the system allocator, or jemalloc
  • NonZero integer types, flattening iterators, and many more API changes to the core and standard libraries

We should now be able to follow Rust updates more closely, thanks to the Ubuntu Mozilla Team PPA.

Popsicle GTK Rewrite

Since writing the original GTK frontend for Popsicle, I’ve gained more experience with Rust-based GTK application development. There’s a consensus today in the Rust community that application development works better with a channel[0]-based approach to managing application signals[1] and state[2]. The maintainers of the glib Rust bindings have come to same conclusion as well. We used this approach in a new in-house GTK application for the hardware side of System76’s business, which worked out particularly well. It’s time to do the same for Popsicle.

The traditional approach to GTK application development is to provide application logic and shared state directly to each signal that is programmed in the UI. Reference cycles[3] are a real threat that this can cause, if this is not carefully designed. This can make managing state more difficult, as application logic is now spread out across the application. It also makes preventing UI freezes difficult, as the UI is unable to resume until the signal is complete.

Typically, the solution to handle UI freezes is to spawn new threads to perform tasks, and then to register additional signals to track the progress of background threads, and to enable cancellation. This can easily lead to threads stepping on each other as they access the same shared state, or possibly hanging the UI in a deadlock. In languages which are not memory safe, including garbage-collected languages like Go, it can also lead to application crashes.

With the channel-based approach, each signal will instead be given a sender to send events through. If a button is clicked, that button may collect UI information and then send it as an event through the sender to a receiver. Multiple signals may share the same sender, and send all events to the same receiver. The receiver could either be in a background thread (or a thread pool) that’s waiting to process tasks, or registered in the main context to handle UI events.

Now we have a solution that can keep state isolated and centralized. Reference cycles are avoided entirely. Less threads are spawned. The application is easier to maintain. With this PR, Popsicle will now be using this approach. Each button click will trigger an event that’s processed either in a background thread to perform a task, or in the main thread to update the UI. UI freezes are now no longer possible.

This will also fix a UI hang that would occur on systems with SD card readers.

Firmware Updates in GNOME Control Center

There’s a plan to provide firmware and OS release updates through GNOME Control Center, similar to how other operating systems distribute their updates. In addition to firmware and OS updates, a few extra system details are also being added to the About / Info panel.

Firmware updates were immediately ready to be integrated into GNOME Control Center due to the pre-existing system76-firmware DBus daemon. This functionality is currently provided by our firmware updater tool, a standalone GTK application, but will soon also be available there.

The Pop! Shop, or perhaps a shell extension, may also soon be updated to ask the DBus daemon if updates are available, and trigger a notification to open GNOME Control Center when updates are found. The PR to add this support to our fork of GNOME Control Center is here.

Pop Upgrade Daemon

We have been working on our own tooling for performing recovery and distribution release upgrades, to increase the reliability that a distribution upgrade will succeed. As with the rest of our tooling, it is also developed with Rust. This makes use of some of the Rust crates that we developed for distinst and our other Rust projects, and will result in a few additional crates once it is done.

This will replace Canonical’s do-release-upgrade Python scripts, which performs a live upgrade. Our tooling will not offer support for live upgrades, as live upgrades cause the session around the user to begin crashing as components are replaced. When GNOME is upgraded, it often causes the entire desktop to crash, resulting in a partially-installed upgrade.

The daemon provides a DBus API for the provided CLI client to interact with, and will eventually be integrated into GNOME Control Center, too. It offers two types of release upgrade possibilities: an offline upgrade via the recovery partition, or an offline upgrade via systemd. We will default to using our recovery partition for performing release upgrades on systems that have a recovery partition, and the systemd system-update method if that is not an option.

To increase the chance of a successful upgrade, the daemon will perform various system checks and repairs before the update process starts, enabling the process to fail early before changes are made. Packages which are deemed critical will also be installed if they were previously uninstalled, or had never been installed before. During the 18.04 -> 18.10 upgrade cycle, it was often found that the cryptsetup package was removed during a release upgrade. This will not happen with our release upgrade process.

One of the bonuses to our new tooling is that we are using asynchronous I/O via futures to fetch package updates, leading to record package-fetching transfer rates. Rather than downloading one package at a time, multiple packages will be fetched in parallel.

The project is here, and under active development. Testing has not yet been thoroughly performed, but is currently in progress.

Rust Crate Updates

The development of the upgrade daemon saw a few holes and missing functionality in some of the Rust crates we use for distinst.

  • proc-mounts can now read the /etc/fstab file, and any file like it. It may also add, remove, and edit mounts in that file through an abstract in-memory representation.
  • A similar change was made for our previously-unused apt-sources-lists crate, which now supports adding, removing, and editing apt sources.

Footnotes

[0] A channel is a concurrent data type which consists of a sender, to give data to; and a receiver, to retrieve data from. A MPSC (multi-producer, single-consumer) channel can have many senders that each send data to the same receiver. A MPMC (multi-producer, multi-consumer) channel can have many senders and receivers. Note that once a receiver receives, other receivers cannot receive what was previously received by another receiver. This is known as work-stealing.

[1] GTK signals are functions which are executed when certain UI events occur. A button click signal would trigger when a button is clicked. These signals are given a function to execute, and state to execute with, and possibly manipulate.

[2] Application state refers to memory which the application keeps track of as the user progresses back and forth in the UI. In the case of popsicle, the path of the ISO to flash, the devices that were selected, and the current progress of flashing is an example of state. State is important for performing and undoing actions.

[3] Reference cycles occur when reference-counted values depend on each other in a cycle which may not break. These values will remain in memory until they reach zero, but they can only reach zero if a cycle does not exist. This can cause memory leak-like behavior, where an application could continually consume more and more memory over time.

I am a System76 software engineer, maintainer of Pop!_OS, and contributor to Redox OS. My preferred programming language is Rust, with four years of e...