diff --git a/src/daemon/frontend.c b/src/daemon/frontend.c index e76c920b6..fa81cc06d 100644 --- a/src/daemon/frontend.c +++ b/src/daemon/frontend.c @@ -121,9 +121,11 @@ static atomic_uint_fast64_t frontend_test_worker_observation_count = ATOMIC_VAR_ static atomic_uint_fast64_t frontend_test_worker_idle_count = ATOMIC_VAR_INIT(0); static atomic_uint_fast64_t frontend_test_routed_cancellation_count = ATOMIC_VAR_INIT(0); static atomic_uint_fast32_t frontend_test_replay_budget_ms = ATOMIC_VAR_INIT(0); +static atomic_uint_fast32_t frontend_test_monitor_idle_sleep = ATOMIC_VAR_INIT(0); void cbm_daemon_frontend_test_observer_reset(bool hold_monitor) { atomic_store_explicit(&frontend_test_monitor_observation_count, 0, memory_order_release); + atomic_store_explicit(&frontend_test_monitor_idle_sleep, 0, memory_order_release); atomic_store_explicit(&frontend_test_worker_observation_count, 0, memory_order_release); atomic_store_explicit(&frontend_test_worker_idle_count, 0, memory_order_release); atomic_store_explicit(&frontend_test_monitor_is_waiting, false, memory_order_release); @@ -157,6 +159,10 @@ uint64_t cbm_daemon_frontend_test_routed_cancellations(void) { uint32_t cbm_daemon_frontend_test_replay_timeout_ms(void) { return (uint32_t)atomic_load_explicit(&frontend_test_replay_budget_ms, memory_order_acquire); } + +uint32_t cbm_daemon_frontend_test_monitor_idle_sleep_ms(void) { + return (uint32_t)atomic_load_explicit(&frontend_test_monitor_idle_sleep, memory_order_acquire); +} #endif static cbm_version_cohort_maintenance_presence_t frontend_observe_maintenance( @@ -187,7 +193,11 @@ static void *frontend_maintenance_monitor_worker(void *opaque) { cbm_version_cohort_maintenance_presence_t presence = frontend_observe_maintenance(monitor->manager, true); if (presence == CBM_VERSION_COHORT_MAINTENANCE_ABSENT) { - cbm_usleep(FRONTEND_MAINTENANCE_IDLE_POLL_MS * 1000U); + uint32_t idle_ms = FRONTEND_MAINTENANCE_IDLE_POLL_MS; +#if defined(CBM_ENABLE_TEST_SEAMS) && CBM_ENABLE_TEST_SEAMS + atomic_store_explicit(&frontend_test_monitor_idle_sleep, idle_ms, memory_order_release); +#endif + cbm_usleep(idle_ms * 1000U); continue; } diff --git a/src/daemon/frontend.h b/src/daemon/frontend.h index 8ee33d276..4629644bb 100644 --- a/src/daemon/frontend.h +++ b/src/daemon/frontend.h @@ -83,6 +83,10 @@ uint64_t cbm_daemon_frontend_test_routed_cancellations(void); * settings requests. Recovery tests use distinct connect and startup budgets * and compare. */ uint32_t cbm_daemon_frontend_test_replay_timeout_ms(void); +/* Milliseconds the maintenance monitor last slept in its no-maintenance + * (ABSENT) branch, recorded from the exact value passed to the sleep; zero + * until the first idle cycle after reset. Pins the idle probe cadence. */ +uint32_t cbm_daemon_frontend_test_monitor_idle_sleep_ms(void); #endif /* Takes ownership of client and borrows cohort_manager for the complete call. diff --git a/tests/test_daemon_frontend.c b/tests/test_daemon_frontend.c index aa49bf4c7..7e5c540ba 100644 --- a/tests/test_daemon_frontend.c +++ b/tests/test_daemon_frontend.c @@ -1641,6 +1641,32 @@ TEST(daemon_frontend_idle_uses_one_maintenance_observer) { ASSERT_TRUE(fixture_closed); PASS(); } + +/* An idle monitor must not repeatedly perform the expensive presence probe + * while no maintenance request exists (#2093). The monitor records the exact + * value it sleeps on in its no-maintenance branch; once recorded that value is + * stable, so the verdict depends on neither elapsed time nor scheduler cadence. + * The deadline below is only a hang guard. */ +TEST(daemon_frontend_idle_maintenance_probe_is_bounded) { + frontend_maintenance_fixture_t fixture; + ASSERT_TRUE(frontend_maintenance_fixture_start(&fixture, "idle-cadence")); + + cbm_daemon_frontend_test_observer_reset(false); + cbm_daemon_maintenance_monitor_t *monitor = cbm_daemon_maintenance_monitor_start( + fixture.manager, NULL, NULL, EXIT_SUCCESS, "idle cadence test"); + ASSERT_TRUE(monitor != NULL); + uint64_t deadline = cbm_now_ms() + 10000U; + while (cbm_now_ms() < deadline && cbm_daemon_frontend_test_monitor_idle_sleep_ms() == 0) { + cbm_usleep(1000); + } + uint32_t idle_sleep_ms = cbm_daemon_frontend_test_monitor_idle_sleep_ms(); + ASSERT_TRUE(cbm_daemon_maintenance_monitor_stop(&monitor)); + frontend_maintenance_fixture_finish(&fixture); + + ASSERT_TRUE(idle_sleep_ms != 0); + ASSERT_GTE(idle_sleep_ms, 250); + PASS(); +} #endif #ifndef _WIN32 @@ -1982,6 +2008,7 @@ SUITE(daemon_frontend) { RUN_TEST(daemon_frontend_rejects_non_notification_cancellation_shapes); #if defined(CBM_ENABLE_TEST_SEAMS) && CBM_ENABLE_TEST_SEAMS RUN_TEST(daemon_frontend_idle_uses_one_maintenance_observer); + RUN_TEST(daemon_frontend_idle_maintenance_probe_is_bounded); #endif #ifndef _WIN32 RUN_TEST(daemon_frontend_maintenance_exits_while_stdio_reader_is_blocked);