{"record":{"id":"3a9572bbf79651c6","repo":"itsfatduck/optimizerDuck","slug":"timed-out-waiting-for-revert-file-lock-0","errorCode":null,"errorMessage":"Timed out waiting for revert file lock ({0}).","messagePattern":"Timed out waiting for revert file lock \\((.+?)\\)\\.","errorType":"exception","errorClass":"TimeoutException","httpStatus":null,"severity":"error","filePath":"optimizerDuck/Services/Revert/RevertManager.cs","lineNumber":589,"sourceCode":"                File.Delete(tmp);\n                logger?.LogInformation(\"Removed orphaned revert temp file {Path}\", tmp);\n            }\n            catch (Exception ex)\n            {\n                logger?.LogWarning(ex, \"Failed to remove orphaned temp file {Path}\", tmp);\n            }\n        }\n    }\n\n    private static async Task<SemaphoreSlim> AcquireFileLockAsync(Guid id)\n    {\n        var lockObj = _fileLocks.GetOrAdd(id, _ => new SemaphoreSlim(1, 1));\n        if (\n            !await lockObj\n                .WaitAsync(TimeSpan.FromSeconds(FileLockTimeoutSeconds))\n                .ConfigureAwait(false)\n        )\n            throw new TimeoutException(\n                string.Format(\"Timed out waiting for revert file lock ({0}).\", id)\n            );\n\n        return lockObj;\n    }\n\n    private async Task<List<(int Index, IRevertStep Step)>> LoadStepsAsync(Guid id)\n    {\n        var data = await LoadAsync(GetFilePath(id), _logger).ConfigureAwait(false);\n        if (data == null || data.Steps.Length == 0)\n            return [];\n\n        var seenIndexes = new HashSet<int>();\n        var result = new List<(int, IRevertStep)>();\n\n        foreach (var stepData in data.Steps.Where(s => s != null))\n        {\n            if (stepData!.Index <= 0)","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/itsfatduck/optimizerDuck/blob/36acf585ae122a09f0e29eb4acedfaf94e81c0de/optimizerDuck/Services/Revert/RevertManager.cs#L571-L607","documentation":"Concurrency guard fired in RevertManager.AcquireFileLockAsync: the per-revert-file SemaphoreSlim (keyed by the revert-data Guid) could not be acquired within FileLockTimeoutSeconds (30s). This means another thread/process path — an apply appending revert steps, a concurrent revert, or a crashed holder that never released — is holding the lock for the same {optimizationId}.json. It is a sentinel for lock contention or a leaked lock, not corruption of the file itself.","triggerScenarios":"Thrown at optimizerDuck/Services/Revert/RevertManager.cs:589 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Retry the operation after a short delay — transient contention (concurrent apply + revert of the same optimization) usually clears once the holder finishes","Check for concurrent calls targeting the same optimization id (e.g., double-clicked revert or apply racing a background retry) and serialize them at the call site","If it persists, a lock holder likely deadlocked or the process is hung; restarting the app releases all in-process SemaphoreSlim locks","Review any recent code path that acquires the file lock without a matching finally-release"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"36acf585ae122a09f0e29eb4acedfaf94e81c0de","analyzedAt":"2026-09-13T08:30:13.845Z","contentChangedAt":"2026-09-13T08:30:13.845Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}