{"record":{"id":"f2f8d72e38b99715","repo":"sgl-project/sglang","slug":"cannot-release-inactive-self-transport-name-pool","errorCode":null,"errorMessage":"Cannot release inactive {self.transport_name} pool lease (slot={lease.slot}, generation={lease.generation})","messagePattern":"Cannot release inactive (.+?) pool lease \\(slot=(.+?), generation=(.+?)\\)","errorType":"error_code","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/multimodal/transport/memory_pool.py","lineNumber":275,"sourceCode":"            raise RuntimeError(f\"{self.transport_name} pool slot generation exhausted\")\n        self._slot_generations[slot] = generation\n        ready_byte_offset = slot * self.control_words_per_slot * CONTROL_WORD_BYTES\n        lease = PoolLease(\n            start=start,\n            end=start + allocation_bytes,\n            nbytes=nbytes,\n            slot=slot,\n            generation=generation,\n            ready_byte_offset=ready_byte_offset,\n            ack_byte_offset=ready_byte_offset + CONTROL_WORD_BYTES,\n        )\n        self._occupied[slot] = lease\n        return lease\n\n    def _release_locked(self, lease: PoolLease) -> None:\n        active_lease = self._occupied.get(lease.slot)\n        if active_lease != lease:\n            raise RuntimeError(\n                f\"Cannot release inactive {self.transport_name} pool lease \"\n                f\"(slot={lease.slot}, generation={lease.generation})\"\n            )\n        del self._occupied[lease.slot]\n        self._available_slots.append(lease.slot)\n        self._available_ranges.append((lease.start, lease.end))\n\n    def _merge_ranges_locked(self) -> None:\n        merged = []\n        for start, end in sorted(self._available_ranges):\n            if merged and merged[-1][1] == start:\n                merged[-1] = (merged[-1][0], end)\n            else:\n                merged.append((start, end))\n        self._available_ranges = merged\n\n    def _recycle_ready_leases_locked(self) -> None:\n        if not self._occupied:","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/multimodal/transport/memory_pool.py#L257-L293","documentation":"_release_locked only accepts the exact PoolLease object currently registered as active for that slot. Releasing a stale lease (wrong generation), an already-released lease, or a different object for the same slot is a bug: it would corrupt the free list.","triggerScenarios":"Calling release twice on the same lease; holding an old lease after the recycler already released it; manually constructing a lease with a stale generation and calling release.","commonSituations":"Missing a single-release ownership rule in caller code; racing with the automatic recycler thread that reclaims ready leases; duplicate release in error-handling paths.","solutions":["Ensure each lease is released exactly once (idempotent wrapper or a 'released' flag)","If racing with the recycler, use the recycler's completion signaling instead of manual release","Catch RuntimeError on release and log/ignore only for already-released duplicates, never for corruption bugs"],"exampleFix":"# before\npool.release(lease)  # may be called twice on error paths\n# after\nif not lease_released:\n    pool.release(lease)\n    lease_released = True","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    pool.release(lease)\nexcept RuntimeError as e:\n    if 'inactive' in str(e):\n        logger.warning('lease already released: %s', e)\n    else:\n        raise","preventionTips":["Enforce single-owner release with a flag or context manager per lease","Don't manually release leases the recycler thread owns"],"tags":["multimodal","memory-pool","double-free","lease-lifecycle"],"backgroundTag":"double-release-resource","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}