threadmill 0.7.1__tar.gz → 0.7.3__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.7.1 → threadmill-0.7.3}/PKG-INFO +2 -1
- {threadmill-0.7.1 → threadmill-0.7.3}/pyproject.toml +1 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/_version.py +3 -3
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/lua/acquire.lua +9 -4
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/redis.py +4 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/executor.py +17 -5
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/management/commands/threadmill.py +3 -3
- {threadmill-0.7.1 → threadmill-0.7.3}/LICENSE +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/README.md +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/__init__.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/apps.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/__init__.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/base.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/lua/acknowledge.lua +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/lua/mover.lua +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/backends/lua/reaper.lua +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/exceptions.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/inspector/__init__.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/inspector/app.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/inspector/inspector.scss +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/inspector/screens.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/inspector/telemetry.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/management/__init__.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/management/commands/__init__.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/retry.py +0 -0
- {threadmill-0.7.1 → threadmill-0.7.3}/threadmill/signals.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: threadmill
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
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>
|
|
@@ -18,6 +18,7 @@ Classifier: Operating System :: POSIX
|
|
|
18
18
|
Classifier: Programming Language :: Python
|
|
19
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
22
|
Classifier: Topic :: Communications :: Email
|
|
22
23
|
Classifier: Topic :: Software Development
|
|
23
24
|
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
@@ -24,6 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Programming Language :: Python",
|
|
25
25
|
"Programming Language :: Python :: 3 :: Only",
|
|
26
26
|
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Programming Language :: Python :: 3.15",
|
|
27
28
|
"Topic :: Communications :: Email",
|
|
28
29
|
"Topic :: Software Development",
|
|
29
30
|
"Topic :: Text Processing :: Markup :: Markdown",
|
|
@@ -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.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g0eee0b8b7'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
-- Atomically pop the lowest-scored task from any of the given priority queues,
|
|
2
2
|
-- update its JSON data with worker info, and move it directly to the running
|
|
3
|
-
-- set.
|
|
3
|
+
-- set. Scans the queues round-robin from ARGV[7] and returns the first available
|
|
4
|
+
-- task, so a backlogged queue cannot starve its neighbours.
|
|
4
5
|
--
|
|
5
6
|
-- KEYS[1..N] -- interleaved running keys and queue keys, one pair per queue:
|
|
6
7
|
-- KEYS[1] = running set, KEYS[2] = queue set, KEYS[3] = running,
|
|
@@ -11,12 +12,16 @@
|
|
|
11
12
|
-- ARGV[4] -- number of queue pairs (N/2)
|
|
12
13
|
-- ARGV[5] -- worker name
|
|
13
14
|
-- ARGV[6] -- lease TTL in milliseconds
|
|
15
|
+
-- ARGV[7] -- start_index; 0-based index of the queue pair to scan first, so
|
|
16
|
+
-- start_index 0 is the pair at KEYS[1] and KEYS[2]
|
|
14
17
|
-- Returns: updated serialized data on success, nil if all queues are empty.
|
|
15
18
|
|
|
16
19
|
local num_queues = tonumber(ARGV[4])
|
|
17
20
|
local lease_ttl_ms = tonumber(ARGV[6])
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
local start_index = tonumber(ARGV[7])
|
|
22
|
+
for offset = 0, num_queues - 1 do
|
|
23
|
+
local queue_index = (start_index + offset) % num_queues + 1
|
|
24
|
+
local result = redis.call('ZPOPMIN', KEYS[queue_index * 2])
|
|
20
25
|
if #result > 0 then
|
|
21
26
|
local task_id = result[1]
|
|
22
27
|
local data = redis.call('HGET', ARGV[3] .. task_id, 'data')
|
|
@@ -34,7 +39,7 @@ for i = 1, num_queues do
|
|
|
34
39
|
table.insert(parsed.worker_ids, ARGV[5])
|
|
35
40
|
local updated_data = cjson.encode(parsed)
|
|
36
41
|
local deadline = tonumber(ARGV[1]) + lease_ttl_ms
|
|
37
|
-
redis.call('ZADD', KEYS[
|
|
42
|
+
redis.call('ZADD', KEYS[queue_index * 2 - 1], deadline, task_id)
|
|
38
43
|
redis.call('HSET', ARGV[3] .. task_id, 'data', updated_data)
|
|
39
44
|
return updated_data
|
|
40
45
|
end
|
|
@@ -5,6 +5,7 @@ import dataclasses
|
|
|
5
5
|
import datetime
|
|
6
6
|
import logging
|
|
7
7
|
import queue
|
|
8
|
+
import random
|
|
8
9
|
import time
|
|
9
10
|
import uuid
|
|
10
11
|
from collections.abc import Generator, Sequence
|
|
@@ -162,6 +163,7 @@ class RedisTaskBackend(ThreadmillTaskBackend):
|
|
|
162
163
|
"poll_max_interval", datetime.timedelta(seconds=1)
|
|
163
164
|
)
|
|
164
165
|
self._miss_count = 0
|
|
166
|
+
self._rotation_offset = random.randrange(len(self.queues)) # noqa: S311
|
|
165
167
|
self._acquire_script = self.client.register_script(self.ACQUIRE_SCRIPT)
|
|
166
168
|
self._acknowledge_script = self.client.register_script(self.ACKNOWLEDGE_SCRIPT)
|
|
167
169
|
|
|
@@ -275,9 +277,11 @@ class RedisTaskBackend(ThreadmillTaskBackend):
|
|
|
275
277
|
str(len(queue_names)),
|
|
276
278
|
worker,
|
|
277
279
|
str(int(self.lease_ttl.total_seconds() * 1000)),
|
|
280
|
+
str(self._rotation_offset),
|
|
278
281
|
],
|
|
279
282
|
):
|
|
280
283
|
self._miss_count = 0
|
|
284
|
+
self._rotation_offset = (self._rotation_offset + 1) % len(queue_names)
|
|
281
285
|
return self.deserialize_task_result(data)
|
|
282
286
|
|
|
283
287
|
try:
|
|
@@ -8,6 +8,7 @@ import logging
|
|
|
8
8
|
import multiprocessing
|
|
9
9
|
import random
|
|
10
10
|
import socket
|
|
11
|
+
import sys
|
|
11
12
|
import threading
|
|
12
13
|
import time
|
|
13
14
|
import typing
|
|
@@ -81,10 +82,21 @@ class JsonFormatter(logging.Formatter):
|
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
logger = multiprocessing.get_logger()
|
|
84
|
-
handler = logging.StreamHandler()
|
|
85
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
85
86
|
handler.setFormatter(JsonFormatter())
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def configure_logging(formatter: logging.Formatter) -> None:
|
|
90
|
+
"""Route every log record of this process through the threadmill handler."""
|
|
91
|
+
handler.setFormatter(formatter)
|
|
92
|
+
for existing_logger in logging.root.manager.loggerDict.values():
|
|
93
|
+
if isinstance(existing_logger, logging.Logger):
|
|
94
|
+
existing_logger.handlers.clear()
|
|
95
|
+
existing_logger.propagate = True
|
|
96
|
+
root_logger = logging.getLogger()
|
|
97
|
+
root_logger.handlers.clear()
|
|
98
|
+
root_logger.addHandler(handler)
|
|
99
|
+
root_logger.setLevel(logging.INFO)
|
|
88
100
|
|
|
89
101
|
|
|
90
102
|
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
@@ -138,7 +150,7 @@ class TaskExecutor:
|
|
|
138
150
|
|
|
139
151
|
def run(self) -> None:
|
|
140
152
|
"""Start consuming tasks until shutdown is requested."""
|
|
141
|
-
|
|
153
|
+
configure_logging(self.log_formatter)
|
|
142
154
|
self.worker_processes = [
|
|
143
155
|
self.create_worker_process() for _ in range(self.process_count)
|
|
144
156
|
]
|
|
@@ -213,8 +225,8 @@ class WorkerProcess(multiprocessing.Process):
|
|
|
213
225
|
|
|
214
226
|
def run(self) -> None:
|
|
215
227
|
"""Start consumer execution inside this process."""
|
|
216
|
-
handler.setFormatter(self.log_formatter)
|
|
217
228
|
django.setup()
|
|
229
|
+
configure_logging(self.log_formatter)
|
|
218
230
|
logger.info("Starting worker process %s", self.name)
|
|
219
231
|
self.lock = threading.Lock()
|
|
220
232
|
self.expired = threading.Event()
|
|
@@ -38,7 +38,7 @@ class WorkerCommand(DjangoBaseCommand):
|
|
|
38
38
|
"--queues",
|
|
39
39
|
nargs="+",
|
|
40
40
|
default=[DEFAULT_TASK_QUEUE_NAME],
|
|
41
|
-
help="Queue names to listen to and process tasks from.",
|
|
41
|
+
help="Queue names to listen to and process tasks from (round-robin).",
|
|
42
42
|
)
|
|
43
43
|
parser.add_argument(
|
|
44
44
|
"-w",
|
|
@@ -88,8 +88,8 @@ class WorkerCommand(DjangoBaseCommand):
|
|
|
88
88
|
parser.add_argument(
|
|
89
89
|
"--log-format",
|
|
90
90
|
help=(
|
|
91
|
-
"Logging format string for
|
|
92
|
-
" '%%(levelname)s %%(message)s'. Defaults to JSON."
|
|
91
|
+
"Logging format string for all log records of the worker process,"
|
|
92
|
+
" e.g. '%%(levelname)s %%(message)s'. Defaults to JSON."
|
|
93
93
|
),
|
|
94
94
|
)
|
|
95
95
|
|
|
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
|