re-apply runtime overrides to the config without re-broadcasting them (#23739)

/api/config/set and camera deletion re-parse yaml into a fresh FrigateConfig and swap it in, then re-layered the persisted runtime toggle overrides so a camera the user turned off wouldn't come back on. That re-layer ran apply_runtime_state, which replays each override through the command handlers, so every save re-published a ZMQ config update, a retained MQTT state message, and a runtime-state disk write for every camera with a stored toggle. All of it was redundant: the worker processes were never swapped and still hold the live toggle values, so only the in-process config object the API and dispatcher read was out of date. The extra traffic churned the retained MQTT topics, amplified disk writes, and co-drained enabled updates with other topics on the config socket.

Add Dispatcher.reapply_runtime_state_to_config, which corrects only the swapped-in config object, mirroring the field mutations and gates of the _on_*_command handlers with no ZMQ, MQTT, or disk writes. swap_runtime_config now calls it instead of apply_runtime_state; apply_runtime_state is unchanged and still used at startup, where the workers genuinely must be told.
This commit is contained in:
Josh Hawkins
2026-07-16 13:30:41 -05:00
committed by GitHub
parent f1028d0c36
commit 48aaafba3c
6 changed files with 162 additions and 9 deletions
@@ -143,11 +143,10 @@ class TestConfigSetWildcardPropagation(BaseTestHttp):
# front_door was turned off via the UI: the override is on disk, and
# yaml still says enabled: true. Stand in for the real replay, which
# reads dispatcher.config - the object the endpoint just swapped in.
def fake_apply():
def fake_reapply():
dispatcher.config.cameras["front_door"].enabled = False
return {"front_door": {"enabled": False}}
dispatcher.apply_runtime_state.side_effect = fake_apply
dispatcher.reapply_runtime_state_to_config.side_effect = fake_reapply
try:
app, _ = self._create_app_with_dispatcher(dispatcher)
@@ -168,7 +167,7 @@ class TestConfigSetWildcardPropagation(BaseTestHttp):
# the swap must be repaired: the new config object the API and
# dispatcher now share has to still show front_door as off
dispatcher.apply_runtime_state.assert_called_once_with()
dispatcher.reapply_runtime_state_to_config.assert_called_once_with()
self.assertFalse(app.frigate_config.cameras["front_door"].enabled)
self.assertIs(dispatcher.config, app.frigate_config)
@@ -178,7 +177,7 @@ class TestConfigSetWildcardPropagation(BaseTestHttp):
call_names = [name for name, _, _ in dispatcher.mock_calls]
self.assertLess(
call_names.index("clear_runtime_state_for_yaml_keys"),
call_names.index("apply_runtime_state"),
call_names.index("reapply_runtime_state_to_config"),
)
finally:
os.unlink(config_path)
@@ -205,7 +204,7 @@ class TestConfigSetWildcardPropagation(BaseTestHttp):
)
self.assertEqual(resp.status_code, 200)
dispatcher.apply_runtime_state.assert_not_called()
dispatcher.reapply_runtime_state_to_config.assert_not_called()
finally:
os.unlink(config_path)