steady-queue 0.1.6__tar.gz → 0.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {steady_queue-0.1.6/steady_queue.egg-info → steady_queue-0.1.8}/PKG-INFO +2 -3
- {steady_queue-0.1.6 → steady_queue-0.1.8}/README.md +1 -2
- {steady_queue-0.1.6 → steady_queue-0.1.8}/pyproject.toml +3 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/__init__.py +2 -2
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/configuration.py +3 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/maintenance.py +2 -1
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/pidfile.py +5 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/registrable.py +2 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/supervisor.py +2 -2
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/timer.py +2 -2
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/recurring_task.py +1 -1
- {steady_queue-0.1.6 → steady_queue-0.1.8/steady_queue.egg-info}/PKG-INFO +2 -3
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/SOURCES.txt +1 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_configuration.py +10 -0
- steady_queue-0.1.8/tests/test_pidfile.py +42 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/LICENSE +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/setup.cfg +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/admin.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/app_executor.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/apps.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/arguments.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/backend.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/collections.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/concurrency.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/db_router.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/management/__init__.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/management/commands/__init__.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/management/commands/steady_queue.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/migrations/0001_initial.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/migrations/__init__.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/__init__.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/base.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/blocked_execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/claimed_execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/clearable.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/concurrency_controls.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/dispatching.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/executable.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/executor.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/failed_execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/job.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/pause.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/process.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/prunable.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/queue.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/ready_execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/recurring_execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/recurring_task.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/retryable.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/schedulable.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/scheduled_execution.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/semaphore.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/__init__.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/base.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/concurrent.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/dispatcher.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/errors.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/interruptible.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/pidfiled.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/poller.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/pool.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/runnable.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/scheduler.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/signals.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/supervised.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/worker.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/queue_selector.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/recurring_schedule.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/task.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/dependency_links.txt +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/requires.txt +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/top_level.txt +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_arguments.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_backend.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_concurrency_controls.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_db_router.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_executions.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_job.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_process_lifecycle.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_queue_selector.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_recurring_task.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_semaphore.py +0 -0
- {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_task.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steady-queue
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: An asynchronous task library for Django that supports delayed tasks, concurrency controls, recurring tasks, pausing queues, numeric priorities per task, priorities by queue order and bulk enqueueing.
|
|
5
5
|
Author-email: Elias Hernandis <elias@hernandis.me>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -505,8 +505,7 @@ There are several settings that control how Steady Queue works that you can set
|
|
|
505
505
|
requesting immediate termination—defaults to 5 seconds.
|
|
506
506
|
- `supervisor_pidfile`: path to a pidfile that the supervisor will create when
|
|
507
507
|
booting to prevent running more than one supervisor in the same host, or in
|
|
508
|
-
case you want to use it for a health check. It's
|
|
509
|
-
`tmp/pids/steady_queue_supervisor.pid` by default.
|
|
508
|
+
case you want to use it for a health check. It's `None` by default.
|
|
510
509
|
- `preserve_finished_jobs`: whether to keep finished jobs in the
|
|
511
510
|
`steady_queue_jobs` table—defaults to `True`.
|
|
512
511
|
- `clear_finished_jobs_after`: period to keep finished jobs around, in case
|
|
@@ -479,8 +479,7 @@ There are several settings that control how Steady Queue works that you can set
|
|
|
479
479
|
requesting immediate termination—defaults to 5 seconds.
|
|
480
480
|
- `supervisor_pidfile`: path to a pidfile that the supervisor will create when
|
|
481
481
|
booting to prevent running more than one supervisor in the same host, or in
|
|
482
|
-
case you want to use it for a health check. It's
|
|
483
|
-
`tmp/pids/steady_queue_supervisor.pid` by default.
|
|
482
|
+
case you want to use it for a health check. It's `None` by default.
|
|
484
483
|
- `preserve_finished_jobs`: whether to keep finished jobs in the
|
|
485
484
|
`steady_queue_jobs` table—defaults to `True`.
|
|
486
485
|
- `clear_finished_jobs_after`: period to keep finished jobs around, in case
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
VERSION = (0, 1,
|
|
4
|
+
VERSION = (0, 1, 8)
|
|
5
5
|
|
|
6
6
|
__version__ = ".".join(map(str, VERSION))
|
|
7
7
|
|
|
@@ -17,6 +17,6 @@ clear_finished_jobs_after: timedelta = timedelta(days=1)
|
|
|
17
17
|
|
|
18
18
|
default_concurrency_control_period: timedelta = timedelta(minutes=3)
|
|
19
19
|
|
|
20
|
-
supervisor_pidfile: Optional[str] =
|
|
20
|
+
supervisor_pidfile: Optional[str] = None
|
|
21
21
|
|
|
22
22
|
database: str = "default"
|
|
@@ -21,7 +21,8 @@ class Maintenance:
|
|
|
21
21
|
self.maintenance_task.start()
|
|
22
22
|
|
|
23
23
|
def stop_maintenance_task(self):
|
|
24
|
-
self
|
|
24
|
+
if hasattr(self, "maintenance_task"):
|
|
25
|
+
self.maintenance_task.stop()
|
|
25
26
|
|
|
26
27
|
def fail_orphaned_executions(self):
|
|
27
28
|
with AppExecutor.wrap_in_app_executor():
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import os
|
|
2
3
|
|
|
3
4
|
|
|
@@ -9,6 +10,7 @@ class Pidfile:
|
|
|
9
10
|
def setup(self):
|
|
10
11
|
self.check_status()
|
|
11
12
|
self.write_file()
|
|
13
|
+
self.set_at_exit_hook()
|
|
12
14
|
|
|
13
15
|
def delete(self):
|
|
14
16
|
self.delete_file()
|
|
@@ -36,6 +38,9 @@ class Pidfile:
|
|
|
36
38
|
self.check_status()
|
|
37
39
|
self.write_file()
|
|
38
40
|
|
|
41
|
+
def set_at_exit_hook(self):
|
|
42
|
+
atexit.register(lambda: self.delete() if os.getpid() == self.pid else None)
|
|
43
|
+
|
|
39
44
|
def delete_file(self):
|
|
40
45
|
try:
|
|
41
46
|
os.unlink(self.path)
|
|
@@ -63,6 +63,8 @@ class Registrable(Base):
|
|
|
63
63
|
self.heartbeat_task.start()
|
|
64
64
|
|
|
65
65
|
def stop_heartbeat(self):
|
|
66
|
+
if not hasattr(self, "heartbeat_task"):
|
|
67
|
+
return
|
|
66
68
|
logger.debug("stopping heartbeat for %s", self.name)
|
|
67
69
|
self.heartbeat_task.stop()
|
|
68
70
|
logger.debug("stopped heartbeat for %s", self.name)
|
|
@@ -48,7 +48,7 @@ class Supervisor(Maintenance, Signals, Pidfiled, Registrable, Interruptible, Bas
|
|
|
48
48
|
except SystemExit:
|
|
49
49
|
logger.info("supervisor interrupted during boot, shutting down")
|
|
50
50
|
self.restore_default_signal_handlers()
|
|
51
|
-
self.
|
|
51
|
+
self.shutdown()
|
|
52
52
|
return
|
|
53
53
|
self.supervise()
|
|
54
54
|
|
|
@@ -154,7 +154,7 @@ class Supervisor(Maintenance, Signals, Pidfiled, Registrable, Interruptible, Bas
|
|
|
154
154
|
if terminated_fork and (not is_exited or exit_status > 0):
|
|
155
155
|
self.handle_claimed_jobs_by(terminated_fork, wait_status)
|
|
156
156
|
|
|
157
|
-
self.configured_processes.pop(pid)
|
|
157
|
+
self.configured_processes.pop(pid, None)
|
|
158
158
|
|
|
159
159
|
def replace_fork(self, pid: int, exitcode: int) -> None:
|
|
160
160
|
logger.info("replacing fork %s due to exit code %s", pid, exitcode)
|
|
@@ -39,7 +39,7 @@ class TimerTask:
|
|
|
39
39
|
self._stop_event = threading.Event()
|
|
40
40
|
|
|
41
41
|
def start(self):
|
|
42
|
-
self.thread = threading.Thread(target=self.run)
|
|
42
|
+
self.thread = threading.Thread(target=self.run, daemon=True)
|
|
43
43
|
self.thread.start()
|
|
44
44
|
|
|
45
45
|
def stop(self):
|
|
@@ -69,7 +69,7 @@ class TimerTask:
|
|
|
69
69
|
|
|
70
70
|
def perform_task(self):
|
|
71
71
|
# Run the callable in a separate thread to isolate crashes
|
|
72
|
-
work_thread = threading.Thread(target=self.wrapped_callable)
|
|
72
|
+
work_thread = threading.Thread(target=self.wrapped_callable, daemon=True)
|
|
73
73
|
work_thread.start()
|
|
74
74
|
work_thread.join()
|
|
75
75
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steady-queue
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: An asynchronous task library for Django that supports delayed tasks, concurrency controls, recurring tasks, pausing queues, numeric priorities per task, priorities by queue order and bulk enqueueing.
|
|
5
5
|
Author-email: Elias Hernandis <elias@hernandis.me>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -505,8 +505,7 @@ There are several settings that control how Steady Queue works that you can set
|
|
|
505
505
|
requesting immediate termination—defaults to 5 seconds.
|
|
506
506
|
- `supervisor_pidfile`: path to a pidfile that the supervisor will create when
|
|
507
507
|
booting to prevent running more than one supervisor in the same host, or in
|
|
508
|
-
case you want to use it for a health check. It's
|
|
509
|
-
`tmp/pids/steady_queue_supervisor.pid` by default.
|
|
508
|
+
case you want to use it for a health check. It's `None` by default.
|
|
510
509
|
- `preserve_finished_jobs`: whether to keep finished jobs in the
|
|
511
510
|
`steady_queue_jobs` table—defaults to `True`.
|
|
512
511
|
- `clear_finished_jobs_after`: period to keep finished jobs around, in case
|
|
@@ -220,6 +220,16 @@ class ConfigurationValidationTestCase(SimpleTestCase):
|
|
|
220
220
|
errors = config.validate_recurring_tasks()
|
|
221
221
|
self.assertEqual(len(errors), 0)
|
|
222
222
|
|
|
223
|
+
def test_skip_recurring_excludes_schedulers_from_configured_processes(self):
|
|
224
|
+
"""skip_recurring option excludes scheduler processes."""
|
|
225
|
+
options = Configuration.Options(skip_recurring=True)
|
|
226
|
+
config = Configuration(options)
|
|
227
|
+
|
|
228
|
+
process_kinds = [p.kind for p in config.configured_processes]
|
|
229
|
+
self.assertNotIn("scheduler", process_kinds)
|
|
230
|
+
self.assertIn("worker", process_kinds)
|
|
231
|
+
self.assertIn("dispatcher", process_kinds)
|
|
232
|
+
|
|
223
233
|
|
|
224
234
|
class RecurringTaskConfigurationTestCase(SimpleTestCase):
|
|
225
235
|
"""Tests for recurring task configuration."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from tempfile import TemporaryDirectory
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from django.test import SimpleTestCase
|
|
6
|
+
|
|
7
|
+
import steady_queue
|
|
8
|
+
from steady_queue.processes.pidfile import Pidfile
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PidfileDefaultsTestCase(SimpleTestCase):
|
|
12
|
+
def test_supervisor_pidfile_defaults_to_none(self):
|
|
13
|
+
self.assertIsNone(steady_queue.supervisor_pidfile)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PidfileLifecycleTestCase(SimpleTestCase):
|
|
17
|
+
def test_setup_registers_atexit_cleanup_hook(self):
|
|
18
|
+
with TemporaryDirectory() as temp_dir, patch("atexit.register") as register:
|
|
19
|
+
path = os.path.join(temp_dir, "pids", "steady_queue_supervisor.pid")
|
|
20
|
+
|
|
21
|
+
pidfile = Pidfile(path)
|
|
22
|
+
pidfile.setup()
|
|
23
|
+
|
|
24
|
+
register.assert_called_once()
|
|
25
|
+
self.assertTrue(os.path.exists(path))
|
|
26
|
+
with open(path) as f:
|
|
27
|
+
self.assertEqual(str(os.getpid()), f.read().strip())
|
|
28
|
+
|
|
29
|
+
def test_setup_replaces_stale_pidfile_for_dead_process(self):
|
|
30
|
+
with TemporaryDirectory() as temp_dir:
|
|
31
|
+
path = os.path.join(temp_dir, "pids", "steady_queue_supervisor.pid")
|
|
32
|
+
|
|
33
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
34
|
+
with open(path, "w") as f:
|
|
35
|
+
f.write("999999")
|
|
36
|
+
|
|
37
|
+
with patch("atexit.register"):
|
|
38
|
+
pidfile = Pidfile(path)
|
|
39
|
+
pidfile.setup()
|
|
40
|
+
|
|
41
|
+
with open(path) as f:
|
|
42
|
+
self.assertEqual(str(os.getpid()), f.read().strip())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|