|
|
@@ -388,6 +388,8 @@ class TestResumeSessionPreserved(unittest.TestCase):
|
|
|
p.config = ProviderConfig(model="sonnet", timeout=600, extra={})
|
|
|
p.claude_bin = "claude"
|
|
|
p._session_id = "sess-123"
|
|
|
+ p._resume_count = 0
|
|
|
+ p._resume_stall_streak = 0
|
|
|
return p, ccp
|
|
|
|
|
|
def test_first_byte_stall_keeps_session(self):
|
|
|
@@ -439,3 +441,36 @@ class TestSessionRotation(unittest.TestCase):
|
|
|
p._call_resume([{"role": "user", "content": "x"}])
|
|
|
self.assertIsNone(p._session_id, "第3次 resume 后应轮换(清会话)")
|
|
|
self.assertEqual(p._resume_count, 0, "轮换后计数归零")
|
|
|
+
|
|
|
+
|
|
|
+class TestResumeStallGiveup(unittest.TestCase):
|
|
|
+ """连续首字节卡死必须熔断:连卡 K 次后清掉大 session 换新,
|
|
|
+ 否则会像那条 run 一样在同一大 session 上连卡 8 次打转到死。"""
|
|
|
+
|
|
|
+ def test_consecutive_first_byte_stalls_clear_session(self):
|
|
|
+ from lambdagent.providers import claude_code_provider as ccp
|
|
|
+ from lambdagent.providers.base import ProviderConfig, ProviderError
|
|
|
+
|
|
|
+ p = ccp.ClaudeCodeProvider.__new__(ccp.ClaudeCodeProvider)
|
|
|
+ p.config = ProviderConfig(model="sonnet", timeout=600,
|
|
|
+ extra={"claude_resume_stall_giveup": 2})
|
|
|
+ p.claude_bin = "claude"
|
|
|
+ p._session_id = "sess-BIG"
|
|
|
+ p._resume_count = 0
|
|
|
+ p._resume_stall_streak = 0
|
|
|
+
|
|
|
+ def always_stall(*a, **k):
|
|
|
+ raise ccp._StallError("first-byte", 45.0, "", "")
|
|
|
+
|
|
|
+ with patch.object(ccp, "_run_with_idle_timeout", side_effect=always_stall), \
|
|
|
+ patch.object(ccp.time, "sleep", lambda *_: None):
|
|
|
+ # 第 1 次 _call_resume 全卡:保留会话(兜瞬时抖动)
|
|
|
+ with self.assertRaises(ProviderError):
|
|
|
+ p._call_resume([{"role": "user", "content": "x"}])
|
|
|
+ self.assertEqual(p._session_id, "sess-BIG", "第1次连卡应保留会话")
|
|
|
+ self.assertEqual(p._resume_stall_streak, 1)
|
|
|
+ # 第 2 次 _call_resume 又全卡:达阈值 → 熔断清会话
|
|
|
+ with self.assertRaises(ProviderError):
|
|
|
+ p._call_resume([{"role": "user", "content": "x"}])
|
|
|
+ self.assertIsNone(p._session_id, "连卡2次后应清会话换新")
|
|
|
+ self.assertEqual(p._resume_stall_streak, 0, "熔断后计数归零")
|