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.
Files changed (56) hide show
  1. {tasktiger-0.26.1/tasktiger.egg-info → tasktiger-0.26.3}/PKG-INFO +1 -1
  2. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/__init__.py +1 -1
  3. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/_internal.py +2 -2
  4. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/schedule.py +5 -3
  5. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/task.py +3 -5
  6. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/tasktiger.py +8 -6
  7. tasktiger-0.26.3/tasktiger/types.py +9 -0
  8. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/worker.py +6 -4
  9. {tasktiger-0.26.1 → tasktiger-0.26.3/tasktiger.egg-info}/PKG-INFO +1 -1
  10. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/tasks.py +10 -0
  11. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_workers.py +14 -7
  12. tasktiger-0.26.1/tasktiger/types.py +0 -3
  13. {tasktiger-0.26.1 → tasktiger-0.26.3}/LICENSE +0 -0
  14. {tasktiger-0.26.1 → tasktiger-0.26.3}/MANIFEST.in +0 -0
  15. {tasktiger-0.26.1 → tasktiger-0.26.3}/README.rst +0 -0
  16. {tasktiger-0.26.1 → tasktiger-0.26.3}/pyproject.toml +0 -0
  17. {tasktiger-0.26.1 → tasktiger-0.26.3}/setup.cfg +0 -0
  18. {tasktiger-0.26.1 → tasktiger-0.26.3}/setup.py +0 -0
  19. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/exceptions.py +0 -0
  20. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/executor.py +0 -0
  21. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/flask_script.py +0 -0
  22. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/logging.py +0 -0
  23. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/lua/move_task.lua +0 -0
  24. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/lua/semaphore.lua +0 -0
  25. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/migrations.py +0 -0
  26. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/redis_scripts.py +0 -0
  27. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/redis_semaphore.py +0 -0
  28. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/retry.py +0 -0
  29. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/rollbar.py +0 -0
  30. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/runner.py +0 -0
  31. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/stats.py +0 -0
  32. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/test_helpers.py +0 -0
  33. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/timeouts.py +0 -0
  34. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger/utils.py +0 -0
  35. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/SOURCES.txt +0 -0
  36. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/dependency_links.txt +0 -0
  37. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/entry_points.txt +0 -0
  38. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/requires.txt +0 -0
  39. {tasktiger-0.26.1 → tasktiger-0.26.3}/tasktiger.egg-info/top_level.txt +0 -0
  40. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/__init__.py +0 -0
  41. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/config.py +0 -0
  42. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/conftest.py +0 -0
  43. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/tasks_periodic.py +0 -0
  44. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_base.py +0 -0
  45. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_context_manager.py +0 -0
  46. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_lazy_init.py +0 -0
  47. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_logging.py +0 -0
  48. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_migrations.py +0 -0
  49. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_periodic.py +0 -0
  50. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_queue_size.py +0 -0
  51. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_redis_scripts.py +0 -0
  52. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_semaphore.py +0 -0
  53. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_stats.py +0 -0
  54. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/test_task.py +0 -0
  55. {tasktiger-0.26.1 → tasktiger-0.26.3}/tests/utils.py +0 -0
  56. {tasktiger-0.26.1 → tasktiger-0.26.3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktiger
3
- Version: 0.26.1
3
+ Version: 0.26.3
4
4
  Summary: Python task queue
5
5
  Home-page: http://github.com/closeio/tasktiger
6
6
  License: MIT
@@ -12,7 +12,7 @@ from .task import Task
12
12
  from .tasktiger import TaskTiger, run_worker
13
13
  from .worker import Worker
14
14
 
15
- __version__ = "0.26.1"
15
+ __version__ = "0.26.3"
16
16
  __all__ = [
17
17
  "TaskTiger",
18
18
  "Worker",
@@ -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) -> Tuple[str, Tuple]:
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 Callable, Optional, Tuple
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
- ) -> Tuple[Callable[..., Optional[datetime.datetime]], Tuple]:
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
- ) -> Tuple[Callable[..., Optional[datetime.datetime]], Tuple]:
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[RetryStrategy]:
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
- Union[Callable[[int], float], Tuple[Callable[..., float], Tuple]]
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
- # Exit the process with an error code if a task timed out to
836
- # prevent any inconsistent state in case the runner requires it.
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
- sys.exit("exiting worker due to job timeout error")
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktiger
3
- Version: 0.26.1
3
+ Version: 0.26.3
4
4
  Summary: Python task queue
5
5
  Home-page: http://github.com/closeio/tasktiger
6
6
  License: MIT
@@ -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, long_task_killed).delay()
186
- worker = Worker(tiger, executor_class=SyncExecutor)
187
- # Worker should exit to avoid any inconsistencies.
188
- with pytest.raises(SystemExit):
189
- worker.run(once=True, force_once=True)
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):
@@ -1,3 +0,0 @@
1
- from typing import Callable, Tuple
2
-
3
- RetryStrategy = Tuple[Callable[..., float], Tuple]
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