This is a technical post on a part of snapd implementation detail.
When designing some of the new cgroup based tracking features I realized this feature is hard to enable reliably. On a given machine that updates, either with the classic package or with the re-execution system, a new snapd with awareness of this feature and with the feature switched to enabled-when-unset will commence to use it immediately. So far that was mostly okay because features were not retroactively affecting existing behavior (e.g. parallel installs or layouts). With the new refresh app awareness feature this is not the case.
The feature only really works reliably if enabled before anything using said feature is used. Attempting to solve this problem would introduce large amount of complexity into the mix. Worse, that complexity would only be triggered in the edge case of a machine that has upgraded but has not rebooted yet.
I think I found an elegant and efficient way to solve this problem in general. Here’s how it would work:
This file contains an UUID of the current boot. Reading it we can reliably determine which boot we are on. We already use this file in the
Feature files store condition
Currently feature flags that are exported outside of snapd state are represented as empty files in the directory
/var/lib/snapd/features. Old versions of snapd understand presence of a file as an indicator that a feature is enabled.
We can extend this idea to store some data in the file, that indicates more precise information when a feature is enabled. The following semantics is proposed:
- When a feature file is absent the feature is disabled
- When a feature file is present and empty the feature is enabled
- When a feature file is present and non-empty the content has the scanf format
%c %37s. The file encodes a single flag and a boot-id. Two flags are designed now
!flag is used when a feature is being enabled but must only become enabled on next boot. It can be understood as not this boot-id. The
=flag is used when a feature flag is being disabled but most only become disabled on next boot. It can be understood as just this boot-id.
The advantage of this approach is that the files encode the correct behavior even if snapd doesn’t start and gets a chance to rewrite them.
snap debug features
To help in understanding of what is enabled and what isn’t I propose that a new command is added. It would print a table like this
$ snap debug features Name Exported State Condition hotplug no enabled - layouts yes enabled - refresh-app-awareness yes enabled boot-id != 9c09e005-f08d-482d-a0a5-44cc3ac6bf2f parallel-installs yes enabled boot-id == 9c09e005-f08d-482d-a0a5-44cc3ac6bf2f
Changes to snapd state.json
Due to the way feature flags are used internally and to the way how they are exported to disk, we cannot rely on edge changes enabled > disabled or disabled > enabled. As such we must store the condition in the state.
I would like to propose that the state file store condition code as
$name is the name of the condition. I would propose that the exact same format used for external files be used internally. This approach is deemed safe against snapd rollback.
The Go and C features API need the following changes.
- Each flag needs to be either immediate or delayed. Delayed features participate in the condition evaluation. Immediate features work as features worked before. Note that this distinction is only relevant for Go side which handles changing the state.
refresh-app-awarenessfeatures should become delayed.
- The C implementation needs consider the size of the file and evaluate the boot condition.
snap set system ...
The overlord will need a small change to store the condition for changes to features that have the delayed property. When enabling it will set the condition
! $boot_id while when disabling the condition becomes
= $boot_id. Note that enabling a conditionally disabled feature or disabling a conditionally enabled feature can simply change the state immediately and can remove the condition.