{"record":{"id":"5e57c096a50e8e45","repo":"microsoft/FASTER","slug":"attempting-to-return-an-already-free-block","errorCode":null,"errorMessage":"Attempting to return an already-free block","messagePattern":"Attempting to return an already-free block","errorType":"exception","errorClass":"FasterException","httpStatus":null,"severity":"critical","filePath":"cs/src/core/Utilities/BufferPool.cs","lineNumber":79,"sourceCode":"        private int level;\n        internal int Level => this.level\n#if CHECK_FREE\n            & ~kFreeBitMask\n#endif\n            ;\n\n        internal SectorAlignedBufferPool pool;\n\n#if CHECK_FREE\n        internal bool Free\n        {\n            get => (level & kFreeBitMask) != 0;\n            set \n            {\n                if (value)\n                {\n                    if (Free)\n                        throw new FasterException(\"Attempting to return an already-free block\");\n                    this.level |= kFreeBitMask;\n                }\n                else\n                {\n                    if (!Free)\n                        throw new FasterException(\"Attempting to allocate an already-allocated block\");\n                    this.level &= ~kFreeBitMask;\n                }\n            }\n        }\n#endif // CHECK_FREE\n\n        /// <summary>\n        /// Default constructor\n        /// </summary>\n        public SectorAlignedMemory(int level = default)\n        {\n            this.level = level;","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/Utilities/BufferPool.cs#L61-L97","documentation":"SectorAlignedBufferPool blocks carry a free bit (kFreeBitMask) in their level field. The Free property setter validates transitions: marking a block free when it is already free throws \"Attempting to return an already-free block\" (a double-free), and marking an allocated block as allocated throws the symmetric error. This indicates the pool's block bookkeeping is corrupted - the same memory sector was released twice or ownership was mishandled.","triggerScenarios":"Internal double-dispose of sector-aligned buffers: calling Dispose/Return twice on the same buffer object, sharing a buffer between threads without synchronization, or a library bug where the same SectorAlignedMemory is returned to the pool by two code paths.","commonSituations":"User code reusing or disposing buffers obtained from FasterLog/FASTER allocators after ownership transferred back to the library; wrapping pool buffers in another disposable that is disposed twice; use-after-free patterns where a stale buffer reference is returned.","solutions":["Audit code that obtains SectorAlignedMemory from FASTER APIs and ensure Dispose/Return is called exactly once per acquisition.","Do not share pool-allocated buffers across threads or keep copies of the struct after disposing it.","If the buffer was returned by a library API (e.g. log scan), let the library manage its lifetime - do not dispose or return it yourself.","If it occurs inside the library with single-threaded, correct usage, report a bug with the call stack."],"exampleFix":"// before\nvar buf = allocator.GetBuffer();\nbuf.Return();\n// ... later in cleanup\nbuf.Return(); // double free\n// after\nvar buf = allocator.GetBuffer();\nbuf.Return();\nbuf.ReturnPool = false; // or drop the reference after first Return()","handlingStrategy":"try-catch","validationCode":"// Track buffer ownership explicitly; return each buffer exactly once\nprivate readonly HashSet<SectorAlignedMemory> returned = new();\nvoid SafeReturn(SectorAlignedMemory buf)\n{\n    if (!returned.Add(buf)) throw new InvalidOperationException(\"Buffer already returned\");\n    buf.Return();\n}","typeGuard":"bool CanReturn(SectorAlignedMemory buf) => !buf.Free; // Free == already returned to the pool","tryCatchPattern":"try\n{\n    buf.Return();\n}\ncatch (FasterException ex) when (ex.Message.Contains(\"already-free block\"))\n{\n    logger.LogError(ex, \"Double-free of sector-aligned buffer detected; ownership bug in caller\");\n}","preventionTips":["Return each pool buffer exactly once; use flags or ownership wrappers to enforce it.","Never dispose or return buffers owned by the library (e.g. log scan results).","Avoid sharing SectorAlignedMemory across threads without synchronization.","Check the Free property before any manual Return() call."],"tags":["faster","buffer-pool","double-free","memory"],"backgroundTag":"internal-invariant-violation","analyzedSha":"321d872eabda6a0345c8bd76419f89723ed864ae","analyzedAt":"2026-09-15T22:18:00.693Z","contentChangedAt":"2026-09-15T22:18:00.693Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}