{"record":{"id":"147503eaa8bf2e88","repo":"sgl-project/sglang","slug":"host-reference-counter-is-already-zero","errorCode":null,"errorMessage":"Host reference counter is already zero.","messagePattern":"Host reference counter is already zero\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/mem_cache/radix_cache.py","lineNumber":285,"sourceCode":"\n    @property\n    def evicted(self):\n        return self.value is None\n\n    @property\n    def backuped(self):\n        return self.host_value is not None\n\n    def protect_host(self):\n        \"\"\"Protect the host value from eviction.\"\"\"\n        self.host_ref_counter += 1\n\n    def release_host(self):\n        \"\"\"Release the host value, allowing it to be evicted.\"\"\"\n        if self.host_ref_counter > 0:\n            self.host_ref_counter -= 1\n        else:\n            raise RuntimeError(\"Host reference counter is already zero.\")\n\n    def get_last_hash_value(self) -> Optional[str]:\n        \"\"\"Returns the hash value of the last page in this node.\"\"\"\n        if self.hash_value is None or len(self.hash_value) == 0:\n            return None\n        return self.hash_value[-1]\n\n    def get_prefix_hash_values(self, node: TreeNode) -> List[str]:\n        if node is None or node.hash_value is None:\n            return []\n\n        return node.get_prefix_hash_values(node.parent) + node.hash_value\n\n    def __lt__(self, other: TreeNode):\n        return self.last_access_time < other.last_access_time\n\n\nclass RadixCache(BasePrefixCache):","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/mem_cache/radix_cache.py#L267-L303","documentation":"TreeNode.release_host() raises RuntimeError when the host (CPU) reference counter is already zero, i.e. the node's host value is not currently pinned. This guards against double-release of host-side KV cache values in the hierarchical cache.","triggerScenarios":"Calling node.release_host() twice without an intervening lock_host(); releasing a node whose host value was already evicted or never written through to host.","commonSituations":"Retraction/eviction paths that race with request completion and both call release_host; refactored code paths that release host refs in a loop plus an unconditional cleanup; bug in ref-count pairing after a cache_finished_req.","solutions":["Audit call sites to guarantee each lock_host() has exactly one matching release_host()","Guard with: if node.host_ref_counter > 0: node.release_host()","Check for duplicate cleanup in retraction + eviction handlers and deduplicate"],"exampleFix":"# before\nnode.release_host()\n# after\nif node.host_ref_counter > 0:\n    node.release_host()","handlingStrategy":"type-guard","validationCode":"if node.host_ref_counter > 0:\n    node.release_host()","typeGuard":"def can_release_host(node) -> bool:\n    return node.host_ref_counter > 0","tryCatchPattern":"try:\n    node.release_host()\nexcept RuntimeError:\n    logger.warning(\"host ref already zero for node %s\", node)\n    # idempotent cleanup: ignore","preventionTips":["Pair every lock_host with exactly one release_host in a try/finally","Audit retraction paths for double release"],"tags":["radix-cache","refcount","hicache","double-release","runtime-error"],"backgroundTag":"reference-count-underflow","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}