Files
codex/codex-rs/rollout/src/maintenance.rs
T
goodman-oai 12b2b58e63 Release the rollout maintenance lock when its guard is dropped (#57247)
## Why

A duplicated or inherited file handle can keep the rollout maintenance lock held after its guard is dropped, blocking subsequent compression or migration.

## What changed

Explicitly unlock the file in `RolloutMaintenanceGuard::drop` so lock ownership ends with the guard even if another handle remains open.

## Testing

Add a regression test that retains a duplicated file handle, verifies that the lock stays held until the last shared guard is dropped, and checks that dropping the old handle does not release a newly acquired lock.

GitOrigin-RevId: 19005680dbc0a01b36c6f13e4ec358c6f00b75a0
2026-10-26 20:52:45 +00:00

53 lines
1.7 KiB
Rust

//! Coordinates maintenance jobs that replace local rollout files.
//!
//! Rollout compression and legacy rollout migration both publish by renaming a replacement over an
//! existing rollout path. They must not do that at the same time for one Codex home, so they share
//! this process-scoped, nonblocking file lock.
//!
//! This is separate from per-thread writer locks, which protect live rollout appenders. It is also
//! separate from compression's durable run marker, which throttles how often compression scans.
use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::path::Path;
const ROLLOUT_MAINTENANCE_LOCK: &str = "rollout-maintenance.lock";
#[cfg(test)]
#[path = "maintenance_tests.rs"]
mod tests;
/// Holds exclusive ownership of operations that replace local rollout files.
pub struct RolloutMaintenanceGuard {
file: File,
}
impl Drop for RolloutMaintenanceGuard {
fn drop(&mut self) {
// Release the lock even if a child inherited the file before exec.
let _ = self.file.unlock();
}
}
/// Try to exclude rollout compression and migration for one Codex home.
pub fn try_acquire_rollout_maintenance_lock(
codex_home: &Path,
) -> io::Result<Option<RolloutMaintenanceGuard>> {
let directory = codex_home.join(".tmp");
fs::create_dir_all(&directory)?;
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(directory.join(ROLLOUT_MAINTENANCE_LOCK))?;
match file.try_lock() {
Ok(()) => Ok(Some(RolloutMaintenanceGuard { file })),
Err(std::fs::TryLockError::WouldBlock) => Ok(None),
Err(std::fs::TryLockError::Error(error)) => Err(error),
}
}