neondatabase/neon · error

file is already locked

Error message

file is already locked

What it means

lock_file::create_exclusive opens the file and takes a non-blocking exclusive flock (LOCK_EX|LOCK_NB); EAGAIN means another process currently holds the lock. This is the single-instance guard used for pidfiles and data dirs, so the error is by design, not corruption.

Source

Thrown at libs/utils/src/lock_file.rs:75

/// The exclusive lock is released when dropping the returned handle.
///
/// It is not an error if the file already exists.
/// It is an error if the file is already locked.
pub fn create_exclusive(lock_file_path: &Utf8Path) -> anyhow::Result<UnwrittenLockFile> {
    let lock_file = fs::OpenOptions::new()
        .create(true) // O_CREAT
        .truncate(true)
        .write(true)
        .open(lock_file_path)
        .context("open lock file")?;

    let res = Flock::lock(lock_file, FlockArg::LockExclusiveNonblock);
    match res {
        Ok(lock_file) => Ok(UnwrittenLockFile {
            path: lock_file_path.to_owned(),
            file: lock_file,
        }),
        Err((_, EAGAIN)) => anyhow::bail!("file is already locked"),
        Err((_, e)) => Err(e).context("flock error"),
    }
}

/// Returned by [`read_and_hold_lock_file`].
/// Check out the [`pid_file`](crate::pid_file) module for what the variants mean
/// and potential caveats if the lock files that are used to store PIDs.
pub enum LockFileRead {
    /// No file exists at the given path.
    NotExist,
    /// No other process held the lock file, so we grabbed an flock
    /// on it and read its contents.
    /// Release the flock by dropping the [`LockFileGuard`].
    NotHeldByAnyProcess(LockFileGuard, String),
    /// The file exists but another process was holding an flock on it.
    LockedByOtherProcess {
        not_locked_file: fs::File,
        content: String,

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Read the lock/pidfile content to find the holder's PID, verify with ps, and stop it if stale
  2. Check systemd/supervisor for duplicate units: systemctl status <unit>
  3. Give each instance its own data/pidfile path

Example fix

# before: second instance started while first is running
systemctl start neon-pageserver && neon_pageserver --pid-file=/var/lib/neon.pid
# after: stop the existing holder first
kill $(cat /var/lib/neon.pid)   # or: systemctl stop neon-pageserver
Defensive patterns

Strategy: validation

Validate before calling

use utils::pid_file;

match pid_file::read(&path)? {
    pid_file::PidFileRead::LockedByOtherProcess(pid) => {
        anyhow::bail!("another instance is running as pid {pid}");
    }
    pid_file::PidFileRead::NotExist | pid_file::PidFileRead::NotHeldByAnyProcess(_) => {
        // safe to claim
    }
}

Type guard

fn is_already_locked_error(err: &anyhow::Error) -> bool {
    err.to_string().contains("file is already locked")
}

Try / catch

match lock_file::create_exclusive(&path) {
    Err(e) if e.to_string().contains("file is already locked") => {
        // read_and_hold_lock_file(&path) yields the holder's content/PID; exit with a clear message
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling claim_for_current_process / create_exclusive on a pidfile or lockfile while another process holds it: a second pageserver on the same data dir, a duplicate safekeeper, or a leftover process that never exited.

Common situations: Accidentally starting the service twice; stale process from a previous run; test harnesses sharing a data directory in parallel; manually running the binary while systemd already has it up.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/1a051b01c35446af. Report an issue: GitHub.