tasktiger 0.26.1__tar.gz → 0.26.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.
- {tasktiger-0.26.1/tasktiger.egg-info → tasktiger-0.26.3}/PKG-INFO +1 -1
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/__init__.py +1 -1
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/_internal.py +2 -2
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/schedule.py +5 -3
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/task.py +3 -5
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/tasktiger.py +8 -6
- tasktiger-0.26.3/tasktiger/types.py +9 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/worker.py +6 -4
- {tasktiger-0.26.1 → tasktiger-0.26.3/tasktiger.egg-info}/PKG-INFO +1 -1
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/tasks.py +10 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_workers.py +14 -7
- tasktiger-0.26.1/tasktiger/types.py +0 -3
- {tasktiger-0.26.1 → tasktiger-0.26.3}/LICENSE +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/MANIFEST.in +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/README.rst +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/pyproject.toml +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/setup.cfg +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/setup.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/exceptions.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/executor.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/flask_script.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/logging.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/lua/move_task.lua +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/lua/semaphore.lua +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/migrations.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/redis_scripts.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/redis_semaphore.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/retry.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/rollbar.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/runner.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/stats.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/test_helpers.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/timeouts.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/utils.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/SOURCES.txt +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/dependency_links.txt +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/entry_points.txt +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/requires.txt +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/top_level.txt +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/__init__.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/config.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/conftest.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/tasks_periodic.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_base.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_context_manager.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_lazy_init.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_logging.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_migrations.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_periodic.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_queue_size.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_redis_scripts.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_semaphore.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_stats.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_task.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/utils.py +0 -0
- {tasktiger-0.26.1 → tasktiger-0.26.3}/tox.ini +0 -0
|
@@ -15,13 +15,13 @@ from typing import (
|
|
|
15
15
|
Iterable,
|
|
16
16
|
List,
|
|
17
17
|
Optional,
|
|
18
|
-
Tuple,
|
|
19
18
|
Type,
|
|
20
19
|
TypedDict,
|
|
21
20
|
Union,
|
|
22
21
|
)
|
|
23
22
|
|
|
24
23
|
from .exceptions import TaskImportError
|
|
24
|
+
from .types import SerializedRetryStrategy
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from .task import Task
|
|
@@ -134,7 +134,7 @@ def reversed_dotted_parts(s: str) -> Iterable[str]:
|
|
|
134
134
|
yield s[:idx]
|
|
135
135
|
|
|
136
136
|
|
|
137
|
-
def serialize_retry_method(retry_method: Any) ->
|
|
137
|
+
def serialize_retry_method(retry_method: Any) -> SerializedRetryStrategy:
|
|
138
138
|
if callable(retry_method):
|
|
139
139
|
return (serialize_func_name(retry_method), ())
|
|
140
140
|
else:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from .types import ScheduleStrategy
|
|
3
5
|
|
|
4
6
|
__all__ = ["periodic", "cron_expr"]
|
|
5
7
|
|
|
@@ -40,7 +42,7 @@ def periodic(
|
|
|
40
42
|
weeks: int = 0,
|
|
41
43
|
start_date: Optional[datetime.datetime] = None,
|
|
42
44
|
end_date: Optional[datetime.datetime] = None,
|
|
43
|
-
) ->
|
|
45
|
+
) -> ScheduleStrategy:
|
|
44
46
|
"""
|
|
45
47
|
Periodic task schedule: Use to schedule a task to run periodically,
|
|
46
48
|
starting from start_date (or None to be active immediately) until end_date
|
|
@@ -94,7 +96,7 @@ def cron_expr(
|
|
|
94
96
|
expr: str,
|
|
95
97
|
start_date: Optional[datetime.datetime] = None,
|
|
96
98
|
end_date: Optional[datetime.datetime] = None,
|
|
97
|
-
) ->
|
|
99
|
+
) -> ScheduleStrategy:
|
|
98
100
|
"""
|
|
99
101
|
Periodic task schedule via cron expression: Use to schedule a task to run periodically,
|
|
100
102
|
starting from start_date (or None to be active immediately) until end_date
|
|
@@ -32,7 +32,7 @@ from ._internal import (
|
|
|
32
32
|
)
|
|
33
33
|
from .exceptions import QueueFullException, TaskImportError, TaskNotFound
|
|
34
34
|
from .runner import BaseRunner, get_runner_class
|
|
35
|
-
from .types import RetryStrategy
|
|
35
|
+
from .types import RetryStrategy, SerializedRetryStrategy
|
|
36
36
|
|
|
37
37
|
if TYPE_CHECKING:
|
|
38
38
|
from . import TaskTiger
|
|
@@ -55,9 +55,7 @@ class Task:
|
|
|
55
55
|
lock_key: Optional[Collection[str]] = None,
|
|
56
56
|
retry: Optional[bool] = None,
|
|
57
57
|
retry_on: Optional[Collection[Type[BaseException]]] = None,
|
|
58
|
-
retry_method: Optional[
|
|
59
|
-
Union[Callable[[int], float], Tuple[Callable[..., float], Tuple]]
|
|
60
|
-
] = None,
|
|
58
|
+
retry_method: Optional[Union[Callable[[int], float], RetryStrategy]] = None,
|
|
61
59
|
max_queue_size: Optional[int] = None,
|
|
62
60
|
max_stored_executions: Optional[int] = None,
|
|
63
61
|
runner_class: Optional[Type["BaseRunner"]] = None,
|
|
@@ -237,7 +235,7 @@ class Task:
|
|
|
237
235
|
return self._data.get("unique_key")
|
|
238
236
|
|
|
239
237
|
@property
|
|
240
|
-
def retry_method(self) -> Optional[
|
|
238
|
+
def retry_method(self) -> Optional[SerializedRetryStrategy]:
|
|
241
239
|
if "retry_method" in self._data:
|
|
242
240
|
retry_func, retry_args = self._data["retry_method"]
|
|
243
241
|
return retry_func, retry_args
|
|
@@ -36,6 +36,7 @@ from .redis_semaphore import Semaphore
|
|
|
36
36
|
from .retry import fixed
|
|
37
37
|
from .runner import BaseRunner
|
|
38
38
|
from .task import Task
|
|
39
|
+
from .types import RetryStrategy, ScheduleStrategy
|
|
39
40
|
from .worker import LOCK_REDIS_KEY, Worker
|
|
40
41
|
|
|
41
42
|
__all__ = ["TaskTiger"]
|
|
@@ -312,10 +313,13 @@ class TaskTiger:
|
|
|
312
313
|
lock_key: Optional[Collection[str]] = None,
|
|
313
314
|
retry: Optional[bool] = None,
|
|
314
315
|
retry_on: Optional[Collection[Type[BaseException]]] = None,
|
|
315
|
-
retry_method: Optional[
|
|
316
|
-
|
|
316
|
+
retry_method: Optional[Union[Callable[[int], float], RetryStrategy]] = None,
|
|
317
|
+
schedule: Optional[
|
|
318
|
+
Union[
|
|
319
|
+
Callable[[datetime.datetime], Optional[datetime.datetime]],
|
|
320
|
+
ScheduleStrategy,
|
|
321
|
+
]
|
|
317
322
|
] = None,
|
|
318
|
-
schedule: Optional[Callable] = None,
|
|
319
323
|
batch: bool = False,
|
|
320
324
|
max_queue_size: Optional[int] = None,
|
|
321
325
|
max_stored_executions: Optional[int] = None,
|
|
@@ -441,9 +445,7 @@ class TaskTiger:
|
|
|
441
445
|
when: Optional[Union[datetime.datetime, datetime.timedelta]] = None,
|
|
442
446
|
retry: Optional[bool] = None,
|
|
443
447
|
retry_on: Optional[Collection[Type[BaseException]]] = None,
|
|
444
|
-
retry_method: Optional[
|
|
445
|
-
Union[Callable[[int], float], Tuple[Callable[..., float], Tuple]]
|
|
446
|
-
] = None,
|
|
448
|
+
retry_method: Optional[Union[Callable[[int], float], RetryStrategy]] = None,
|
|
447
449
|
max_queue_size: Optional[int] = None,
|
|
448
450
|
max_stored_executions: Optional[int] = None,
|
|
449
451
|
runner_class: Optional[Type["BaseRunner"]] = None,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Any, Callable, Optional, Sequence, Tuple
|
|
3
|
+
|
|
4
|
+
RetryStrategy = Tuple[Callable[..., float], Tuple[Any, ...]]
|
|
5
|
+
ScheduleStrategy = Tuple[Callable[..., Optional[datetime.datetime]], Tuple[Any, ...]]
|
|
6
|
+
|
|
7
|
+
# A retry strategy as stored with a task. The function is replaced with its
|
|
8
|
+
# import path, and the arguments come back as a list after a JSON round trip.
|
|
9
|
+
SerializedRetryStrategy = Tuple[str, Sequence[Any]]
|
|
@@ -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
|
|
@@ -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
|