Skip to content

refactor: atomic installation#201

Open
lima-limon-inc wants to merge 48 commits into
mainfrom
fabrizioorsi/atomic-installation
Open

refactor: atomic installation#201
lima-limon-inc wants to merge 48 commits into
mainfrom
fabrizioorsi/atomic-installation

Conversation

@lima-limon-inc

@lima-limon-inc lima-limon-inc commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #198
Closes #195
Blocked-by: #209

This PR changes the way installs are performed in midenup. After this PR, installs in midenup are made atomically, meaning that the entire toolchain is either fully installed onto the filesystem or not.

Installs are performed on a separate directory that's stored inside the installed_toolchains/<channel-version>-<hash>. This directory is then atomically symlinked into the corresponding toolchains/<channel-version> directory. If a toolchain gets re-installed with newer components (such as in the case=
This structure roughly looks like:

midenup
├── installed_toolchains
│   └── 0.14.0-fb9b2252a909ddb9
│       ├── bin
│       │   └── (...)
│       ├── install.rs
│       ├── lib
│       │   └── (...)
│       ├── install.rs
│       └── opt
│           └── (...)
└── toolchains
    ├── 0.14.0 -> ../installed_toolchains/0.14.0-fb9b2252a909ddb9
    └── stable -> 0.14.0

Notes:

  • A couple of trait implementations were removed in favor of the default derived ones.
    • Hash, PartialEq and Eq for Component. These custom definition were only used in update.rs when performing set operations. Removed in 5597a1e.
    • Now, in order to compare Components by names, the wrapper struct ComponentByName can be used.
  • All components on the Channel now derive Hash, which is then used to generate the directory name in installed_toolchains.
  • The installation-successful and .installation-in-progress install-logs have been removed. Since toolchains are atomically installed, if the toolchain is symlinked, then it was definitely installed.
  • The channel.json snapshot is no longer saved during installs. Now, uninstall relies on the information saved on the local manifest to uninstall the component. This got changed on 2c39912
  • There's one limitation with this approach: if a channel requires a name change when updating, the install is no longer "atomic".
    • This particularly affects channel migrations. However, as pointed out in the code, were a migration to be stopped right before installing the previous channel, re-issuing a midenup update would finish the process accordingly.

@lima-limon-inc lima-limon-inc added the check:install PRs only: runs workflows that perform additional end-to-end integration testing label May 8, 2026
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from 5cdcf71 to 66cd69c Compare May 11, 2026 20:41
@lima-limon-inc lima-limon-inc changed the title (WIP): atomic installation refactor: atomic installation May 11, 2026
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch 3 times, most recently from d4a962e to f9967ef Compare May 13, 2026 20:32
@lima-limon-inc lima-limon-inc marked this pull request as ready for review May 13, 2026 22:23
@lima-limon-inc lima-limon-inc changed the base branch from main to fabrizioorsi/i193-fix-migration May 13, 2026 22:32
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/i193-fix-migration branch from d1f6aad to 1d53ddf Compare May 13, 2026 22:36
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from f9967ef to d1f6aad Compare May 13, 2026 22:37
@lima-limon-inc lima-limon-inc requested a review from bitwalker May 13, 2026 22:38
@lima-limon-inc

Copy link
Copy Markdown
Collaborator Author

@bitwalker like mentioned on #194, this PR is also ready for review. The main set of changes come from install.rs + channel.rs.

Thanks in advance ^_^

Base automatically changed from fabrizioorsi/i193-fix-migration to main May 20, 2026 14:15

@bitwalker bitwalker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall! I found a couple of issues that we should address, but this is pretty close

Comment thread src/commands/install.rs
// We now rename tmp_link to toolchain_dir. When renamed, it will still be
// pointing to relative_install_target. If the channel direcotry existed, it
// will overwrite the file. This is what marks the install as completed.
std::fs::rename(&temp_symlink, &toolchain_dir).with_context(|| {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't clear to me whether there will be an issue here with existing installations. Those may already have real directories at toolchains<version>, so this will fail if toolchain_dir already exists.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point, addressed it here: https://github.com/0xmiden/midenup/compare/35b49ac..970b91d

I added a migration check and associated function. Were more migrations to be needed in the future, we could just add more checks and associated fuctions on the corresponding mod.rs file

Comment thread src/commands/uninstall.rs

local_manifest.remove_channel(channel.name.clone());
// We remove the symlink, thus making the channel unaccesible.
if toolchain_symlink.exists() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comment in install.rs, if there is an existing install, then remove_file here may be getting called on a real directory, not a symlink. Existing installs will need a one-time migration or compatibility path.

Comment thread src/commands/install.rs
.unwrap_or(false);
let installed_toolchains_dir = config.midenup_home.join("installed_toolchains");
let install_dir_name = format!("{}-{}", &channel.name, channel.content_hash());
let install_dir = installed_toolchains_dir.join(&install_dir_name);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears updates that have the same hash can be marked complete without reinstalling. Same-hash updates can be marked complete without reinstalling. install_dir is based on channel.content_hash() here, and changed branch/path components can reuse the same upstream channel hash. If the hashed install directory already exists, component removal is skipped entirely (see line 44), the generated install script skips existing binaries, and the manifest is refreshed as if the update happened. This affects branch-tip and local-path updates.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch, I've since adressed this issue here: https://github.com/0xmiden/midenup/compare/2402558..725ac8f

Now, when performing an update, the channels might mutate (for instance, channels that are tracking branches will get the fetched latest commit added onto their latest_commit field. Same with Path tracked components.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: This is no longer the case, the is_up_to_date function no longer changes the component. Now, when the UpstreamChannel is created, we sync the channel to get the latest changes.

Comment thread src/commands/update.rs
Authority::Cargo { .. } | Authority::Git { .. } => true,
// Since uninstalling a component from the filesystem is potentially
// irreversible, we take special precautions before uninstalling them.
Authority::Path { .. } => match options.path_update {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--path-update=off no longer preserves skipped components. Update excludes path components here, but installation still saves the upstream channel and refreshes path metadata for all path components (see line 223 of install.rs), so a skipped rebuild can then look current.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. This has seen been fixed here: f29ac1e

This same issue also caused a bug where partial channels would install all the remaining components.

Comment thread src/commands/install.rs
// We now rename tmp_link to toolchain_dir. When renamed, it will still be
// pointing to relative_install_target. If the channel direcotry existed, it
// will overwrite the file. This is what marks the install as completed.
std::fs::rename(&temp_symlink, &toolchain_dir).with_context(|| {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like installs can be published before metadata is committed. The runnable toolchains/<version> symlink is published before stable and manifest.json are written here and on line 176. A failure in between will leave the filesystem and local manifest state inconsistent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has since been fixed here: d58c9dc

Updating the manifest should be the last operation we do IIUC

Comment thread src/commands/uninstall.rs

local_manifest.remove_channel(channel.name.clone());
// We remove the symlink, thus making the channel unaccesible.
if toolchain_symlink.exists() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears uninstallation is not retry-safe after cleanup failure. This removes the symlink and manifest entry before removing components/install directory. If cleanup fails, rerunning uninstall bails because the manifest entry is gone.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, changed here: f1a3b5d

Now the symlink is removed after removing the components.

@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from a826083 to d288af2 Compare June 5, 2026 19:37
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from fa817e0 to d58c9dc Compare June 5, 2026 20:49
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from 03ca629 to 452b5ee Compare June 9, 2026 15:41
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch 2 times, most recently from 0e97928 to 100f6a4 Compare June 9, 2026 21:27
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from 100f6a4 to 6c1f491 Compare June 9, 2026 21:56
@lima-limon-inc lima-limon-inc force-pushed the fabrizioorsi/atomic-installation branch from c51597a to 8cc8e8f Compare June 11, 2026 19:01
@lima-limon-inc

Copy link
Copy Markdown
Collaborator Author

The latest commits updated the way updates were computed. The main change is the following:
https://github.com/0xmiden/midenup/compare/fd96e357560fc4a3f33b94db1de4df69c2dee28b..f1a3b5dff88878b4a7de6a6c298538515eca049f#diff-4bf1ef1a8771c506c71d2ef69d5d59e623f54b2cc09f2c297496e30d5f7da25eR487-R508

  • The old channel gets cloned as "base", in order to inherit tags
  • The components are taken from the components_to_install variable, which includes both the components that need updating and, notably, the components that are up to date
  • If the channel got migrated, we apply the changes (i.e. the name change), on the migrate_channel function

The entire change set can be seen on this diff: https://github.com/0xmiden/midenup/compare/fd96e357560fc4a3f33b94db1de4df69c2dee28b..aab07d5c0c32eb76c113d61f39960f943dacc840

@lima-limon-inc lima-limon-inc requested a review from bitwalker June 12, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

check:install PRs only: runs workflows that perform additional end-to-end integration testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

proposal/refactor: make installs atomic bug: midenup fails to uninstall components that were installed from pre-compiled artifacts

2 participants