fix(reliability): clear boot marker on graceful shutdown (I1)

This commit is contained in:
2026-08-22 16:51:33 -06:00
parent 027646c905
commit ba3e1b1ae0
2 changed files with 56 additions and 0 deletions
@@ -5106,6 +5106,17 @@ def check_boot_marker():
return False return False
def _clear_boot_marker():
"""Best-effort: a clean shutdown must not read as an unexpected reboot."""
marker = globals().get('BOOT_MARKER')
if not marker:
return
try:
os.unlink(marker)
except Exception:
pass
def startup_env_check(attempts=STARTUP_CHECK_ATTEMPTS, def startup_env_check(attempts=STARTUP_CHECK_ATTEMPTS,
delay=STARTUP_CHECK_DELAY): delay=STARTUP_CHECK_DELAY):
"""Run and print the startup contract, allowing boot dependencies time. """Run and print the startup contract, allowing boot dependencies time.
@@ -7143,6 +7154,10 @@ def _request_shutdown(signum=None, frame=None):
LIVE_STOP.set() LIVE_STOP.set()
HEALTH_STOP.set() HEALTH_STOP.set()
_recon_hopper_stop.set() _recon_hopper_stop.set()
try:
_clear_boot_marker()
except Exception:
pass
def _uci_iface_present(name): def _uci_iface_present(name):
+41
View File
@@ -63,6 +63,47 @@ class ReliabilityApiTest(unittest.TestCase):
os.unlink(marker) os.unlink(marker)
server.BOOT_MARKER = old server.BOOT_MARKER = old
def test_clean_cycle_boot_shutdown_next_boot_not_unexpected(self):
import tempfile
import threading
import mk8_events as events_mod
marker = tempfile.mktemp()
old = (server.BOOT_MARKER, server.LIVE_STOP, server.HEALTH_STOP,
server._recon_hopper_stop, events_mod.mark_boot)
stops = (threading.Event(), threading.Event(), threading.Event())
server.BOOT_MARKER = marker
server.LIVE_STOP, server.HEALTH_STOP, \
server._recon_hopper_stop = stops
booted = []
events_mod.mark_boot = \
lambda unexpected=False: booted.append(unexpected)
try:
# Simulate a previous run's marker left behind: boot is unexpected.
open(marker, 'w').write('0')
self.assertTrue(server.check_boot_marker())
self.assertEqual(booted, [True])
# Clean shutdown clears the marker...
self.assertTrue(os.path.exists(marker))
server._request_shutdown()
self.assertFalse(os.path.exists(marker),
'graceful shutdown must clear the boot marker')
for ev in stops:
self.assertTrue(ev.is_set())
# ...so the next boot is clean and re-arms the marker.
self.assertFalse(server.check_boot_marker())
self.assertEqual(booted[-1], False)
self.assertTrue(os.path.exists(marker))
# _clear_boot_marker is best-effort on missing/None markers.
server._clear_boot_marker()
self.assertFalse(os.path.exists(marker))
server.BOOT_MARKER = None
server._clear_boot_marker()
finally:
(server.BOOT_MARKER, server.LIVE_STOP, server.HEALTH_STOP,
server._recon_hopper_stop, events_mod.mark_boot) = old
if os.path.exists(marker):
os.unlink(marker)
def test_profile_routes_registered(self): def test_profile_routes_registered(self):
handler, _ = server.ROUTER.dispatch('GET', handler, _ = server.ROUTER.dispatch('GET',
'/api/reliability/profiles') '/api/reliability/profiles')