{"record":{"id":"c53f6de81577a6bc","repo":"vllm-project/vllm","slug":"attempted-to-free-more-buffers-than-allocated","errorCode":null,"errorMessage":"Attempted to free more buffers than allocated","messagePattern":"Attempted to free more buffers than allocated","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"vllm/distributed/kv_transfer/kv_connector/v1/hf3fs/utils/gather_scatter_helper.py","lineNumber":285,"sourceCode":"            return []\n\n        if self._inuse_count + count <= self._max_count:\n            self._inuse_count += count\n            result = self._free_buffers[-count:]\n            del self._free_buffers[-count:]\n            return result\n        return None\n\n    def free_buffer(self, buffers: list[torch.Tensor]) -> None:\n        \"\"\"Return buffers to the pool.\"\"\"\n        if not buffers:\n            return\n\n        if self._inuse_count >= len(buffers):\n            self._inuse_count -= len(buffers)\n            self._free_buffers.extend(buffers)\n        else:\n            raise RuntimeError(\"Attempted to free more buffers than allocated\")\n\n\nlogger = init_logger(__name__)\n","sourceCodeStart":267,"sourceCodeEnd":289,"githubUrl":"https://github.com/vllm-project/vllm/blob/c794754062d49a8fdb63ab3c5215b488b865030c/vllm/distributed/kv_transfer/kv_connector/v1/hf3fs/utils/gather_scatter_helper.py#L267-L289","documentation":"RuntimeError from the HF3FS gather/scatter buffer pool's free_buffer when the number of buffers being returned exceeds the pool's in-use count (self._inuse_count). The pool only tracks how many buffers it handed out; returning more than that means the caller is double-freeing or freeing buffers obtained elsewhere. It guards against pool accounting corruption.","triggerScenarios":"Calling free_buffer twice on the same buffer list; mixing buffers from two pools in one free_buffer call; freeing an empty-but-nonnull list after the pool was already drained; error paths that free and then the normal path frees again.","commonSituations":"Exception handling that returns buffers, followed by a finally block that returns them again; refactoring that changed buffer ownership without updating free sites.","solutions":["Audit all free_buffer call sites and guarantee each acquired buffer list is freed exactly once (use try/finally with a 'freed' flag or clear the list after freeing).","Do not return buffers that were not allocated from this pool instance.","After fixing ownership, if the error persists, log _inuse_count and len(buffers) before free to find the double-free."],"exampleFix":"# before\nbufs = pool.get_buffers(n)\ntry:\n    ...\nfinally:\n    pool.free_buffer(bufs)\n    pool.free_buffer(bufs)  # double free\n# after\nbufs = pool.get_buffers(n)\ntry:\n    ...\nfinally:\n    pool.free_buffer(bufs)\n    bufs = []","handlingStrategy":"validation","validationCode":"def can_free(pool, buffers):\n    return len(buffers) <= pool._inuse_count","typeGuard":null,"tryCatchPattern":"Catch RuntimeError at free sites, log pool in-use count vs freed count, and treat as a programming bug — fix the ownership path rather than swallowing.","preventionTips":["Free each acquired buffer list exactly once; null it after free","Centralize buffer ownership in one class","Add a debug counter asserting inuse never goes negative in tests"],"tags":["hf3fs","buffer-pool","double-free","kv-transfer","resource-management"],"backgroundTag":null,"analyzedSha":"c794754062d49a8fdb63ab3c5215b488b865030c","analyzedAt":"2026-08-14T21:17:39.825Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}