From a6894488546a2f7ea3d211504d04b181a230f177 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Thu, 30 Nov 2023 13:40:59 +0200 Subject: [PATCH] fix: use event instead of chained timer for daemon Old solution causes amount of thread to be growing as well as stack is increased during each iteration. Instead of cycle-free implementation, this commit just uses while cycle --- src/ahriman/application/handlers/daemon.py | 12 ++++++----- .../handlers/test_handler_daemon.py | 20 +++++++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/ahriman/application/handlers/daemon.py b/src/ahriman/application/handlers/daemon.py index 33c00bf7..665a8994 100644 --- a/src/ahriman/application/handlers/daemon.py +++ b/src/ahriman/application/handlers/daemon.py @@ -43,8 +43,10 @@ class Daemon(Handler): report(bool): force enable or disable reporting """ from ahriman.application.handlers import Update - Update.run(args, repository_id, configuration, report=report) - timer = threading.Timer(args.interval, Daemon.run, args=[args, repository_id, configuration], - kwargs={"report": report}) - timer.start() - timer.join() + + event = threading.Event() + try: + while not event.wait(args.interval): + Update.run(args, repository_id, configuration, report=report) + except KeyboardInterrupt: + pass # normal exit diff --git a/tests/ahriman/application/handlers/test_handler_daemon.py b/tests/ahriman/application/handlers/test_handler_daemon.py index 4018e3a1..8c470690 100644 --- a/tests/ahriman/application/handlers/test_handler_daemon.py +++ b/tests/ahriman/application/handlers/test_handler_daemon.py @@ -1,6 +1,7 @@ import argparse from pytest_mock import MockerFixture +from unittest.mock import call as MockCall from ahriman.application.handlers import Daemon from ahriman.core.configuration import Configuration @@ -30,11 +31,22 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc """ args = _default_args(args) run_mock = mocker.patch("ahriman.application.handlers.Update.run") - start_mock = mocker.patch("threading.Timer.start") - join_mock = mocker.patch("threading.Timer.join") + wait_mock = mocker.patch("threading.Event.wait", side_effect=[False, True]) _, repository_id = configuration.check_loaded() Daemon.run(args, repository_id, configuration, report=True) run_mock.assert_called_once_with(args, repository_id, configuration, report=True) - start_mock.assert_called_once_with() - join_mock.assert_called_once_with() + wait_mock.assert_has_calls([MockCall(args.interval), MockCall(args.interval)]) + + +def test_run_keyboard_interrupt(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must handle KeyboardInterrupt exception + """ + args = _default_args(args) + mocker.patch("ahriman.application.handlers.Update.run", side_effect=KeyboardInterrupt) + wait_mock = mocker.patch("threading.Event.wait", return_value=False) + + _, repository_id = configuration.check_loaded() + Daemon.run(args, repository_id, configuration, report=True) + wait_mock.assert_called_once_with(args.interval)