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
- Read the lock/pidfile content to find the holder's PID, verify with ps, and stop it if stale
- Check systemd/supervisor for duplicate units: systemctl status <unit>
- 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
- Run services under a supervisor that guarantees a single instance
- Use per-instance pidfile paths in test harnesses
- Always drop lock guards cleanly on shutdown
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
- `datadir` must be a directory when calling this function: {d
- Endpoint::from_dir_entry failed: '{}' is not a directory
- expected a directory, got {:?}
- directory not empty: {base_path:?}
- Failed to read startup script: {e}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/1a051b01c35446af.
Report an issue: GitHub.