From 5b83ca83f706a491fe61f993a1923abece3c487b Mon Sep 17 00:00:00 2001 From: olivierdalang Date: Mon, 4 Dec 2023 16:52:44 +0100 Subject: [PATCH 1/6] fix invalid version with recent setuptools (until we improve packaging in pull/20) --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a49eff2..9600b55 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ def get_version(*file_paths): version_file = open(filename).read() version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: - return version_match.group(1) + version_str = version_match.group(1) + return "0.0.0-dev" if version_str == "dev" else version_str raise RuntimeError("Unable to find version string.") From 55ca7d879057fa64c656178886e51583e80fdc91 Mon Sep 17 00:00:00 2001 From: Olivier Dalang Date: Fri, 19 Jan 2024 10:08:11 +0100 Subject: [PATCH 2/6] fix deleting schedules # Conflicts: # django_toosimple_q/admin.py # django_toosimple_q/models.py --- django_toosimple_q/admin.py | 4 +++ django_toosimple_q/models.py | 4 +++ django_toosimple_q/tests/tests_regression.py | 27 ++++++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/django_toosimple_q/admin.py b/django_toosimple_q/admin.py index ec8dca6..435e3bd 100644 --- a/django_toosimple_q/admin.py +++ b/django_toosimple_q/admin.py @@ -188,6 +188,10 @@ def last_due_(self, obj): @admin.display() def next_due_(self, obj): + # for schedule not in the code anymore + if not obj.schedule: + return "invalid" + if len(obj.past_dues) >= 1: next_due = obj.past_dues[0] else: diff --git a/django_toosimple_q/models.py b/django_toosimple_q/models.py index d1c15d7..293a7e2 100644 --- a/django_toosimple_q/models.py +++ b/django_toosimple_q/models.py @@ -204,6 +204,10 @@ def icon(self): @cached_property def past_dues(self): + if self.schedule is None: + # Deal with invalid schedule (e.g. deleted from the code but still in the DB) + return [] + if self.schedule.cron == "manual": # A manual schedule is never due return [] diff --git a/django_toosimple_q/tests/tests_regression.py b/django_toosimple_q/tests/tests_regression.py index cb51aee..a7c2224 100644 --- a/django_toosimple_q/tests/tests_regression.py +++ b/django_toosimple_q/tests/tests_regression.py @@ -1,11 +1,15 @@ import time +from django.core import management + +from django_toosimple_q.decorators import register_task, schedule_task from django_toosimple_q.models import TaskExec +from django_toosimple_q.registry import schedules_registry, tasks_registry -from .base import TooSimpleQBackgroundTestCase +from .base import TooSimpleQBackgroundTestCase, TooSimpleQRegularTestCase -class TestRegression(TooSimpleQBackgroundTestCase): +class TestRegressionBackground(TooSimpleQBackgroundTestCase): def test_regr_schedule_short(self): # Regression test for an issue where a schedule with smaller periods was not always processed @@ -17,3 +21,22 @@ def test_regr_schedule_short(self): # It should do almost 20 tasks self.assertGreaterEqual(TaskExec.objects.all().count(), 18) + + +class TestRegressionRegular(TooSimpleQRegularTestCase): + def test_deleting_schedule(self): + # Regression test for an issue where deleting a schedule in code would crash the admin view + + @schedule_task(cron="0 12 * * *", datetime_kwarg="scheduled_on") + @register_task(name="normal") + def a(scheduled_on): + return f"{scheduled_on:%Y-%m-%d %H:%M}" + + management.call_command("worker", "--until_done") + + schedules_registry.clear() + tasks_registry.clear() + + # the admin view still works even for deleted schedules + response = self.client.get("/admin/toosimpleq/scheduleexec/") + self.assertEqual(response.status_code, 200) From 3e5ab3cf6e6f884553cd23a1f0b4f3ef7463f698 Mon Sep 17 00:00:00 2001 From: Olivier Dalang Date: Wed, 13 Sep 2023 21:36:45 +0200 Subject: [PATCH 3/6] (cleanup) --- django_toosimple_q/management/commands/worker.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/django_toosimple_q/management/commands/worker.py b/django_toosimple_q/management/commands/worker.py index f9bdd33..dd73837 100644 --- a/django_toosimple_q/management/commands/worker.py +++ b/django_toosimple_q/management/commands/worker.py @@ -65,12 +65,9 @@ def add_arguments(self, parser): def handle(self, *args, **options): # Handle interuption signals signal.signal(signal.SIGINT, self.handle_signal) - # signal.signal(signal.SIGTERM, self.handle_signal) - signal.signal(signal.SIGTERM, signal.default_int_handler) - # for simulating an exception in tests - signal.signal(signal.SIGUSR1, self.handle_signal) - # Handle termination (raises KeyboardInterrupt) + signal.signal(signal.SIGTERM, self.handle_signal) # Custom signal to provoke an artifical exception, used for testing only + signal.signal(signal.SIGUSR1, self.handle_signal) # TODO: replace by simple-parsing self.queues = options["queue"] or [] From 2909e6ba98869ebea52491898c2ed1f02238b2dc Mon Sep 17 00:00:00 2001 From: Olivier Dalang Date: Fri, 19 Jan 2024 11:09:32 +0100 Subject: [PATCH 4/6] fix: worker crash when schedule name doesn't match task name --- django_toosimple_q/schedule.py | 5 +---- django_toosimple_q/tests/tests_regression.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/django_toosimple_q/schedule.py b/django_toosimple_q/schedule.py index a246cbd..789b4fc 100644 --- a/django_toosimple_q/schedule.py +++ b/django_toosimple_q/schedule.py @@ -2,7 +2,6 @@ from typing import Dict, List, Optional from .logging import logger -from .registry import tasks_registry from .task import Task @@ -42,9 +41,7 @@ def execute(self, dues: List[Optional[datetime]]): if self.datetime_kwarg: dt_kwarg = {self.datetime_kwarg: due} - tasks_registry[self.name].enqueue( - *self.args, due=due, **dt_kwarg, **self.kwargs - ) + self.task.enqueue(*self.args, due=due, **dt_kwarg, **self.kwargs) def __str__(self): return f"Schedule {self.name}" diff --git a/django_toosimple_q/tests/tests_regression.py b/django_toosimple_q/tests/tests_regression.py index a7c2224..998b440 100644 --- a/django_toosimple_q/tests/tests_regression.py +++ b/django_toosimple_q/tests/tests_regression.py @@ -40,3 +40,13 @@ def a(scheduled_on): # the admin view still works even for deleted schedules response = self.client.get("/admin/toosimpleq/scheduleexec/") self.assertEqual(response.status_code, 200) + + def test_different_schedule_and_task(self): + # Regression test for an issue where schedule with a different name than the task would fail + + @schedule_task(cron="0 12 * * *", name="name_a", run_on_creation=True) + @register_task(name="name_b") + def a(): + return True + + management.call_command("worker", "--until_done") From f6d9670314f3aa6db44bbf22c2123501dba4b3b9 Mon Sep 17 00:00:00 2001 From: Rex Zhang Date: Sat, 3 Feb 2024 20:18:19 +0800 Subject: [PATCH 5/6] Fix high CPU usage in idle --- django_toosimple_q/management/commands/worker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_toosimple_q/management/commands/worker.py b/django_toosimple_q/management/commands/worker.py index dd73837..0e55c00 100644 --- a/django_toosimple_q/management/commands/worker.py +++ b/django_toosimple_q/management/commands/worker.py @@ -3,6 +3,7 @@ import os import signal from traceback import format_exc +from time import sleep from django.core.management.base import BaseCommand, CommandError from django.db import IntegrityError, transaction @@ -241,7 +242,7 @@ def do_loop(self) -> bool: logger.debug(f"Waiting for next tick...") next_run = last_run + datetime.timedelta(seconds=self.tick_duration) while not self.exit_requested and now() < next_run: - pass + sleep(1) return True From c59d368f1495d890c726474aee81f09528d85bf8 Mon Sep 17 00:00:00 2001 From: Olivier Dalang Date: Fri, 9 Feb 2024 15:27:53 +0100 Subject: [PATCH 6/6] (lint) --- django_toosimple_q/management/commands/worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_toosimple_q/management/commands/worker.py b/django_toosimple_q/management/commands/worker.py index 0e55c00..060d166 100644 --- a/django_toosimple_q/management/commands/worker.py +++ b/django_toosimple_q/management/commands/worker.py @@ -2,8 +2,8 @@ import logging import os import signal -from traceback import format_exc from time import sleep +from traceback import format_exc from django.core.management.base import BaseCommand, CommandError from django.db import IntegrityError, transaction