diff --git a/python/sglang/srt/mem_cache/hiradix_cache.py b/python/sglang/srt/mem_cache/hiradix_cache.py index 9a7f620fc..ad3d2b57d 100644 --- a/python/sglang/srt/mem_cache/hiradix_cache.py +++ b/python/sglang/srt/mem_cache/hiradix_cache.py @@ -1553,6 +1553,16 @@ class HiRadixCache(RadixCache): self.ongoing_l3_reload[existing]["waiters"].append(rid) # piggyback on the in-flight reload self.cp_l3_reload_done[rid] = False continue + # Re-validate the clean-attach against the POST-admit tree (the contract _cp_l3_reload_attach_ok + # documents: "checked at reserve AND at admission -- the tree may change in between"). valid_cands + # was built BEFORE _cp_l3_admit_reload ran this tick; a same-tick admit of THIS suffix inserts its + # node and pops its inflight key while leaving the l3rl object committed-and-live in the allocator. + # Without this recheck the dedup guard above misses (inflight key gone) and reserve() below would + # raise "duplicate live reservation" on the still-live object. If the suffix is now present, drop + # the candidate; the (un-held) request re-matches and hits the just-inserted L2 node. Rank-uniform + # (replicated radix + rank-uniform admit -> every rank skips the same candidate). + if not self._cp_l3_reload_attach_ok(lhn, skey): + continue if len(self.ongoing_l3_reload) >= self.cp_l3_reload_max_inflight: continue # cap reached (len is replicated) -> recompute ranges = self._cp_l3_reserve_reload_object(reload_key, store.payloads, c, allocator)