diff --git a/python/sglang/srt/mem_cache/unified_radix_cache.py b/python/sglang/srt/mem_cache/unified_radix_cache.py index 5cb53fac7..1e24a987f 100644 --- a/python/sglang/srt/mem_cache/unified_radix_cache.py +++ b/python/sglang/srt/mem_cache/unified_radix_cache.py @@ -903,7 +903,9 @@ class UnifiedRadixCache(BasePrefixCache): else: self.free_kv_row(req.kv, [(req.kv.cache_protected_len, kv_len_to_handle)]) - self._dec_req_lock(req, skip_swa=req.swa_prefix_lock_released) + # Synthetic profiling requests may own KV without locking a tree node. + if req.last_node is not None: + self._dec_req_lock(req, skip_swa=req.swa_prefix_lock_released) if is_insert and result is not None and result.last_device_node is not None: req.last_node = result.last_device_node diff --git a/test/registered/unit/mem_cache/test_unified_radix_lock_ref.py b/test/registered/unit/mem_cache/test_unified_radix_lock_ref.py new file mode 100644 index 000000000..95e39bf13 --- /dev/null +++ b/test/registered/unit/mem_cache/test_unified_radix_lock_ref.py @@ -0,0 +1,45 @@ +"""CPU-only tests for UnifiedRadixCache request lock lifecycle.""" + +import unittest +from array import array +from types import SimpleNamespace +from unittest.mock import MagicMock + +import torch + +from sglang.srt.mem_cache.unified_radix_cache import UnifiedRadixCache +from sglang.test.ci.ci_register import register_cpu_ci + +register_cpu_ci(est_time=2, suite="base-a-test-cpu") + + +class TestUnifiedRadixLockRefScenarios(unittest.TestCase): + def test_no_insert_without_last_node_skips_lock_release(self): + cache = object.__new__(UnifiedRadixCache) + cache.session = MagicMock() + cache.session.try_cache_finished_req.return_value = False + cache.disable = False + cache.req_to_token_pool = MagicMock() + cache.req_to_token_pool.req_to_token = torch.arange(8).reshape(1, 8) + cache.free_kv_row = MagicMock() + cache._dec_req_lock = MagicMock() + cache._components_tuple = () + cache.enable_session_radix_cache = False + + kv = SimpleNamespace(req_pool_idx=0, cache_protected_len=0) + req = SimpleNamespace( + origin_input_ids=array("q", [1, 2, 3]), + output_ids=array("q"), + kv=kv, + last_node=None, + swa_prefix_lock_released=False, + ) + + cache.cache_finished_req(req, is_insert=False, kv_len_to_handle=3) + + cache.free_kv_row.assert_called_once_with(kv, [(0, 3)]) + cache._dec_req_lock.assert_not_called() + + +if __name__ == "__main__": + unittest.main()