unicity-aos/aos-ce · error · io::Error

another runtime migration is already in progress

Error message

another runtime migration is already in progress

What it means

During migration, the library creates/opens a runtime migration lock file and attempts `try_lock_exclusive()`. A `WouldBlock` result means another migration process already holds this exclusive lock, so the library raises `WouldBlock` with this message to prevent concurrent migrations from corrupting state.

Solutions

  1. Wait for the in-progress migration to finish, then re-run.
  2. Find the other migration process (`ps aux | grep <bootstrap>`) and confirm whether it is alive; kill it if it is hung.
  3. Remove the stale migration lock file only after confirming no migration process is running, then retry.
  4. Add an outer guard in your automation (e.g. flock around the whole migration command) to prevent concurrent invocations.
Defensive patterns

Strategy: retry

Validate before calling

if migration_lock_exists_and_is_locked() {
    return Err(anyhow!("another migration is already in progress; waiting"));
}

Try / catch

match err.downcast_ref::<io::Error>() {
    Some(e) if e.kind() == io::ErrorKind::WouldBlock && e.to_string().contains("already in progress") => backoff_and_retry(),
    _ => return Err(err),
}

Prevention

When it happens

Trigger: Two `migrate_runtime` invocations overlapping — e.g. running the bootstrap migration command twice concurrently, or an automated job retried while the first attempt is still in progress.

Common situations: CI pipelines with retry logic re-launching the migration while the original still runs; two operators performing the upgrade simultaneously; a previous migration process stuck without releasing the lock.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/ca7a6d553586132b. Report an issue: GitHub.

Appendix: source

Thrown at crates/unicity-aos-bootstrap/src/migration.rs:213

    false
}

impl MigrationLock {
    fn acquire(home: &AosHome) -> io::Result<Self> {
        let path = home.root().join("migrations").join(LOCK_FILE);
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .truncate(false)
            .open(&path)?;
        if !file.metadata()?.is_file() {
            return invalid("runtime migration lock must be a regular file");
        }
        set_private_permissions(&path, false)?;
        file.try_lock_exclusive().map_err(|error| {
            if error.kind() == io::ErrorKind::WouldBlock {
                io::Error::new(
                    io::ErrorKind::WouldBlock,
                    "another runtime migration is already in progress",
                )
            } else {
                error
            }
        })?;
        Ok(Self { _file: file })
    }
}

pub(crate) fn migrate_runtime(home: &AosHome, source: &Path) -> io::Result<MigrationOutcome> {
    let source = checked_root(source, "legacy runtime home")?;
    let target = checked_target_path(&home.runtime_home())?;
    let release_runtime_bin = home.release_runtime_bin_dir();
    if source == target || source.starts_with(&target) || target.starts_with(&source) {
        return invalid("legacy runtime home and product runtime home must not overlap");
    }

View on GitHub (pinned to f6f22024fb)