tasktiger 0.26.0__tar.gz → 0.26.2__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.
- {tasktiger-0.26.0/tasktiger.egg-info → tasktiger-0.26.2}/PKG-INFO +1 -1
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/__init__.py +1 -1
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/task.py +13 -7
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/worker.py +6 -4
- {tasktiger-0.26.0 → tasktiger-0.26.2/tasktiger.egg-info}/PKG-INFO +1 -1
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/tasks.py +10 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_base.py +29 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_workers.py +14 -7
- {tasktiger-0.26.0 → tasktiger-0.26.2}/LICENSE +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/MANIFEST.in +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/README.rst +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/pyproject.toml +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/setup.cfg +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/setup.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/_internal.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/exceptions.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/executor.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/flask_script.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/logging.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/lua/move_task.lua +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/lua/semaphore.lua +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/migrations.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/redis_scripts.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/redis_semaphore.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/retry.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/rollbar.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/runner.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/schedule.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/stats.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/tasktiger.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/test_helpers.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/timeouts.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/types.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger/utils.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger.egg-info/SOURCES.txt +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger.egg-info/dependency_links.txt +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger.egg-info/entry_points.txt +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger.egg-info/requires.txt +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tasktiger.egg-info/top_level.txt +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/__init__.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/config.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/conftest.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/tasks_periodic.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_context_manager.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_lazy_init.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_logging.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_migrations.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_periodic.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_queue_size.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_redis_scripts.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_semaphore.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_stats.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/test_task.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tests/utils.py +0 -0
- {tasktiger-0.26.0 → tasktiger-0.26.2}/tox.ini +0 -0
|
@@ -513,15 +513,19 @@ class Task:
|
|
|
513
513
|
tiger: TaskTiger instance.
|
|
514
514
|
queue: Name of the queue.
|
|
515
515
|
state: State of the task (QUEUED, ACTIVE, SCHEDULED, ERROR).
|
|
516
|
+
skip: Number of latest tasks to pass over.
|
|
516
517
|
limit: Maximum number of tasks to return.
|
|
517
518
|
load_executions: Maximum number of executions to load for each task
|
|
518
519
|
(starting from the latest).
|
|
519
|
-
include_not_found: Whether to include tasks that
|
|
520
|
+
include_not_found: Whether to include tasks that have no payload.
|
|
520
521
|
|
|
521
522
|
Returns:
|
|
522
523
|
Tuple with the following information:
|
|
523
|
-
* total items in the queue
|
|
524
|
-
*
|
|
524
|
+
* total items in the queue, including tasks without a payload
|
|
525
|
+
* the latest `limit` tasks after the latest `skip` tasks, in
|
|
526
|
+
ascending order of score. If tasks without a payload are left
|
|
527
|
+
out, this list can be shorter than `limit` before the end of the
|
|
528
|
+
queue.
|
|
525
529
|
"""
|
|
526
530
|
|
|
527
531
|
key = tiger._key(state, queue)
|
|
@@ -549,8 +553,9 @@ class Task:
|
|
|
549
553
|
range(len(items)), results[0], results[1:], tss
|
|
550
554
|
):
|
|
551
555
|
if serialized_data is None:
|
|
552
|
-
if include_not_found:
|
|
553
|
-
|
|
556
|
+
if not include_not_found:
|
|
557
|
+
continue
|
|
558
|
+
data = {"id": items[idx][0]}
|
|
554
559
|
else:
|
|
555
560
|
data = json.loads(serialized_data)
|
|
556
561
|
|
|
@@ -572,8 +577,9 @@ class Task:
|
|
|
572
577
|
)
|
|
573
578
|
for idx, serialized_data, ts in zip(range(len(items)), result, tss):
|
|
574
579
|
if serialized_data is None:
|
|
575
|
-
if include_not_found:
|
|
576
|
-
|
|
580
|
+
if not include_not_found:
|
|
581
|
+
continue
|
|
582
|
+
data = {"id": items[idx][0]}
|
|
577
583
|
else:
|
|
578
584
|
data = json.loads(serialized_data)
|
|
579
585
|
|
|
@@ -4,7 +4,6 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import random
|
|
6
6
|
import signal
|
|
7
|
-
import sys
|
|
8
7
|
import time
|
|
9
8
|
import uuid
|
|
10
9
|
from collections import OrderedDict
|
|
@@ -832,10 +831,13 @@ class Worker:
|
|
|
832
831
|
runner = runner_class(self.tiger)
|
|
833
832
|
runner.on_permanent_error(task, execution)
|
|
834
833
|
|
|
835
|
-
#
|
|
836
|
-
#
|
|
834
|
+
# Force the process to exit after a task times out. A normal interpreter
|
|
835
|
+
# shutdown can wait indefinitely for threads started by the task.
|
|
837
836
|
if self.executor.exit_worker_on_job_timeout and has_job_timeout:
|
|
838
|
-
|
|
837
|
+
try:
|
|
838
|
+
os.write(2, b"exiting worker due to job timeout error\n")
|
|
839
|
+
finally:
|
|
840
|
+
os._exit(1)
|
|
839
841
|
|
|
840
842
|
def _worker_run(self) -> None:
|
|
841
843
|
"""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import threading
|
|
2
3
|
import time
|
|
3
4
|
from math import ceil
|
|
4
5
|
|
|
@@ -54,6 +55,15 @@ def long_task_killed():
|
|
|
54
55
|
time.sleep(DELAY * 2)
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
@tiger.task(hard_timeout=DELAY)
|
|
59
|
+
def long_task_with_blocked_thread():
|
|
60
|
+
blocker = threading.Event()
|
|
61
|
+
# A non-daemon thread makes normal interpreter shutdown wait indefinitely.
|
|
62
|
+
thread = threading.Thread(target=blocker.wait, daemon=False)
|
|
63
|
+
thread.start()
|
|
64
|
+
time.sleep(DELAY * 2)
|
|
65
|
+
|
|
66
|
+
|
|
57
67
|
@tiger.task(hard_timeout=DELAY * 2)
|
|
58
68
|
def long_task_ok():
|
|
59
69
|
# Signal task has started
|
|
@@ -1072,6 +1072,35 @@ class TestTasks(BaseTestCase):
|
|
|
1072
1072
|
assert n == 1
|
|
1073
1073
|
assert len(tasks[0].executions) == 2
|
|
1074
1074
|
|
|
1075
|
+
@pytest.mark.parametrize("load_executions", [0, 1])
|
|
1076
|
+
@pytest.mark.parametrize("missing_index", [0, 1, 2])
|
|
1077
|
+
def test_tasks_from_queue_task_not_found(self, load_executions, missing_index):
|
|
1078
|
+
tasks = [Task(self.tiger, simple_task) for _ in range(3)]
|
|
1079
|
+
# Distinct scheduled times give a fixed order in the zset.
|
|
1080
|
+
for minutes, task in enumerate(tasks, start=1):
|
|
1081
|
+
task.delay(when=datetime.timedelta(minutes=minutes))
|
|
1082
|
+
missing_task = tasks[missing_index]
|
|
1083
|
+
self.conn.delete(self.tiger._key("task", missing_task.id))
|
|
1084
|
+
|
|
1085
|
+
n, found = Task.tasks_from_queue(
|
|
1086
|
+
self.tiger, "default", "scheduled", load_executions=load_executions
|
|
1087
|
+
)
|
|
1088
|
+
assert n == 3
|
|
1089
|
+
assert [task.id for task in found] == [
|
|
1090
|
+
task.id for task in tasks if task is not missing_task
|
|
1091
|
+
]
|
|
1092
|
+
|
|
1093
|
+
n, found = Task.tasks_from_queue(
|
|
1094
|
+
self.tiger,
|
|
1095
|
+
"default",
|
|
1096
|
+
"scheduled",
|
|
1097
|
+
load_executions=load_executions,
|
|
1098
|
+
include_not_found=True,
|
|
1099
|
+
)
|
|
1100
|
+
assert n == 3
|
|
1101
|
+
assert [task.id for task in found] == [task.id for task in tasks]
|
|
1102
|
+
assert found[missing_index].data == {"id": missing_task.id}
|
|
1103
|
+
|
|
1075
1104
|
def test_eager(self):
|
|
1076
1105
|
self.tiger.config["ALWAYS_EAGER"] = True
|
|
1077
1106
|
|
|
@@ -4,7 +4,6 @@ import datetime
|
|
|
4
4
|
import time
|
|
5
5
|
from multiprocessing import Process
|
|
6
6
|
|
|
7
|
-
import pytest
|
|
8
7
|
from freezefrog import FreezeTime
|
|
9
8
|
|
|
10
9
|
from tasktiger import Task, Worker
|
|
@@ -15,8 +14,8 @@ from tasktiger.worker import LOCK_REDIS_KEY
|
|
|
15
14
|
from .config import DELAY
|
|
16
15
|
from .tasks import (
|
|
17
16
|
exception_task,
|
|
18
|
-
long_task_killed,
|
|
19
17
|
long_task_ok,
|
|
18
|
+
long_task_with_blocked_thread,
|
|
20
19
|
simple_task,
|
|
21
20
|
sleep_task,
|
|
22
21
|
system_exit_task,
|
|
@@ -182,11 +181,19 @@ class TestSyncExecutorWorker:
|
|
|
182
181
|
ensure_queues(error={"default": 1})
|
|
183
182
|
|
|
184
183
|
def test_handles_timeout(self, tiger, ensure_queues):
|
|
185
|
-
Task(tiger,
|
|
186
|
-
worker =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
Task(tiger, long_task_with_blocked_thread).delay()
|
|
185
|
+
worker = Process(
|
|
186
|
+
target=external_worker,
|
|
187
|
+
kwargs={"worker_kwargs": {"executor_class": SyncExecutor}},
|
|
188
|
+
)
|
|
189
|
+
worker.start()
|
|
190
|
+
worker.join(timeout=DELAY * 5)
|
|
191
|
+
try:
|
|
192
|
+
assert worker.exitcode == 1
|
|
193
|
+
finally:
|
|
194
|
+
if worker.is_alive():
|
|
195
|
+
worker.kill()
|
|
196
|
+
worker.join()
|
|
190
197
|
ensure_queues(error={"default": 1})
|
|
191
198
|
|
|
192
199
|
def test_heartbeat(self, tiger):
|
|
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
|