{"record":{"id":"3b844912d4575e9c","repo":"BigPizzaV3/CodexPlusPlus","slug":"provider-sync-lock-ownership-changed-before-release","errorCode":null,"errorMessage":"provider-sync lock ownership changed before release","messagePattern":"provider-sync lock ownership changed before release","errorType":"exception","errorClass":"std::io::Error (Other)","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/provider_sync.rs","lineNumber":72,"sourceCode":"    /// 锁存在、owner 信息不可读，但仍在宽限期内——无法判断是否有人正在建锁。\n    Indeterminate,\n}\n\n#[derive(Debug)]\npub struct ProviderSyncLifecycleGuard {\n    lock_dir: PathBuf,\n    lock_file: File,\n    lock_id: String,\n    directory_released: bool,\n    file_unlocked: bool,\n}\n\nimpl ProviderSyncLifecycleGuard {\n    /// Releases both compatibility and OS ownership before a caller starts a successor process.\n    /// A mismatched owner is an ABA conflict and must block the successor instead of deleting it.\n    pub fn release(mut self) -> std::io::Result<()> {\n        if !release_owned_lock(&self.lock_dir, &self.lock_id)? {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::Other,\n                \"provider-sync lock ownership changed before release\",\n            ));\n        }\n        self.directory_released = true;\n        FileExt::unlock(&self.lock_file)?;\n        self.file_unlocked = true;\n        Ok(())\n    }\n}\n\nimpl Drop for ProviderSyncLifecycleGuard {\n    fn drop(&mut self) {\n        if !self.directory_released {\n            let _ = release_owned_lock(&self.lock_dir, &self.lock_id);\n        }\n        if !self.file_unlocked {\n            let _ = FileExt::unlock(&self.lock_file);","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-data/src/provider_sync.rs#L54-L90","documentation":"ProviderSyncLifecycleGuard::release first checks OS-level lock ownership before releasing the directory lock. If release_owned_lock reports the current process no longer owns the lock, an ABA conflict is detected (another process took over between acquire and release); release refuses to proceed so it cannot delete a lock that a successor process now owns.","triggerScenarios":"Calling release() on a ProviderSyncLifecycleGuard after the OS lock ownership changed — typically another process stole or re-created the lock in lock_dir, or the lock file was deleted and re-created between acquire and release.","commonSituations":"Two app instances racing on provider sync; an external cleanup job deleted the lock file mid-run; long-held guards spanning a restart where a successor force-removed the lock; filesystem shared over NFS with inconsistent lock semantics.","solutions":["Investigate which other process acquired the lock (check lock_dir contents/PIDs) and ensure single-instance semantics","Re-acquire a fresh guard instead of releasing the stale one; treat this instance's session as invalidated","Avoid external cleanup of lock_dir while a sync is in progress","If using a shared/network filesystem, move locks to a local disk to get reliable ownership semantics"],"exampleFix":"// before\nmatch guard.release() { Ok(_) => spawn_successor(), Err(_) => {} }\n// after\nmatch guard.release() {\n    Ok(_) => spawn_successor(),\n    Err(e) if e.to_string().contains(\"ownership changed\") => {\n        // ABA: do not spawn successor blindly; re-sync state first\n        reconcile_with_current_owner(&lock_dir)?;\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"// before releasing, confirm we still hold the lock\nconst stillOurs = readLockOwner(lockDir) === myPid;\nif (!stillOurs) console.warn('lock ownership changed; do not release blindly');","typeGuard":"const isOwnershipConflict = (e: Error): boolean => e.message.includes('ownership changed');","tryCatchPattern":"match guard.release() {\n    Ok(()) => spawn_successor(),\n    Err(e) if e.to_string().contains(\"ownership changed\") => {\n        // ABA conflict: don't delete; re-acquire fresh lock and reconcile\n        reconcile_and_reacquire(&lock_dir)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep guard lifetimes short so ownership can't change mid-run","Never let external jobs delete lock_dir contents while sync is active","Store owner PID/uuid in the lock file and verify before every release"],"tags":["locking","concurrency","aba-conflict","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}