{"record":{"id":"6fca4fdfdc6e4a0d","repo":"microsoft/garnet","slug":"failed-to-acquire-inprogress-lock-at-nameof-postb","errorCode":null,"errorMessage":"Failed to acquire inProgress lock at {nameof(PostBatchKeyConsistentReadCallback)}","messagePattern":"Failed to acquire inProgress lock at (.+?)","errorType":"exception","errorClass":"GarnetException","httpStatus":null,"severity":"error","filePath":"libs/server/AOF/ReadConsistency/ReplicaReadSessionContext.cs","lineNumber":239,"sourceCode":"                    consistencyManager.PreBatchKeyConsistentRead(key.ReadOnlySpan, ref batchReadContext, readTimeout, consistentReadCts.Token, out var hash);\n                    keyHashCache[i] = hash;\n                }\n            }\n            finally\n            {\n                inProgress.ReadUnlock();\n            }\n        }\n\n        /// <summary>\n        /// Validate keys have not changed after reading a key batch.\n        /// </summary>\n        /// <param name=\"keyCount\"></param>\n        /// <returns></returns>\n        public bool PostBatchKeyConsistentReadCallback(int keyCount)\n        {\n            if (!inProgress.TryReadLock())\n                throw new GarnetException($\"Failed to acquire inProgress lock at {nameof(PostBatchKeyConsistentReadCallback)}\");\n            try\n            {\n                var consistencyManager = appendOnlyFile.readConsistencyManager;\n                for (var i = 0; i < keyCount; i++)\n                {\n                    var hash = keyHashCache[i];\n                    if (!consistencyManager.PostBatchKeyConsistentReadValidate(hash, ref batchReadContext))\n                        return false;\n                }\n\n                // Propagate batch context back to session context to maintain prefix consistency\n                // for subsequent single-key reads across different sublogs.\n                replicaReadContext.maximumSessionSequenceNumber = batchReadContext.maximumSessionSequenceNumber;\n                replicaReadContext.lastVirtualSublogIdx = batchReadContext.lastVirtualSublogIdx;\n                replicaReadContext.lastHash = batchReadContext.lastHash;\n\n                return true;\n            }","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/server/AOF/ReadConsistency/ReplicaReadSessionContext.cs#L221-L257","documentation":"Thrown by ReplicaReadSessionContext.PostBatchKeyConsistentReadCallback when TryReadLock() on the 'inProgress' lock fails. This is the post-read validation step for a batch: it validates each key's hash against the consistency manager and propagates the batch context back to the session. The read lock is unavailable because Dispose holds the write lock.","triggerScenarios":"The batch post-validation callback runs after a batch read completes, but the session is being disposed concurrently. The non-blocking TryReadLock fails and the method throws rather than blocking.","commonSituations":"Session teardown racing with batch-read completion; a cancellation path that disposes without waiting for in-flight batch reads; a re-entrancy bug.","solutions":["Ensure dispose does not run while batch reads are pending (drain first).","Guard the callback with a disposed check and catch GarnetException to treat the batch as invalidated.","Review the session close path to guarantee all PostBatch callbacks have completed.","Log the lock failure to correlate with dispose timing during incident analysis."],"exampleFix":"// before\nbool ok = session.PostBatchKeyConsistentReadCallback(keyCount);\n\n// after\nbool ok;\ntry { ok = session.PostBatchKeyConsistentReadCallback(keyCount); }\ncatch (GarnetException) { ok = false; /* session disposed; batch invalid */ }","handlingStrategy":"try-catch","validationCode":"if (session.IsDisposed) return false;\nreturn session.PostBatchKeyConsistentReadCallback(keyCount);","typeGuard":null,"tryCatchPattern":"try { return session.PostBatchKeyConsistentReadCallback(keyCount); }\ncatch (GarnetException ex) when (ex.Message.Contains(\"inProgress lock\"))\n{ return false; /* batch invalidated by session disposal */ }","preventionTips":["Ensure the session close path waits for all PostBatch callbacks to complete.","Guard post-read callbacks with a disposed check.","Treat lock failure as batch invalidation, not a hard crash."],"tags":["aof","read-consistency","locking","replica","batch","internal"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}