This is a technical post on a part of snapd implementation detail.
Problem statement
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:
/proc/sys/kernel/random/boot_id
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 BoodID
function.
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!
and=
. The!
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 config.core.experimental.conditions.$name
where $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.
Changes to features
API
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.
- The
parallel-installs
andrefresh-app-awareness
features should become delayed. - The C implementation needs consider the size of the file and evaluate the boot condition.
Changes to 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.