sgl-project/sglang · error · RuntimeError
Cannot release inactive {self.transport_name} pool lease (sl
Error message
Cannot release inactive {self.transport_name} pool lease (slot={lease.slot}, generation={lease.generation}) What it means
_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.
Source
Thrown at python/sglang/srt/multimodal/transport/memory_pool.py:275
raise RuntimeError(f"{self.transport_name} pool slot generation exhausted")
self._slot_generations[slot] = generation
ready_byte_offset = slot * self.control_words_per_slot * CONTROL_WORD_BYTES
lease = PoolLease(
start=start,
end=start + allocation_bytes,
nbytes=nbytes,
slot=slot,
generation=generation,
ready_byte_offset=ready_byte_offset,
ack_byte_offset=ready_byte_offset + CONTROL_WORD_BYTES,
)
self._occupied[slot] = lease
return lease
def _release_locked(self, lease: PoolLease) -> None:
active_lease = self._occupied.get(lease.slot)
if active_lease != lease:
raise RuntimeError(
f"Cannot release inactive {self.transport_name} pool lease "
f"(slot={lease.slot}, generation={lease.generation})"
)
del self._occupied[lease.slot]
self._available_slots.append(lease.slot)
self._available_ranges.append((lease.start, lease.end))
def _merge_ranges_locked(self) -> None:
merged = []
for start, end in sorted(self._available_ranges):
if merged and merged[-1][1] == start:
merged[-1] = (merged[-1][0], end)
else:
merged.append((start, end))
self._available_ranges = merged
def _recycle_ready_leases_locked(self) -> None:
if not self._occupied:View on GitHub (pinned to 0132848349)
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
Example fix
# before
pool.release(lease) # may be called twice on error paths
# after
if not lease_released:
pool.release(lease)
lease_released = True Defensive patterns
Strategy: try-catch
Try / catch
try:
pool.release(lease)
except RuntimeError as e:
if 'inactive' in str(e):
logger.warning('lease already released: %s', e)
else:
raise Prevention
- Enforce single-owner release with a flag or context manager per lease
- Don't manually release leases the recycler thread owns
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- recycle_interval must be positive
- byte_tensor must be a sufficiently large contiguous uint8 te
- {transport_name} pool is too small after control metadata: p
- {self.transport_name} pool slot generation exhausted
- CUDA VMM pool has no occupied slice at control offset {contr
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/f2f8d72e38b99715.
Report an issue: GitHub.