|
20 | 20 | import unittest |
21 | 21 | import threading |
22 | 22 | import json |
| 23 | +import sys |
23 | 24 | import time |
24 | 25 | import pytest |
25 | 26 | from unittest.mock import patch |
@@ -222,6 +223,32 @@ def record_thread(*args, **kwargs): |
222 | 223 | self.assertIn("daemon", captured) |
223 | 224 | self.assertTrue(captured["daemon"]) |
224 | 225 |
|
| 226 | + """Expected behavior: if onstarted_leading raises, the lease it protects is no |
| 227 | + longer backed by any running work, so the candidate must stop renewing it and |
| 228 | + run onstopped_leading. The lock below never refuses a renewal, so the renew |
| 229 | + loop can only end because the callback failed.""" |
| 230 | + def test_stops_leading_when_onstarted_leading_raises(self): |
| 231 | + stopped = threading.Event() |
| 232 | + |
| 233 | + mock_lock = MockResourceLock("mock", "mock_namespace", "mock", thread_lock, |
| 234 | + lambda: None, lambda: None, lambda: None, None) |
| 235 | + mock_lock.renew_count_max = sys.maxsize |
| 236 | + |
| 237 | + def on_started_leading(): |
| 238 | + raise RuntimeError("onstarted_leading failed") |
| 239 | + |
| 240 | + config = electionconfig.Config(lock=mock_lock, lease_duration=2, |
| 241 | + renew_deadline=1.5, retry_period=1.1, |
| 242 | + onstarted_leading=on_started_leading, |
| 243 | + onstopped_leading=stopped.set) |
| 244 | + |
| 245 | + # Run in a daemon thread so a regression times out instead of hanging. |
| 246 | + threading.Thread(target=leaderelection.LeaderElection(config).run, |
| 247 | + daemon=True).start() |
| 248 | + |
| 249 | + self.assertTrue(stopped.wait(10), |
| 250 | + "onstopped_leading was not called after onstarted_leading raised") |
| 251 | + |
225 | 252 | def assert_history(self, history, expected): |
226 | 253 | self.assertIsNotNone(expected) |
227 | 254 | self.assertIsNotNone(history) |
|
0 commit comments