Skip to content

Commit 34a3438

Browse files
committed
findings: deterministic repro for Defect 3 (HIT_PENDING no-deadline)
Adds repro_defect3.py driving the real TieringOffloadingManager.lookup() against a stuck-HIT_PENDING primary tier; stock hangs, defect3 deadline downgrades to MISS. Verified live on main@70009fb9. Signed-off-by: nilig <nili.ifergan@gmail.com>
1 parent 8cece8a commit 34a3438

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

test/p2p-findings/p2p-lookup-hangs.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ attempt lost requests to this hang. A production version should also clear
116116
the deadline map on request finish, and the hot re-poll loop deserves an
117117
upstream backoff independent of the deadline.
118118

119+
Deterministic repro (`repro_defect3.py`, no GPU / no HTTP / no second pod):
120+
drives the real `TieringOffloadingManager.lookup()` against a primary tier
121+
stub whose block is permanently write-in-flight (returns `HIT_PENDING`), via
122+
`object.__new__` + the three attributes the `HIT_PENDING` path touches
123+
(`_req_state`, `primary_tier`, `_maybe_process_finished_jobs`). Verified on
124+
`nightly-0ba2aa35a` (== `main`@70009fb9 for this path): stock returns
125+
`HIT_PENDING` on all 1,000 rapid calls and still after 8.5s (the request would
126+
hang to the client timeout); with the `defect3` deadline inserted it downgrades
127+
to `MISS` after 8s and recomputes. Confirms the bug is live in current `main`
128+
and tier-agnostic (the `HIT_PENDING` wait is in the shared tiering manager, not
129+
the P2P session).
130+
119131
## Fix for defects 1 and 2 (validated): `defect12_fix_lookup-deadline-sticky-miss.diff`, one file (`session/client.py`), ~30 lines
120132

121133
1. Consumer-side lookup deadline (8s = server's 5s straggler deadline plus

test/p2p-findings/repro_defect3.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Deterministic repro of lookup-hangs Defect 3: unbounded HIT_PENDING wait.
2+
3+
Drives the REAL TieringOffloadingManager.lookup() against a primary tier whose
4+
block is permanently write-in-flight (returns HIT_PENDING). On stock main the
5+
method returns HIT_PENDING on every call forever -> the scheduler re-polls in a
6+
hot loop and the request defers until the HTTP client times out. With the
7+
defect3 pending-wait deadline, it downgrades to MISS past 8s -> recompute.
8+
9+
No GPU, no HTTP, no second pod. Bypasses __init__ and sets only the three
10+
attributes the HIT_PENDING path of lookup() touches.
11+
"""
12+
import time
13+
14+
from vllm.v1.kv_offload.base import LookupResult, ReqContext
15+
from vllm.v1.kv_offload.tiering.manager import TieringOffloadingManager
16+
17+
18+
class StuckPrimary:
19+
"""Primary tier whose block never finishes its write (HIT_PENDING forever)."""
20+
21+
def lookup(self, key, req_context):
22+
return LookupResult.HIT_PENDING
23+
24+
25+
def make_mgr():
26+
m = object.__new__(TieringOffloadingManager) # skip the real constructor
27+
m._req_state = {}
28+
m.primary_tier = StuckPrimary()
29+
m.secondary_tiers = []
30+
m._maybe_process_finished_jobs = lambda: None # shadow the method
31+
return m
32+
33+
34+
def main():
35+
m = make_mgr()
36+
ctx = ReqContext(req_id="req-defect3")
37+
key = b"\xde\xad\xbe\xef" * 8
38+
39+
results = {m.lookup(key, ctx).name for _ in range(1000)}
40+
print(f"1000 rapid calls -> {results}")
41+
42+
print("waiting 8.5s (past the 8s deadline the fix enforces)...")
43+
time.sleep(8.5)
44+
after = m.lookup(key, ctx).name
45+
print(f"after 8.5s -> {after}")
46+
47+
if after == "HIT_PENDING":
48+
print("RESULT: DEFECT 3 REPRODUCED - block stuck HIT_PENDING with no "
49+
"downgrade; the request would hang until the client timeout.")
50+
elif after == "MISS":
51+
print("RESULT: FIXED - downgraded to MISS past the deadline; the "
52+
"request recomputes instead of hanging.")
53+
else:
54+
print(f"RESULT: unexpected terminal state {after}")
55+
56+
57+
if __name__ == "__main__":
58+
main()

0 commit comments

Comments
 (0)