diff --git a/CHANGELOG.md b/CHANGELOG.md index c3cfd75..4e0b6ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ User-visible changes are recorded here. ## Unreleased +### Fixed + +- Release the workspace lock when its final owner drops, even if a file + descriptor was duplicated or inherited. + ## 0.0.1 - 2026-09-16 ### Added diff --git a/src/workspace.rs b/src/workspace.rs index 02396db..cc7144e 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -124,6 +124,15 @@ pub(crate) struct Workspace { pub(crate) remove_fault: std::sync::Mutex>, } +impl Drop for Workspace { + fn drop(&mut self) { + // A duplicated or inherited descriptor keeps a flock alive after this + // file is closed. Release the logical workspace owner explicitly so + // such descriptors cannot outlive the Workspace's ownership. + let _ = self._lock.unlock(); + } +} + #[cfg(test)] #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) enum PersistFault { @@ -516,6 +525,19 @@ mod tests { assert!(Workspace::open(temp.path()).is_ok()); } + #[cfg(unix)] + #[test] + fn duplicated_lock_descriptor_does_not_outlive_workspace() { + let temp = tempfile::tempdir().unwrap(); + let workspace = Workspace::open(temp.path()).unwrap(); + let duplicate = workspace._lock.try_clone().unwrap(); + drop(workspace); + + let reopened = Workspace::open(temp.path()).unwrap(); + drop(duplicate); + drop(reopened); + } + #[cfg(unix)] #[test] fn unregistered_non_utf8_directory_is_ignored() {