feat: pace startup housekeeper runs

This commit is contained in:
Marcel Peterkau
2026-06-21 21:42:38 +02:00
parent fc042f6711
commit e6200f4a02
5 changed files with 56 additions and 4 deletions
+1
View File
@@ -29,6 +29,7 @@
"Splash-Screen auf das eingebettete CCMA-Hintergrundmotiv umgestellt und redundante Titeltexte entfernt.", "Splash-Screen auf das eingebettete CCMA-Hintergrundmotiv umgestellt und redundante Titeltexte entfernt.",
"Konfigurierbare Mindestanzeigezeit des Splash-Screens mit fünf Sekunden Standardwert ergänzt.", "Konfigurierbare Mindestanzeigezeit des Splash-Screens mit fünf Sekunden Standardwert ergänzt.",
"Theme-unabhängigen Splash-Fortschrittsbalken in Bild-Blau mit Silberrahmen und zeitbasiertem Fortschritt eingeführt.", "Theme-unabhängigen Splash-Fortschrittsbalken in Bild-Blau mit Silberrahmen und zeitbasiertem Fortschritt eingeführt.",
"Splash-Start verteilt seine Mindestanzeigezeit als optionales Mitglieder-Delay auf den Hausmeisterlauf, um insbesondere Netzwerk-Dateisysteme zu entlasten.",
"Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.", "Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.",
"Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.", "Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.",
"Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.", "Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.",
+20 -2
View File
@@ -2,7 +2,9 @@ from __future__ import annotations
import copy import copy
import json import json
import math
import os import os
import time
from contextlib import contextmanager from contextlib import contextmanager
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import date, datetime from datetime import date, datetime
@@ -54,8 +56,14 @@ class Housekeeper:
self.state_path = repository.root / "housekeeper.json" self.state_path = repository.root / "housekeeper.json"
self.lock_path = repository.root / ".housekeeper.lock" self.lock_path = repository.root / ".housekeeper.lock"
def run(self, today: date | None = None) -> list[HousekeeperFinding]: def run(
self,
today: date | None = None,
*,
member_delay: float = 0.0,
) -> list[HousekeeperFinding]:
current_date = today or date.today() current_date = today or date.today()
delay = _non_negative_delay(member_delay)
with _exclusive_lock(self.lock_path): with _exclusive_lock(self.lock_path):
original = self._load_state() original = self._load_state()
working = copy.deepcopy(original) working = copy.deepcopy(original)
@@ -69,7 +77,9 @@ class Housekeeper:
rules = load_rules(self.repository.root) rules = load_rules(self.repository.root)
repository_config = self.repository.get_configuration() repository_config = self.repository.get_configuration()
for member_id in sorted(member_ids): for index, member_id in enumerate(sorted(member_ids)):
if index and delay:
time.sleep(delay)
try: try:
member, contributions = self.repository.preflight_member_record(member_id) member, contributions = self.repository.preflight_member_record(member_id)
except RepositoryError as exc: except RepositoryError as exc:
@@ -405,6 +415,14 @@ def _remove_orphaned_member_items(items: dict[str, dict[str, Any]], member_ids:
del items[key] del items[key]
def _non_negative_delay(value: float) -> float:
try:
delay = float(value)
except (TypeError, ValueError):
return 0.0
return max(0.0, delay) if math.isfinite(delay) else 0.0
@contextmanager @contextmanager
def _exclusive_lock(path: Path): def _exclusive_lock(path: Path):
path.parent.mkdir(parents=True, exist_ok=True) path.parent.mkdir(parents=True, exist_ok=True)
+13 -1
View File
@@ -119,10 +119,16 @@ class SplashScreen(tk.Toplevel):
errors = self.repository.validate() errors = self.repository.validate()
self._messages.put(("status", "Baue Suchindex …")) self._messages.put(("status", "Baue Suchindex …"))
self.repository.list_members() self.repository.list_members()
member_count = len(self.repository.list_member_ids())
findings = [] findings = []
if self.run_housekeeper: if self.run_housekeeper:
self._messages.put(("status", "Starte Hausmeister …")) self._messages.put(("status", "Starte Hausmeister …"))
findings = Housekeeper(self.repository, self.housekeeper_settings).run() findings = Housekeeper(self.repository, self.housekeeper_settings).run(
member_delay=_member_delay_for_splash(
self.minimum_display_seconds,
member_count,
)
)
result = StartupResult(self.repository, errors, findings) result = StartupResult(self.repository, errors, findings)
self._messages.put(("result", result)) self._messages.put(("result", result))
except Exception as exc: except Exception as exc:
@@ -265,3 +271,9 @@ def _progress_value(elapsed: float, minimum_seconds: float, startup_finished: bo
if startup_finished: if startup_finished:
return elapsed_ratio * 100.0 return elapsed_ratio * 100.0
return elapsed_ratio * 95.0 return elapsed_ratio * 95.0
def _member_delay_for_splash(minimum_seconds: float, member_count: int) -> float:
if minimum_seconds <= 0 or member_count <= 0:
return 0.0
return minimum_seconds / member_count
+14
View File
@@ -4,11 +4,25 @@ from datetime import date
import pytest import pytest
import ccma.services.housekeeper as housekeeper_module
from ccma.rules.loader import RuleLoadError from ccma.rules.loader import RuleLoadError
from ccma.services.housekeeper import Housekeeper from ccma.services.housekeeper import Housekeeper
from ccma.storage.repository import MemberRepository from ccma.storage.repository import MemberRepository
def test_housekeeper_optionally_waits_between_members(tmp_path, monkeypatch) -> None:
repository = MemberRepository(tmp_path)
repository.initialize()
repository.create_member(first_name="First", last_name="Member")
repository.create_member(first_name="Second", last_name="Member")
delays: list[float] = []
monkeypatch.setattr(housekeeper_module.time, "sleep", delays.append)
Housekeeper(repository).run(today=date(2026, 6, 21), member_delay=0.25)
assert delays == [0.25]
def test_store_rule_overrides_builtin_rule_with_same_filename(tmp_path) -> None: def test_store_rule_overrides_builtin_rule_with_same_filename(tmp_path) -> None:
repository = MemberRepository(tmp_path) repository = MemberRepository(tmp_path)
repository.initialize() repository.initialize()
+8 -1
View File
@@ -32,7 +32,11 @@ def test_splash_position_centers_on_pointer_and_stays_on_screen() -> None:
def test_splash_minimum_time_only_waits_for_remaining_duration() -> None: def test_splash_minimum_time_only_waits_for_remaining_duration() -> None:
from ccma.ui.splash import _progress_value, _remaining_minimum_ms from ccma.ui.splash import (
_member_delay_for_splash,
_progress_value,
_remaining_minimum_ms,
)
assert _remaining_minimum_ms(100.0, 5.0, 102.25) == 2750 assert _remaining_minimum_ms(100.0, 5.0, 102.25) == 2750
assert _remaining_minimum_ms(100.0, 5.0, 106.0) == 0 assert _remaining_minimum_ms(100.0, 5.0, 106.0) == 0
@@ -41,6 +45,9 @@ def test_splash_minimum_time_only_waits_for_remaining_duration() -> None:
assert _progress_value(5.0, 5.0, startup_finished=False) == 95.0 assert _progress_value(5.0, 5.0, startup_finished=False) == 95.0
assert _progress_value(2.5, 5.0, startup_finished=True) == 50.0 assert _progress_value(2.5, 5.0, startup_finished=True) == 50.0
assert _progress_value(5.0, 5.0, startup_finished=True) == 100.0 assert _progress_value(5.0, 5.0, startup_finished=True) == 100.0
assert _member_delay_for_splash(5.0, 10) == 0.5
assert _member_delay_for_splash(5.0, 0) == 0.0
assert _member_delay_for_splash(0.0, 10) == 0.0
def test_event_labels_hide_board_actor_but_keep_automatic_marker() -> None: def test_event_labels_hide_board_actor_but_keep_automatic_marker() -> None: