threadmill 0.2.0__tar.gz → 0.3.1__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.
- {threadmill-0.2.0 → threadmill-0.3.1}/PKG-INFO +33 -19
- {threadmill-0.2.0 → threadmill-0.3.1}/README.md +28 -17
- {threadmill-0.2.0 → threadmill-0.3.1}/pyproject.toml +5 -1
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/_version.py +3 -3
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/backends/base.py +77 -2
- threadmill-0.3.1/threadmill/backends/lua/acknowledge.lua +34 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/backends/lua/reaper.lua +6 -2
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/backends/redis.py +168 -23
- threadmill-0.3.1/threadmill/inspector/__init__.py +5 -0
- threadmill-0.3.1/threadmill/inspector/app.py +463 -0
- threadmill-0.3.1/threadmill/inspector/inspector.scss +106 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/management/commands/threadmill.py +57 -7
- threadmill-0.2.0/threadmill/backends/lua/acknowledge.lua +0 -24
- {threadmill-0.2.0 → threadmill-0.3.1}/LICENSE +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/__init__.py +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/backends/__init__.py +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/backends/lua/acquire.lua +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/backends/lua/mover.lua +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/exceptions.py +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/executor.py +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/management/__init__.py +0 -0
- {threadmill-0.2.0 → threadmill-0.3.1}/threadmill/management/commands/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: threadmill
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: The most reliable backend for Django's task framework.
|
|
5
5
|
Keywords: Django,tasks,worker
|
|
6
6
|
Author-email: Johannes Maron <johannes@maron.family>
|
|
@@ -24,9 +24,11 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.14
|
|
26
26
|
Classifier: Framework :: Django
|
|
27
|
+
Classifier: Framework :: Django :: 6.0
|
|
27
28
|
Classifier: Framework :: Django :: 6.1
|
|
28
29
|
License-File: LICENSE
|
|
29
|
-
Requires-Dist: django>=6.
|
|
30
|
+
Requires-Dist: django>=6.0
|
|
31
|
+
Requires-Dist: textual>=8.2.7 ; extra == "inspector"
|
|
30
32
|
Requires-Dist: redis>=5.0 ; extra == "redis"
|
|
31
33
|
Project-URL: Changelog, https://github.com/codingjoe/threadmill/releases
|
|
32
34
|
Project-URL: Documentation, https://github.com/codingjoe/threadmill/
|
|
@@ -35,15 +37,14 @@ Project-URL: Homepage, https://github.com/codingjoe/threadmill
|
|
|
35
37
|
Project-URL: Issues, https://github.com/codingjoe/threadmill/issues
|
|
36
38
|
Project-URL: Releasenotes, https://github.com/codingjoe/threadmill/releases/latest
|
|
37
39
|
Project-URL: Source, https://github.com/codingjoe/threadmill
|
|
40
|
+
Provides-Extra: inspector
|
|
38
41
|
Provides-Extra: redis
|
|
39
42
|
|
|
40
|
-
# Threadmill
|
|
41
|
-
|
|
42
43
|
<p align="center">
|
|
43
44
|
<picture>
|
|
44
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/codingjoe/threadmill/raw/main/images/logo-dark.svg">
|
|
45
|
-
<source media="(prefers-color-scheme: light)" srcset="https://github.com/codingjoe/threadmill/raw/main/images/logo-light.svg">
|
|
46
|
-
<img alt="Threadmill:
|
|
45
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/codingjoe/threadmill/raw/main/docs/images/logo-dark.svg">
|
|
46
|
+
<source media="(prefers-color-scheme: light)" srcset="https://github.com/codingjoe/threadmill/raw/main/docs/images/logo-light.svg">
|
|
47
|
+
<img alt="Threadmill: Durable high-performance backend for Django's task framework." src="https://github.com/codingjoe/threadmill/raw/main/docs/images/logo-light.svg">
|
|
47
48
|
</picture>
|
|
48
49
|
<br>
|
|
49
50
|
<a href="https://github.com/codingjoe/threadmill/">Documentation</a> |
|
|
@@ -52,7 +53,9 @@ Provides-Extra: redis
|
|
|
52
53
|
<a href="https://github.com/sponsors/codingjoe">Funding</a> 💚
|
|
53
54
|
</p>
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
# Threadmill [](https://pypi.python.org/pypi/threadmill/) [](https://codecov.io/gh/codingjoe/threadmill) [](https://raw.githubusercontent.com/codingjoe/threadmill/master/LICENSE)
|
|
57
|
+
|
|
58
|
+
**Durable high-performance backend for Django's task framework.**
|
|
56
59
|
|
|
57
60
|
## Design Principles
|
|
58
61
|
|
|
@@ -60,13 +63,6 @@ Provides-Extra: redis
|
|
|
60
63
|
- **Consistency** – We never lose data, even if someone unplugs the power or network.
|
|
61
64
|
- **Utilization** – We keep the CPU saturated with tasks, not with idle time or waiting for locks.
|
|
62
65
|
|
|
63
|
-
> [!WARNING]
|
|
64
|
-
> Threadmill requires a development version of Django and is in a preview stage.
|
|
65
|
-
|
|
66
|
-
[](https://pypi.python.org/pypi/threadmill/)
|
|
67
|
-
[](https://codecov.io/gh/codingjoe/threadmill)
|
|
68
|
-
[](https://raw.githubusercontent.com/codingjoe/threadmill/master/LICENSE)
|
|
69
|
-
|
|
70
66
|
## Sponsors
|
|
71
67
|
|
|
72
68
|
[](https://github.com/sponsors/codingjoe)
|
|
@@ -100,10 +96,16 @@ TASKS = {
|
|
|
100
96
|
}
|
|
101
97
|
```
|
|
102
98
|
|
|
103
|
-
|
|
99
|
+
Optionally, install the inspector dependency if you want the TUI:
|
|
100
|
+
|
|
101
|
+
```console
|
|
102
|
+
uv add threadmill[inspector]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then launch the worker pool:
|
|
104
106
|
|
|
105
107
|
```console
|
|
106
|
-
uv run manage.py threadmill
|
|
108
|
+
uv run manage.py threadmill worker
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
## Usage
|
|
@@ -118,7 +120,7 @@ Depending on your workload, you can tweak the number of processes and threads.
|
|
|
118
120
|
Processes allow for parallel compute (no GIL) while threads are great for low-memory concurrent IO.
|
|
119
121
|
|
|
120
122
|
```console
|
|
121
|
-
uv run manage.py threadmill --processes 4 --threads 2
|
|
123
|
+
uv run manage.py threadmill worker --processes 4 --threads 2
|
|
122
124
|
```
|
|
123
125
|
|
|
124
126
|
#### Health
|
|
@@ -126,7 +128,7 @@ uv run manage.py threadmill --processes 4 --threads 2
|
|
|
126
128
|
If your tasks leak memory, you can recycle (restart) the workers after a certain number of tasks have been processed:
|
|
127
129
|
|
|
128
130
|
```console
|
|
129
|
-
uv run manage.py threadmill --max-tasks 1000 --max-tasks-jitter 100
|
|
131
|
+
uv run manage.py threadmill worker --max-tasks 1000 --max-tasks-jitter 100
|
|
130
132
|
```
|
|
131
133
|
|
|
132
134
|
This will restart the workers after 1000 tasks have been processed, with a random jitter of up to 100 tasks to avoid all workers restarting at the same time.
|
|
@@ -141,6 +143,18 @@ All workers will finish the tasks they acquired and acknowledge them.
|
|
|
141
143
|
You can use `--exit-empty` to exit immediately after all tasks have been processed,
|
|
142
144
|
which might be useful for draining a one-off queue.
|
|
143
145
|
|
|
146
|
+
### Inspector
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
The optional TUI inspector lets you watch queues, tasks, and task details in real-time.
|
|
151
|
+
Install it with the `inspector` extra and launch it from a separate terminal:
|
|
152
|
+
|
|
153
|
+
```console
|
|
154
|
+
uv add threadmill[inspector]
|
|
155
|
+
uv run manage.py threadmill inspector
|
|
156
|
+
```
|
|
157
|
+
|
|
144
158
|
### Redis Backend Options
|
|
145
159
|
|
|
146
160
|
The `RedisTaskBackend` accepts the following options under `OPTIONS` in your
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
# Threadmill
|
|
2
|
-
|
|
3
1
|
<p align="center">
|
|
4
2
|
<picture>
|
|
5
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/codingjoe/threadmill/raw/main/images/logo-dark.svg">
|
|
6
|
-
<source media="(prefers-color-scheme: light)" srcset="https://github.com/codingjoe/threadmill/raw/main/images/logo-light.svg">
|
|
7
|
-
<img alt="Threadmill:
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/codingjoe/threadmill/raw/main/docs/images/logo-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://github.com/codingjoe/threadmill/raw/main/docs/images/logo-light.svg">
|
|
5
|
+
<img alt="Threadmill: Durable high-performance backend for Django's task framework." src="https://github.com/codingjoe/threadmill/raw/main/docs/images/logo-light.svg">
|
|
8
6
|
</picture>
|
|
9
7
|
<br>
|
|
10
8
|
<a href="https://github.com/codingjoe/threadmill/">Documentation</a> |
|
|
@@ -13,7 +11,9 @@
|
|
|
13
11
|
<a href="https://github.com/sponsors/codingjoe">Funding</a> 💚
|
|
14
12
|
</p>
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
# Threadmill [](https://pypi.python.org/pypi/threadmill/) [](https://codecov.io/gh/codingjoe/threadmill) [](https://raw.githubusercontent.com/codingjoe/threadmill/master/LICENSE)
|
|
15
|
+
|
|
16
|
+
**Durable high-performance backend for Django's task framework.**
|
|
17
17
|
|
|
18
18
|
## Design Principles
|
|
19
19
|
|
|
@@ -21,13 +21,6 @@
|
|
|
21
21
|
- **Consistency** – We never lose data, even if someone unplugs the power or network.
|
|
22
22
|
- **Utilization** – We keep the CPU saturated with tasks, not with idle time or waiting for locks.
|
|
23
23
|
|
|
24
|
-
> [!WARNING]
|
|
25
|
-
> Threadmill requires a development version of Django and is in a preview stage.
|
|
26
|
-
|
|
27
|
-
[](https://pypi.python.org/pypi/threadmill/)
|
|
28
|
-
[](https://codecov.io/gh/codingjoe/threadmill)
|
|
29
|
-
[](https://raw.githubusercontent.com/codingjoe/threadmill/master/LICENSE)
|
|
30
|
-
|
|
31
24
|
## Sponsors
|
|
32
25
|
|
|
33
26
|
[](https://github.com/sponsors/codingjoe)
|
|
@@ -61,10 +54,16 @@ TASKS = {
|
|
|
61
54
|
}
|
|
62
55
|
```
|
|
63
56
|
|
|
64
|
-
|
|
57
|
+
Optionally, install the inspector dependency if you want the TUI:
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
uv add threadmill[inspector]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then launch the worker pool:
|
|
65
64
|
|
|
66
65
|
```console
|
|
67
|
-
uv run manage.py threadmill
|
|
66
|
+
uv run manage.py threadmill worker
|
|
68
67
|
```
|
|
69
68
|
|
|
70
69
|
## Usage
|
|
@@ -79,7 +78,7 @@ Depending on your workload, you can tweak the number of processes and threads.
|
|
|
79
78
|
Processes allow for parallel compute (no GIL) while threads are great for low-memory concurrent IO.
|
|
80
79
|
|
|
81
80
|
```console
|
|
82
|
-
uv run manage.py threadmill --processes 4 --threads 2
|
|
81
|
+
uv run manage.py threadmill worker --processes 4 --threads 2
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
#### Health
|
|
@@ -87,7 +86,7 @@ uv run manage.py threadmill --processes 4 --threads 2
|
|
|
87
86
|
If your tasks leak memory, you can recycle (restart) the workers after a certain number of tasks have been processed:
|
|
88
87
|
|
|
89
88
|
```console
|
|
90
|
-
uv run manage.py threadmill --max-tasks 1000 --max-tasks-jitter 100
|
|
89
|
+
uv run manage.py threadmill worker --max-tasks 1000 --max-tasks-jitter 100
|
|
91
90
|
```
|
|
92
91
|
|
|
93
92
|
This will restart the workers after 1000 tasks have been processed, with a random jitter of up to 100 tasks to avoid all workers restarting at the same time.
|
|
@@ -102,6 +101,18 @@ All workers will finish the tasks they acquired and acknowledge them.
|
|
|
102
101
|
You can use `--exit-empty` to exit immediately after all tasks have been processed,
|
|
103
102
|
which might be useful for draining a one-off queue.
|
|
104
103
|
|
|
104
|
+
### Inspector
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
The optional TUI inspector lets you watch queues, tasks, and task details in real-time.
|
|
109
|
+
Install it with the `inspector` extra and launch it from a separate terminal:
|
|
110
|
+
|
|
111
|
+
```console
|
|
112
|
+
uv add threadmill[inspector]
|
|
113
|
+
uv run manage.py threadmill inspector
|
|
114
|
+
```
|
|
115
|
+
|
|
105
116
|
### Redis Backend Options
|
|
106
117
|
|
|
107
118
|
The `RedisTaskBackend` accepts the following options under `OPTIONS` in your
|
|
@@ -30,13 +30,17 @@ classifiers = [
|
|
|
30
30
|
"Programming Language :: Python :: 3.13",
|
|
31
31
|
"Programming Language :: Python :: 3.14",
|
|
32
32
|
"Framework :: Django",
|
|
33
|
+
"Framework :: Django :: 6.0",
|
|
33
34
|
"Framework :: Django :: 6.1",
|
|
34
35
|
]
|
|
35
36
|
requires-python = ">=3.12"
|
|
36
|
-
dependencies = ["django>=6.
|
|
37
|
+
dependencies = ["django>=6.0"]
|
|
37
38
|
|
|
38
39
|
[project.optional-dependencies]
|
|
39
40
|
redis = ["redis>=5.0"]
|
|
41
|
+
inspector = [
|
|
42
|
+
"textual>=8.2.7",
|
|
43
|
+
]
|
|
40
44
|
|
|
41
45
|
[project.urls]
|
|
42
46
|
# https://packaging.python.org/en/latest/specifications/well-known-project-urls/#well-known-labels
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g32b680b8b'
|
|
@@ -7,12 +7,69 @@ import json
|
|
|
7
7
|
import threading
|
|
8
8
|
from abc import ABC
|
|
9
9
|
|
|
10
|
+
import django
|
|
10
11
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
11
12
|
from django.tasks import DEFAULT_TASK_QUEUE_NAME, Task, TaskResult, TaskResultStatus
|
|
12
13
|
from django.tasks.backends.base import BaseTaskBackend
|
|
13
14
|
from django.tasks.base import TaskError
|
|
14
15
|
from django.utils.module_loading import import_string
|
|
15
16
|
|
|
17
|
+
if django.VERSION == (6, 0):
|
|
18
|
+
# https://github.com/django/django/commit/8c8b833d32c02d3ae6f43b04bb1e45968796b402
|
|
19
|
+
@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
|
|
20
|
+
class Task(Task):
|
|
21
|
+
@classmethod
|
|
22
|
+
def _reconstruct(cls, kwargs):
|
|
23
|
+
func_path = kwargs["func"]
|
|
24
|
+
try:
|
|
25
|
+
func = import_string(func_path)
|
|
26
|
+
kwargs["func"] = func.func
|
|
27
|
+
except (ImportError, AttributeError) as e:
|
|
28
|
+
msg = f"Expected {func_path!r} to point to a Task instance."
|
|
29
|
+
raise ValueError(msg) from e
|
|
30
|
+
return cls(**kwargs)
|
|
31
|
+
|
|
32
|
+
def __reduce__(self):
|
|
33
|
+
kwargs = {f.name: getattr(self, f.name) for f in dataclasses.fields(self)}
|
|
34
|
+
kwargs["func"] = self.module_path
|
|
35
|
+
|
|
36
|
+
return (self.__class__._reconstruct, (kwargs,))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
40
|
+
class QueueCounts:
|
|
41
|
+
"""Point-in-time cardinality of each queue segment."""
|
|
42
|
+
|
|
43
|
+
ready: int
|
|
44
|
+
running: int
|
|
45
|
+
deferred: int
|
|
46
|
+
successful: int
|
|
47
|
+
failed: int
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
51
|
+
class QueueRates:
|
|
52
|
+
"""Rolling ingress/egress throughput over a time window (time-series data)."""
|
|
53
|
+
|
|
54
|
+
interval: datetime.timedelta
|
|
55
|
+
ingress: int
|
|
56
|
+
egress: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
60
|
+
class QueueStats:
|
|
61
|
+
"""Telemetry for a single queue: point-in-time counts plus rolling rates."""
|
|
62
|
+
|
|
63
|
+
counts: QueueCounts
|
|
64
|
+
rates: QueueRates
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
68
|
+
class BackendTelemetry:
|
|
69
|
+
"""Snapshot of counts and rates across a backend's queues."""
|
|
70
|
+
|
|
71
|
+
queues: dict[str, QueueStats]
|
|
72
|
+
|
|
16
73
|
|
|
17
74
|
class Broker(threading.Thread):
|
|
18
75
|
"""Backend maintenance thread launched by the task executor."""
|
|
@@ -71,6 +128,7 @@ class TaskResultEncoder(DjangoJSONEncoder):
|
|
|
71
128
|
class ThreadmillTaskBackend(BaseTaskBackend, ABC):
|
|
72
129
|
"""Interface for task queues to be processed by the executor."""
|
|
73
130
|
|
|
131
|
+
task_class = Task # can be removed in the future when Django 6.0 support is dropped
|
|
74
132
|
supports_async_task = True
|
|
75
133
|
supports_get_result = True
|
|
76
134
|
broker_class: type[Broker] | None = None
|
|
@@ -138,8 +196,25 @@ class ThreadmillTaskBackend(BaseTaskBackend, ABC):
|
|
|
138
196
|
self,
|
|
139
197
|
queue_name: str = DEFAULT_TASK_QUEUE_NAME,
|
|
140
198
|
*,
|
|
141
|
-
status: TaskResultStatus
|
|
199
|
+
status: TaskResultStatus,
|
|
142
200
|
count: int = 1,
|
|
143
201
|
) -> collections.abc.Generator[TaskResult, None, None]:
|
|
144
|
-
"""
|
|
202
|
+
"""
|
|
203
|
+
Yield up to ``count`` tasks from a queue in the given status segment.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
queue_name: The name of the queue to peek into.
|
|
207
|
+
status: The status of the tasks to yield.
|
|
208
|
+
count: The maximum number of tasks to yield. If 0, yield all available tasks.
|
|
209
|
+
"""
|
|
210
|
+
raise NotImplementedError
|
|
211
|
+
|
|
212
|
+
def telemetry(
|
|
213
|
+
self, *, interval: datetime.timedelta = datetime.timedelta(seconds=60)
|
|
214
|
+
) -> BackendTelemetry:
|
|
215
|
+
"""Return a snapshot of stats for all configured queues.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
interval: The time window for rolling rates.
|
|
219
|
+
"""
|
|
145
220
|
raise NotImplementedError
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- Finalize a completed task: remove it from the running set, persist the
|
|
2
|
+
-- result with a TTL, delete the task data hash, and add the result to the
|
|
3
|
+
-- per-status results history set. Evicts results whose finish score falls
|
|
4
|
+
-- outside the retention window (result_ttl). The per-status history sets are
|
|
5
|
+
-- the time series the inspector counts over for telemetry, so no separate
|
|
6
|
+
-- egress window or status counters are needed here.
|
|
7
|
+
--
|
|
8
|
+
-- KEYS[1] -- running set (ZSET)
|
|
9
|
+
-- KEYS[2] -- result key (STRING, stores serialized TaskResult)
|
|
10
|
+
-- KEYS[3] -- task data key (HASH, deleted after acknowledge)
|
|
11
|
+
-- KEYS[4] -- successful results history (ZSET, scored by finish time)
|
|
12
|
+
-- KEYS[5] -- failed results history (ZSET, scored by finish time)
|
|
13
|
+
-- ARGV[1] -- task ID
|
|
14
|
+
-- ARGV[2] -- serialized TaskResult JSON
|
|
15
|
+
-- ARGV[3] -- result TTL in seconds
|
|
16
|
+
-- ARGV[4] -- finish timestamp in milliseconds (score for the history set)
|
|
17
|
+
-- ARGV[5] -- status (SUCCESSFUL or FAILED)
|
|
18
|
+
-- Returns: 1 on success, 0 if task was not in the running set
|
|
19
|
+
|
|
20
|
+
local removed = redis.call('ZREM', KEYS[1], ARGV[1])
|
|
21
|
+
if removed == 0 then
|
|
22
|
+
return 0 -- Task already reaped, skip
|
|
23
|
+
end
|
|
24
|
+
redis.call('SET', KEYS[2], ARGV[2], 'EX', ARGV[3])
|
|
25
|
+
redis.call('DEL', KEYS[3])
|
|
26
|
+
local finish = tonumber(ARGV[4])
|
|
27
|
+
local cutoff = finish - tonumber(ARGV[3]) * 1000
|
|
28
|
+
local results_key = KEYS[4]
|
|
29
|
+
if ARGV[5] ~= 'SUCCESSFUL' then
|
|
30
|
+
results_key = KEYS[5]
|
|
31
|
+
end
|
|
32
|
+
redis.call('ZADD', results_key, finish, ARGV[1])
|
|
33
|
+
redis.call('ZREMRANGEBYSCORE', results_key, 0, cutoff)
|
|
34
|
+
return 1
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
-- Fail tasks whose processing lease has expired.
|
|
1
|
+
-- Fail tasks whose processing lease has expired. Reaped tasks are recorded
|
|
2
|
+
-- in the failed results history (the time series the inspector counts over),
|
|
3
|
+
-- then evicted once older than result_ttl.
|
|
2
4
|
--
|
|
3
5
|
-- KEYS[1] -- running set (ZSET)
|
|
4
|
-
-- KEYS[2] -- results history
|
|
6
|
+
-- KEYS[2] -- failed results history (ZSET, scored by finish time)
|
|
5
7
|
-- ARGV[1] -- current time in milliseconds (for score comparison)
|
|
6
8
|
-- ARGV[2] -- task key prefix (e.g. "threadmill:default:task:")
|
|
7
9
|
-- ARGV[3] -- result key prefix (e.g. "threadmill:default:result:")
|
|
@@ -33,4 +35,6 @@ for _, task_id in ipairs(stale) do
|
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
end
|
|
38
|
+
-- Evict failed results older than result_ttl to bound the history.
|
|
39
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[2], 0, tonumber(ARGV[1]) - tonumber(ARGV[5]) * 1000)
|
|
36
40
|
return #stale
|