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

  1. Ensure each lease is released exactly once (idempotent wrapper or a 'released' flag)
  2. If racing with the recycler, use the recycler's completion signaling instead of manual release
  3. 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

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


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/f2f8d72e38b99715. Report an issue: GitHub.