fix(hicache): isolate decode offload state per request (#37026)
Co-authored-by: Guanyi Chen <939416532@qq.com>
This commit is contained in:
@@ -93,25 +93,29 @@ class DecodeKVCacheOffloadManager:
|
|||||||
|
|
||||||
self.ongoing_offload = {}
|
self.ongoing_offload = {}
|
||||||
self.ongoing_backup = {}
|
self.ongoing_backup = {}
|
||||||
self.offloaded_state = {}
|
# A caller may reuse a rid as soon as the previous response finishes,
|
||||||
self.offload_inflight = {}
|
# while that request's asynchronous D2H copy is still in flight. Key
|
||||||
|
# lifecycle state by the Req instance so a late ack cannot mutate the
|
||||||
|
# new request's state.
|
||||||
|
self.offloaded_state: dict[Req, OffloadedState] = {}
|
||||||
|
self.offload_inflight: dict[Req, int] = {}
|
||||||
logger.info("Enable offload kv cache for decode side")
|
logger.info("Enable offload kv cache for decode side")
|
||||||
|
|
||||||
def release_host_resources(self) -> None:
|
def release_host_resources(self) -> None:
|
||||||
self.decode_host_mem_pool.destroy()
|
self.decode_host_mem_pool.destroy()
|
||||||
|
|
||||||
def _mark_offload_started(self, rid):
|
def _mark_offload_started(self, req: Req):
|
||||||
self.offload_inflight[rid] = self.offload_inflight.get(rid, 0) + 1
|
self.offload_inflight[req] = self.offload_inflight.get(req, 0) + 1
|
||||||
|
|
||||||
def _mark_offload_finished(self, rid):
|
def _mark_offload_finished(self, req: Req):
|
||||||
count = self.offload_inflight.get(rid, 0)
|
count = self.offload_inflight.get(req, 0)
|
||||||
if count <= 1:
|
if count <= 1:
|
||||||
self.offload_inflight.pop(rid, None)
|
self.offload_inflight.pop(req, None)
|
||||||
else:
|
else:
|
||||||
self.offload_inflight[rid] = count - 1
|
self.offload_inflight[req] = count - 1
|
||||||
|
|
||||||
def _has_inflight_offload(self, rid):
|
def _has_inflight_offload(self, req: Req):
|
||||||
return self.offload_inflight.get(rid, 0) > 0
|
return self.offload_inflight.get(req, 0) > 0
|
||||||
|
|
||||||
def offload_kv_cache(self, req) -> bool:
|
def offload_kv_cache(self, req) -> bool:
|
||||||
"""Offload incremental KV cache for decode side."""
|
"""Offload incremental KV cache for decode side."""
|
||||||
@@ -131,7 +135,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
prefill_offloaded_len = (
|
prefill_offloaded_len = (
|
||||||
len(req.origin_input_ids) // self.page_size * self.page_size
|
len(req.origin_input_ids) // self.page_size * self.page_size
|
||||||
)
|
)
|
||||||
state = self.offloaded_state.get(req.rid)
|
state = self.offloaded_state.get(req)
|
||||||
if state is None:
|
if state is None:
|
||||||
prefill_hashes = self._compute_prefix_hash(
|
prefill_hashes = self._compute_prefix_hash(
|
||||||
req.origin_input_ids[:prefill_offloaded_len]
|
req.origin_input_ids[:prefill_offloaded_len]
|
||||||
@@ -144,7 +148,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
inc_len=0,
|
inc_len=0,
|
||||||
last_hash=last_prefill_hash,
|
last_hash=last_prefill_hash,
|
||||||
)
|
)
|
||||||
self.offloaded_state[req.rid] = state
|
self.offloaded_state[req] = state
|
||||||
incremental_total = len(all_tokens) - state.prefill_len
|
incremental_total = len(all_tokens) - state.prefill_len
|
||||||
incremental_new = incremental_total - state.inc_len
|
incremental_new = incremental_total - state.inc_len
|
||||||
incremental_aligned_len = (
|
incremental_aligned_len = (
|
||||||
@@ -177,7 +181,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
logger.error(f"Not enough host memory for request {req.rid}")
|
logger.error(f"Not enough host memory for request {req.rid}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._mark_offload_started(req.rid)
|
self._mark_offload_started(req)
|
||||||
self.ongoing_offload[ack_id] = (
|
self.ongoing_offload[ack_id] = (
|
||||||
req,
|
req,
|
||||||
host_indices,
|
host_indices,
|
||||||
@@ -224,20 +228,20 @@ class DecodeKVCacheOffloadManager:
|
|||||||
end,
|
end,
|
||||||
) = self.ongoing_offload.pop(ack_id)
|
) = self.ongoing_offload.pop(ack_id)
|
||||||
|
|
||||||
self._mark_offload_finished(req.rid)
|
self._mark_offload_finished(req)
|
||||||
prior_hash = (
|
prior_hash = (
|
||||||
self.offloaded_state[req.rid].last_hash
|
self.offloaded_state[req].last_hash
|
||||||
if req.rid in self.offloaded_state
|
if req in self.offloaded_state
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
last_hash = self._trigger_backup(
|
last_hash = self._trigger_backup(
|
||||||
req, host_indices, incremental_tokens, start_time, prior_hash
|
req, host_indices, incremental_tokens, start_time, prior_hash
|
||||||
)
|
)
|
||||||
if req.rid in self.offloaded_state:
|
if req in self.offloaded_state:
|
||||||
self.offloaded_state[req.rid].last_hash = last_hash
|
self.offloaded_state[req].last_hash = last_hash
|
||||||
|
|
||||||
if req.finished() and not self._has_inflight_offload(req.rid):
|
if req.finished() and not self._has_inflight_offload(req):
|
||||||
state = self.offloaded_state.get(req.rid)
|
state = self.offloaded_state.get(req)
|
||||||
start_offset = state.prefill_len if state is not None else start
|
start_offset = state.prefill_len if state is not None else start
|
||||||
self._release_finished_req(req, start_offset)
|
self._release_finished_req(req, start_offset)
|
||||||
finish_count -= 1
|
finish_count -= 1
|
||||||
@@ -257,7 +261,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
# concurrent admission. Now consolidated here at request
|
# concurrent admission. Now consolidated here at request
|
||||||
# finish, where the request is guaranteed to no longer attend
|
# finish, where the request is guaranteed to no longer attend
|
||||||
# to those slots.
|
# to those slots.
|
||||||
state = self.offloaded_state.get(req.rid)
|
state = self.offloaded_state.get(req)
|
||||||
if state is not None and state.prefill_len > 0:
|
if state is not None and state.prefill_len > 0:
|
||||||
prefill_indices = self.req_to_token_pool.req_to_token[
|
prefill_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, : state.prefill_len
|
req.req_pool_idx, : state.prefill_len
|
||||||
@@ -283,8 +287,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
self.req_to_token_pool.free(req)
|
self.req_to_token_pool.free(req)
|
||||||
req.kv.mark_released()
|
req.kv.mark_released()
|
||||||
self.tree_cache.protected_size_ -= len(req.prefix_indices)
|
self.tree_cache.protected_size_ -= len(req.prefix_indices)
|
||||||
if req.rid in self.offloaded_state:
|
self.offloaded_state.pop(req, None)
|
||||||
del self.offloaded_state[req.rid]
|
|
||||||
|
|
||||||
def _check_backup_progress(self, finish_count):
|
def _check_backup_progress(self, finish_count):
|
||||||
"""Check the progress of backup from host to storage."""
|
"""Check the progress of backup from host to storage."""
|
||||||
@@ -328,7 +331,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
# guard against both sentinels here.
|
# guard against both sentinels here.
|
||||||
if req.req_pool_idx is None or req.req_pool_idx == -1:
|
if req.req_pool_idx is None or req.req_pool_idx == -1:
|
||||||
return
|
return
|
||||||
state = self.offloaded_state.get(req.rid)
|
state = self.offloaded_state.get(req)
|
||||||
if state is None:
|
if state is None:
|
||||||
prefill_len = len(req.origin_input_ids) // self.page_size * self.page_size
|
prefill_len = len(req.origin_input_ids) // self.page_size * self.page_size
|
||||||
inc_len = 0
|
inc_len = 0
|
||||||
@@ -338,10 +341,10 @@ class DecodeKVCacheOffloadManager:
|
|||||||
# Prefill-aligned slots are freed by _release_finished_req. Make
|
# Prefill-aligned slots are freed by _release_finished_req. Make
|
||||||
# sure state exists so it can find prefill_len.
|
# sure state exists so it can find prefill_len.
|
||||||
if state is None:
|
if state is None:
|
||||||
self.offloaded_state[req.rid] = OffloadedState(
|
self.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=prefill_len, inc_len=0, last_hash=None
|
prefill_len=prefill_len, inc_len=0, last_hash=None
|
||||||
)
|
)
|
||||||
if self._has_inflight_offload(req.rid):
|
if self._has_inflight_offload(req):
|
||||||
return
|
return
|
||||||
start_offset = prefill_len
|
start_offset = prefill_len
|
||||||
self._release_finished_req(req, start_offset)
|
self._release_finished_req(req, start_offset)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
|
|
||||||
def test_release_finished_req_frees_prefill_when_state_present(self):
|
def test_release_finished_req_frees_prefill_when_state_present(self):
|
||||||
"""
|
"""
|
||||||
When offloaded_state[rid].prefill_len > 0, _release_finished_req must
|
When offloaded_state[req].prefill_len > 0, _release_finished_req must
|
||||||
free the prefill-aligned slots in addition to the committed range.
|
free the prefill-aligned slots in addition to the committed range.
|
||||||
|
|
||||||
This is the consolidated free path that replaces the eager free that
|
This is the consolidated free path that replaces the eager free that
|
||||||
@@ -190,7 +190,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
kv_allocated_len=20,
|
kv_allocated_len=20,
|
||||||
rid=rid,
|
rid=rid,
|
||||||
)
|
)
|
||||||
manager.offloaded_state[rid] = OffloadedState(
|
manager.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=8, inc_len=0, last_hash=None
|
prefill_len=8, inc_len=0, last_hash=None
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
self.assertTrue(torch.equal(freed[0], expected_prefill))
|
self.assertTrue(torch.equal(freed[0], expected_prefill))
|
||||||
self.assertTrue(torch.equal(freed[1], expected_committed))
|
self.assertTrue(torch.equal(freed[1], expected_committed))
|
||||||
# State entry is removed at the end of _release_finished_req.
|
# State entry is removed at the end of _release_finished_req.
|
||||||
self.assertNotIn(rid, manager.offloaded_state)
|
self.assertNotIn(req, manager.offloaded_state)
|
||||||
|
|
||||||
def test_release_finished_req_skips_prefill_free_when_prefill_len_zero(self):
|
def test_release_finished_req_skips_prefill_free_when_prefill_len_zero(self):
|
||||||
"""
|
"""
|
||||||
@@ -219,7 +219,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
kv_allocated_len=10,
|
kv_allocated_len=10,
|
||||||
rid=rid,
|
rid=rid,
|
||||||
)
|
)
|
||||||
manager.offloaded_state[rid] = OffloadedState(
|
manager.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=0, inc_len=0, last_hash=None
|
prefill_len=0, inc_len=0, last_hash=None
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
self.assertTrue(torch.equal(freed[0], expected_prefill))
|
self.assertTrue(torch.equal(freed[0], expected_prefill))
|
||||||
self.assertTrue(torch.equal(freed[1], expected_committed))
|
self.assertTrue(torch.equal(freed[1], expected_committed))
|
||||||
# State is deleted by _release_finished_req on the way out.
|
# State is deleted by _release_finished_req on the way out.
|
||||||
self.assertNotIn(rid, manager.offloaded_state)
|
self.assertNotIn(req, manager.offloaded_state)
|
||||||
|
|
||||||
def test_unfinished_offload_ack_does_not_free_incremental_slots(self):
|
def test_unfinished_offload_ack_does_not_free_incremental_slots(self):
|
||||||
manager, freed = _make_manager(pool_size=32)
|
manager, freed = _make_manager(pool_size=32)
|
||||||
@@ -267,10 +267,10 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=1
|
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=1
|
||||||
)
|
)
|
||||||
req.finished.return_value = False
|
req.finished.return_value = False
|
||||||
manager.offloaded_state[req.rid] = OffloadedState(
|
manager.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=4, inc_len=4, last_hash=None
|
prefill_len=4, inc_len=4, last_hash=None
|
||||||
)
|
)
|
||||||
manager.offload_inflight[req.rid] = 1
|
manager.offload_inflight[req] = 1
|
||||||
manager.ongoing_offload[7] = (
|
manager.ongoing_offload[7] = (
|
||||||
req,
|
req,
|
||||||
torch.arange(4, 8, dtype=torch.int64),
|
torch.arange(4, 8, dtype=torch.int64),
|
||||||
@@ -289,7 +289,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(freed, [])
|
self.assertEqual(freed, [])
|
||||||
manager.req_to_token_pool.free.assert_not_called()
|
manager.req_to_token_pool.free.assert_not_called()
|
||||||
self.assertNotIn(req.rid, manager.offload_inflight)
|
self.assertNotIn(req, manager.offload_inflight)
|
||||||
|
|
||||||
def test_offload_kv_cache_tracks_inflight_write_until_ack(self):
|
def test_offload_kv_cache_tracks_inflight_write_until_ack(self):
|
||||||
manager, freed = _make_manager(pool_size=32, page_size=4)
|
manager, freed = _make_manager(pool_size=32, page_size=4)
|
||||||
@@ -312,8 +312,8 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
did_offload = manager.offload_kv_cache(req)
|
did_offload = manager.offload_kv_cache(req)
|
||||||
|
|
||||||
self.assertTrue(did_offload)
|
self.assertTrue(did_offload)
|
||||||
self.assertEqual(manager.offload_inflight[req.rid], 1)
|
self.assertEqual(manager.offload_inflight[req], 1)
|
||||||
self.assertEqual(manager.offloaded_state[req.rid].inc_len, 4)
|
self.assertEqual(manager.offloaded_state[req].inc_len, 4)
|
||||||
manager.cache_controller.write.assert_called_once()
|
manager.cache_controller.write.assert_called_once()
|
||||||
|
|
||||||
manager.cache_controller.ack_write_queue = [
|
manager.cache_controller.ack_write_queue = [
|
||||||
@@ -324,23 +324,70 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
manager._check_offload_progress(1)
|
manager._check_offload_progress(1)
|
||||||
|
|
||||||
self.assertEqual(freed, [])
|
self.assertEqual(freed, [])
|
||||||
self.assertNotIn(req.rid, manager.offload_inflight)
|
self.assertNotIn(req, manager.offload_inflight)
|
||||||
|
|
||||||
|
def test_reused_rid_does_not_share_offload_lifecycle(self):
|
||||||
|
manager, _ = _make_manager(pool_size=32, page_size=4)
|
||||||
|
manager.cache_controller = MagicMock()
|
||||||
|
manager.cache_controller.get_hash_str.return_value = "prefill_hash"
|
||||||
|
manager.cache_controller.write.return_value = torch.arange(
|
||||||
|
4, 8, dtype=torch.int64
|
||||||
|
)
|
||||||
|
manager.decode_host_mem_pool = MagicMock()
|
||||||
|
manager.request_counter = 0
|
||||||
|
manager.offload_stride = 4
|
||||||
|
|
||||||
|
old_req = _make_mock_req(
|
||||||
|
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid="reused"
|
||||||
|
)
|
||||||
|
new_req = _make_mock_req(
|
||||||
|
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid="reused"
|
||||||
|
)
|
||||||
|
for req in (old_req, new_req):
|
||||||
|
req.origin_input_ids = [0, 1, 2, 3]
|
||||||
|
req.output_ids = [4, 5, 6, 7, 8]
|
||||||
|
req.finished.return_value = False
|
||||||
|
|
||||||
|
self.assertTrue(manager.offload_kv_cache(old_req))
|
||||||
|
old_req.finished.return_value = True
|
||||||
|
|
||||||
|
# A completed request leaves the API before its asynchronous D2H copy
|
||||||
|
# necessarily finishes, so a caller can reuse the same rid here.
|
||||||
|
self.assertTrue(manager.offload_kv_cache(new_req))
|
||||||
|
self.assertIsNot(old_req, new_req)
|
||||||
|
self.assertIn(old_req, manager.offloaded_state)
|
||||||
|
self.assertIn(new_req, manager.offloaded_state)
|
||||||
|
self.assertEqual(manager.offload_inflight[old_req], 1)
|
||||||
|
self.assertEqual(manager.offload_inflight[new_req], 1)
|
||||||
|
|
||||||
|
manager.cache_controller.ack_write_queue = [
|
||||||
|
HiCacheAck(None, _FinishedEvent(), [1])
|
||||||
|
]
|
||||||
|
manager._trigger_backup = MagicMock(return_value="old_last_hash")
|
||||||
|
manager._check_offload_progress(1)
|
||||||
|
|
||||||
|
self.assertNotIn(old_req, manager.offloaded_state)
|
||||||
|
self.assertNotIn(old_req, manager.offload_inflight)
|
||||||
|
self.assertIn(new_req, manager.offloaded_state)
|
||||||
|
self.assertEqual(manager.offloaded_state[new_req].inc_len, 4)
|
||||||
|
self.assertEqual(manager.offload_inflight[new_req], 1)
|
||||||
|
self.assertIn(2, manager.ongoing_offload)
|
||||||
|
|
||||||
def test_finalize_release_defers_while_offload_is_in_flight(self):
|
def test_finalize_release_defers_while_offload_is_in_flight(self):
|
||||||
manager, freed = _make_manager(pool_size=32)
|
manager, freed = _make_manager(pool_size=32)
|
||||||
req = _make_mock_req(
|
req = _make_mock_req(
|
||||||
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=2
|
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=2
|
||||||
)
|
)
|
||||||
manager.offloaded_state[req.rid] = OffloadedState(
|
manager.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=4, inc_len=8, last_hash=None
|
prefill_len=4, inc_len=8, last_hash=None
|
||||||
)
|
)
|
||||||
manager.offload_inflight[req.rid] = 1
|
manager.offload_inflight[req] = 1
|
||||||
|
|
||||||
manager.finalize_release_on_finish(req)
|
manager.finalize_release_on_finish(req)
|
||||||
|
|
||||||
self.assertEqual(freed, [])
|
self.assertEqual(freed, [])
|
||||||
manager.req_to_token_pool.free.assert_not_called()
|
manager.req_to_token_pool.free.assert_not_called()
|
||||||
self.assertIn(req.rid, manager.offloaded_state)
|
self.assertIn(req, manager.offloaded_state)
|
||||||
|
|
||||||
def test_finished_offload_ack_waits_for_other_inflight_writes(self):
|
def test_finished_offload_ack_waits_for_other_inflight_writes(self):
|
||||||
manager, freed = _make_manager(pool_size=32)
|
manager, freed = _make_manager(pool_size=32)
|
||||||
@@ -348,10 +395,10 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=3
|
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=3
|
||||||
)
|
)
|
||||||
req.finished.return_value = True
|
req.finished.return_value = True
|
||||||
manager.offloaded_state[req.rid] = OffloadedState(
|
manager.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=4, inc_len=8, last_hash=None
|
prefill_len=4, inc_len=8, last_hash=None
|
||||||
)
|
)
|
||||||
manager.offload_inflight[req.rid] = 2
|
manager.offload_inflight[req] = 2
|
||||||
manager.ongoing_offload[8] = (
|
manager.ongoing_offload[8] = (
|
||||||
req,
|
req,
|
||||||
torch.arange(4, 8, dtype=torch.int64),
|
torch.arange(4, 8, dtype=torch.int64),
|
||||||
@@ -370,7 +417,7 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(freed, [])
|
self.assertEqual(freed, [])
|
||||||
manager.req_to_token_pool.free.assert_not_called()
|
manager.req_to_token_pool.free.assert_not_called()
|
||||||
self.assertEqual(manager.offload_inflight[req.rid], 1)
|
self.assertEqual(manager.offload_inflight[req], 1)
|
||||||
|
|
||||||
def test_finished_request_releases_all_committed_slots_after_last_offload_ack(
|
def test_finished_request_releases_all_committed_slots_after_last_offload_ack(
|
||||||
self,
|
self,
|
||||||
@@ -380,10 +427,10 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=4
|
req_pool_idx=0, kv_committed_len=20, kv_allocated_len=20, rid=4
|
||||||
)
|
)
|
||||||
req.finished.return_value = True
|
req.finished.return_value = True
|
||||||
manager.offloaded_state[req.rid] = OffloadedState(
|
manager.offloaded_state[req] = OffloadedState(
|
||||||
prefill_len=4, inc_len=8, last_hash=None
|
prefill_len=4, inc_len=8, last_hash=None
|
||||||
)
|
)
|
||||||
manager.offload_inflight[req.rid] = 1
|
manager.offload_inflight[req] = 1
|
||||||
manager.ongoing_offload[9] = (
|
manager.ongoing_offload[9] = (
|
||||||
req,
|
req,
|
||||||
torch.arange(8, 12, dtype=torch.int64),
|
torch.arange(8, 12, dtype=torch.int64),
|
||||||
@@ -404,8 +451,8 @@ class TestReleaseFinishedReq(unittest.TestCase):
|
|||||||
self.assertTrue(torch.equal(freed[0], torch.arange(0, 4, dtype=torch.int64)))
|
self.assertTrue(torch.equal(freed[0], torch.arange(0, 4, dtype=torch.int64)))
|
||||||
self.assertTrue(torch.equal(freed[1], torch.arange(4, 20, dtype=torch.int64)))
|
self.assertTrue(torch.equal(freed[1], torch.arange(4, 20, dtype=torch.int64)))
|
||||||
manager.req_to_token_pool.free.assert_called_once_with(req)
|
manager.req_to_token_pool.free.assert_called_once_with(req)
|
||||||
self.assertNotIn(req.rid, manager.offloaded_state)
|
self.assertNotIn(req, manager.offloaded_state)
|
||||||
self.assertNotIn(req.rid, manager.offload_inflight)
|
self.assertNotIn(req, manager.offload_inflight)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user