csu 2.2.4__tar.gz → 2.3.0__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 (108) hide show
  1. {csu-2.2.4 → csu-2.3.0}/.bumpversion.cfg +1 -1
  2. {csu-2.2.4 → csu-2.3.0}/.cookiecutterrc +1 -1
  3. {csu-2.2.4 → csu-2.3.0}/PKG-INFO +3 -3
  4. {csu-2.2.4 → csu-2.3.0}/README.rst +2 -2
  5. {csu-2.2.4 → csu-2.3.0}/pyproject.toml +1 -1
  6. {csu-2.2.4 → csu-2.3.0}/src/csu/__init__.py +1 -1
  7. {csu-2.2.4 → csu-2.3.0}/src/csu/export.py +3 -4
  8. {csu-2.2.4 → csu-2.3.0}/src/csu/test_logging.py +1 -1
  9. {csu-2.2.4 → csu-2.3.0}/src/csu/test_management.py +1 -1
  10. {csu-2.2.4 → csu-2.3.0}/src/csu/test_service.py +4 -5
  11. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/__init__.py +0 -4
  12. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/admin.py +1 -2
  13. csu-2.3.0/src/csu/worker/asgi.py +74 -0
  14. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/engine.py +5 -4
  15. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/models.py +3 -1
  16. {csu-2.2.4 → csu-2.3.0}/src/csu.egg-info/PKG-INFO +3 -3
  17. csu-2.3.0/tests/test_exampleworker.py +164 -0
  18. {csu-2.2.4 → csu-2.3.0}/tests/test_worker.py +1 -1
  19. csu-2.2.4/src/csu/worker/asgi.py +0 -36
  20. csu-2.2.4/tests/test_exampleworker.py +0 -164
  21. {csu-2.2.4 → csu-2.3.0}/.coveragerc +0 -0
  22. {csu-2.2.4 → csu-2.3.0}/.editorconfig +0 -0
  23. {csu-2.2.4 → csu-2.3.0}/.github/workflows/github-actions.yml +0 -0
  24. {csu-2.2.4 → csu-2.3.0}/.pre-commit-config.yaml +0 -0
  25. {csu-2.2.4 → csu-2.3.0}/.taplo.toml +0 -0
  26. {csu-2.2.4 → csu-2.3.0}/AUTHORS.rst +0 -0
  27. {csu-2.2.4 → csu-2.3.0}/CHANGELOG.rst +0 -0
  28. {csu-2.2.4 → csu-2.3.0}/CONTRIBUTING.rst +0 -0
  29. {csu-2.2.4 → csu-2.3.0}/LICENSE +0 -0
  30. {csu-2.2.4 → csu-2.3.0}/MANIFEST.in +0 -0
  31. {csu-2.2.4 → csu-2.3.0}/ci/bootstrap.py +0 -0
  32. {csu-2.2.4 → csu-2.3.0}/ci/requirements.txt +0 -0
  33. {csu-2.2.4 → csu-2.3.0}/ci/templates/.github/workflows/github-actions.yml +0 -0
  34. {csu-2.2.4 → csu-2.3.0}/pytest.ini +0 -0
  35. {csu-2.2.4 → csu-2.3.0}/setup.cfg +0 -0
  36. {csu-2.2.4 → csu-2.3.0}/src/csu/admin.py +0 -0
  37. {csu-2.2.4 → csu-2.3.0}/src/csu/conf.py +0 -0
  38. {csu-2.2.4 → csu-2.3.0}/src/csu/consts.py +0 -0
  39. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/__init__.py +0 -0
  40. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/auth.py +0 -0
  41. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/fields.py +0 -0
  42. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/forms.py +0 -0
  43. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/perms.py +0 -0
  44. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/phonenumber.py +0 -0
  45. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/serializers.py +0 -0
  46. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/test_auth.py +0 -0
  47. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/test_fields.py +0 -0
  48. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/test_forms.py +0 -0
  49. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/test_phonenumber.py +0 -0
  50. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/test_serializers.py +0 -0
  51. {csu-2.2.4 → csu-2.3.0}/src/csu/drf/views.py +0 -0
  52. {csu-2.2.4 → csu-2.3.0}/src/csu/enums.py +0 -0
  53. {csu-2.2.4 → csu-2.3.0}/src/csu/env.py +0 -0
  54. {csu-2.2.4 → csu-2.3.0}/src/csu/exceptions.py +0 -0
  55. {csu-2.2.4 → csu-2.3.0}/src/csu/fixups.py +0 -0
  56. {csu-2.2.4 → csu-2.3.0}/src/csu/forms/__init__.py +0 -0
  57. {csu-2.2.4 → csu-2.3.0}/src/csu/forms/crispy.py +0 -0
  58. {csu-2.2.4 → csu-2.3.0}/src/csu/forms/fields.py +0 -0
  59. {csu-2.2.4 → csu-2.3.0}/src/csu/gettext.py +0 -0
  60. {csu-2.2.4 → csu-2.3.0}/src/csu/gettext_lazy.py +0 -0
  61. {csu-2.2.4 → csu-2.3.0}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
  62. {csu-2.2.4 → csu-2.3.0}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
  63. {csu-2.2.4 → csu-2.3.0}/src/csu/logging.py +0 -0
  64. {csu-2.2.4 → csu-2.3.0}/src/csu/management.py +0 -0
  65. {csu-2.2.4 → csu-2.3.0}/src/csu/models.py +0 -0
  66. {csu-2.2.4 → csu-2.3.0}/src/csu/query.py +0 -0
  67. {csu-2.2.4 → csu-2.3.0}/src/csu/routers.py +0 -0
  68. {csu-2.2.4 → csu-2.3.0}/src/csu/service.py +0 -0
  69. {csu-2.2.4 → csu-2.3.0}/src/csu/templates/api_exception.html +0 -0
  70. {csu-2.2.4 → csu-2.3.0}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
  71. {csu-2.2.4 → csu-2.3.0}/src/csu/test_consts.py +0 -0
  72. {csu-2.2.4 → csu-2.3.0}/src/csu/test_env.py +0 -0
  73. {csu-2.2.4 → csu-2.3.0}/src/csu/test_exceptions.py +0 -0
  74. {csu-2.2.4 → csu-2.3.0}/src/csu/test_timezones.py +0 -0
  75. {csu-2.2.4 → csu-2.3.0}/src/csu/test_utils.py +0 -0
  76. {csu-2.2.4 → csu-2.3.0}/src/csu/test_xml.py +0 -0
  77. {csu-2.2.4 → csu-2.3.0}/src/csu/timezones.py +0 -0
  78. {csu-2.2.4 → csu-2.3.0}/src/csu/utils.py +0 -0
  79. {csu-2.2.4 → csu-2.3.0}/src/csu/views.py +0 -0
  80. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/enums.py +0 -0
  81. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/job.py +0 -0
  82. {csu-2.2.4 → csu-2.3.0}/src/csu/worker/registry.py +0 -0
  83. {csu-2.2.4 → csu-2.3.0}/src/csu/wsgi.py +0 -0
  84. {csu-2.2.4 → csu-2.3.0}/src/csu/xml.py +0 -0
  85. {csu-2.2.4 → csu-2.3.0}/src/csu.egg-info/SOURCES.txt +0 -0
  86. {csu-2.2.4 → csu-2.3.0}/src/csu.egg-info/dependency_links.txt +0 -0
  87. {csu-2.2.4 → csu-2.3.0}/src/csu.egg-info/requires.txt +0 -0
  88. {csu-2.2.4 → csu-2.3.0}/src/csu.egg-info/top_level.txt +0 -0
  89. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
  90. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
  91. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service/test_decoding.yaml +0 -0
  92. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service/test_error.yaml +0 -0
  93. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service/test_logging.yaml +0 -0
  94. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service/test_redirects.yaml +0 -0
  95. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
  96. {csu-2.2.4 → csu-2.3.0}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
  97. {csu-2.2.4 → csu-2.3.0}/tests/conftest.py +0 -0
  98. {csu-2.2.4 → csu-2.3.0}/tests/exampleworker.py +0 -0
  99. {csu-2.2.4 → csu-2.3.0}/tests/test_models.py +0 -0
  100. {csu-2.2.4 → csu-2.3.0}/tests/test_service.py +0 -0
  101. {csu-2.2.4 → csu-2.3.0}/tests/test_service_auth.py +0 -0
  102. {csu-2.2.4 → csu-2.3.0}/tests/test_views.py +0 -0
  103. {csu-2.2.4 → csu-2.3.0}/tests/testproject/__init__.py +0 -0
  104. {csu-2.2.4 → csu-2.3.0}/tests/testproject/fixtures/testuser.json +0 -0
  105. {csu-2.2.4 → csu-2.3.0}/tests/testproject/models.py +0 -0
  106. {csu-2.2.4 → csu-2.3.0}/tests/testproject/settings.py +0 -0
  107. {csu-2.2.4 → csu-2.3.0}/tests/testproject/urls.py +0 -0
  108. {csu-2.2.4 → csu-2.3.0}/tox.ini +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 2.2.4
2
+ current_version = 2.3.0
3
3
  commit = True
4
4
  tag = True
5
5
 
@@ -40,7 +40,7 @@ default_context:
40
40
  sphinx_theme: furo
41
41
  test_matrix_separate_coverage: 'no'
42
42
  tests_inside_package: 'yes'
43
- version: 2.2.4
43
+ version: 2.3.0
44
44
  version_manager: bump2version
45
45
  website: https://blog.ionelmc.ro
46
46
  year_from: '2024'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csu
3
- Version: 2.2.4
3
+ Version: 2.3.0
4
4
  Summary: Clean Slate Utils - bunch of utility code, mostly Django/DRF specific.
5
5
  Author-email: Ionel Cristian Mărieș <contact@ionelmc.ro>
6
6
  License-Expression: BSD-2-Clause
@@ -77,9 +77,9 @@ Overview
77
77
  .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
78
78
  :alt: Supported implementations
79
79
  :target: https://pypi.org/project/csu
80
- .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.2.4.svg
80
+ .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.3.0.svg
81
81
  :alt: Commits since latest release
82
- :target: https://github.com/ionelmc/python-csu/compare/v2.2.4...main
82
+ :target: https://github.com/ionelmc/python-csu/compare/v2.3.0...main
83
83
  .. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
84
84
  :alt: Scrutinizer Status
85
85
  :target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
@@ -34,9 +34,9 @@ Overview
34
34
  .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
35
35
  :alt: Supported implementations
36
36
  :target: https://pypi.org/project/csu
37
- .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.2.4.svg
37
+ .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.3.0.svg
38
38
  :alt: Commits since latest release
39
- :target: https://github.com/ionelmc/python-csu/compare/v2.2.4...main
39
+ :target: https://github.com/ionelmc/python-csu/compare/v2.3.0...main
40
40
  .. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
41
41
  :alt: Scrutinizer Status
42
42
  :target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
@@ -12,7 +12,7 @@ dynamic = [
12
12
  "readme",
13
13
  ]
14
14
  name = "csu"
15
- version = "2.2.4"
15
+ version = "2.3.0"
16
16
  license = "BSD-2-Clause"
17
17
  license-files = ["LICENSE"]
18
18
  description = "Clean Slate Utils - bunch of utility code, mostly Django/DRF specific."
@@ -2,4 +2,4 @@
2
2
  a.k.a Clean Slate Utils
3
3
  """
4
4
 
5
- __version__ = "2.2.4"
5
+ __version__ = "2.3.0"
@@ -26,10 +26,9 @@ from import_export.widgets import Widget
26
26
  from rest_framework import serializers
27
27
  from tablib import InvalidDimensions
28
28
 
29
- from csu.gettext import _
30
- from csu.timezones import adjust_dt
31
- from csu.timezones import now
32
-
29
+ from .gettext import _
30
+ from .timezones import adjust_dt
31
+ from .timezones import now
33
32
  from .timezones import today
34
33
 
35
34
  EXPORT_FORMATS = {
@@ -1,4 +1,4 @@
1
- from csu.logging import fa_append_repr
1
+ from .logging import fa_append_repr
2
2
 
3
3
 
4
4
  def test_fa_append_repr():
@@ -2,7 +2,7 @@ from unittest.mock import call
2
2
 
3
3
  import pytest
4
4
 
5
- from csu.management import SimpleCommand
5
+ from .management import SimpleCommand
6
6
 
7
7
  junk = dict.fromkeys(["verbosity", "settings", "pythonpath", "traceback", "no_color", "force_color", "skip_checks"])
8
8
 
@@ -4,13 +4,12 @@ import time_machine
4
4
  from niquests import PreparedRequest
5
5
  from niquests import Response
6
6
 
7
- from csu.service import AsyncHTTPService
8
- from csu.service import AsyncHTTPServiceContext
9
- from csu.service import HTTPService
10
- from csu.service import HTTPServiceContext
11
-
12
7
  from . import exceptions
13
8
  from .exceptions import get_event_id
9
+ from .service import AsyncHTTPService
10
+ from .service import AsyncHTTPServiceContext
11
+ from .service import HTTPService
12
+ from .service import HTTPServiceContext
14
13
 
15
14
 
16
15
  def test_get_accident_id(monkeypatch):
@@ -17,7 +17,3 @@ Typical batch processing workload::
17
17
 
18
18
  The database is completely optional.
19
19
  """
20
-
21
- from logging import getLogger
22
-
23
- logger = getLogger("worker")
@@ -8,8 +8,6 @@ from django.utils.html import format_html
8
8
  from django.utils.safestring import mark_safe
9
9
  from import_export.admin import ImportExportMixin
10
10
 
11
- from csu.timezones import now
12
-
13
11
  from ..export import ChoiceWidget
14
12
  from ..export import FullFilenameExportAdminMixin
15
13
  from ..export import SmartModelResource
@@ -17,6 +15,7 @@ from ..gettext import _
17
15
  from .models import AbstractTask
18
16
  from .models import QueueState
19
17
  from .models import ResultType
18
+ from .timezones import now
20
19
 
21
20
 
22
21
  class AbstractTaskResource(SmartModelResource):
@@ -0,0 +1,74 @@
1
+ from logging import getLogger
2
+
3
+ import django
4
+ from asgiref.typing import ASGIReceiveCallable
5
+ from asgiref.typing import ASGIReceiveEvent
6
+ from asgiref.typing import ASGISendCallable
7
+ from asgiref.typing import LifespanShutdownCompleteEvent
8
+ from asgiref.typing import LifespanShutdownFailedEvent
9
+ from asgiref.typing import LifespanStartupCompleteEvent
10
+ from asgiref.typing import LifespanStartupFailedEvent
11
+ from django.core.handlers.asgi import ASGIHandler
12
+
13
+ from ..utils import lazy_import_classproperty
14
+
15
+ logger = getLogger(__name__)
16
+
17
+
18
+ class WorkerLifespanASGIHandler(ASGIHandler):
19
+ async def __call__(self, scope, receive: ASGIReceiveCallable, send) -> None:
20
+ if scope["type"] == "lifespan":
21
+ await self.handle_lifespan(receive, send)
22
+ else:
23
+ await super().__call__(scope, receive, send)
24
+
25
+ engine_registry = lazy_import_classproperty("csu.worker.registry.REGISTRY")
26
+
27
+ async def handle_start_engines(self):
28
+ registry = self.engine_registry
29
+ logger.info("Found %s registered engines.", len(registry))
30
+ for module_name, engine in registry.items():
31
+ logger.info("Starting engine for %s: %s...", module_name, engine)
32
+ if engine.shutdown_on:
33
+ raise RuntimeError(f"Engine {type(engine)} shutdown_on should be empty, not {engine.shutdown_on}!")
34
+ await engine.start()
35
+ logger.info(
36
+ "Started %s engines: \n %s",
37
+ len(registry),
38
+ "\n ".join(f"{module_name}: {engine}" for module_name, engine in registry.items()),
39
+ )
40
+
41
+ async def handle_stop_engines(self):
42
+ registry = self.engine_registry
43
+ logger.info("Stopping %s registered engines.", len(registry))
44
+ for engine in registry.values():
45
+ await engine.stop(graceful=True)
46
+
47
+ async def handle_lifespan(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
48
+ while True:
49
+ message: ASGIReceiveEvent = await receive()
50
+ match message["type"]:
51
+ case "lifespan.startup":
52
+ try:
53
+ await self.handle_start_engines()
54
+ except Exception as exc:
55
+ await send(LifespanStartupFailedEvent(type="lifespan.startup.failed", message=str(exc)))
56
+ raise
57
+ else:
58
+ await send(LifespanStartupCompleteEvent(type="lifespan.startup.complete"))
59
+ case "lifespan.shutdown":
60
+ try:
61
+ await self.handle_stop_engines()
62
+ except Exception as exc:
63
+ await send(LifespanShutdownFailedEvent(type="lifespan.shutdown.failed", message=str(exc)))
64
+ raise
65
+ else:
66
+ await send(LifespanShutdownCompleteEvent(type="lifespan.shutdown.complete"))
67
+ return
68
+ case _:
69
+ raise ValueError(f"Unexpected message type: {message['type']!r}", message)
70
+
71
+
72
+ def get_worker_lifespan_application() -> WorkerLifespanASGIHandler:
73
+ django.setup(set_prefix=False)
74
+ return WorkerLifespanASGIHandler()
@@ -18,6 +18,7 @@ from datetime import timedelta
18
18
  from enum import Flag
19
19
  from enum import auto
20
20
  from functools import partial
21
+ from logging import getLogger
21
22
  from random import randint
22
23
  from typing import ClassVar
23
24
  from typing import Protocol
@@ -26,10 +27,11 @@ from datetimerange import DateTimeRange
26
27
 
27
28
  from ..exceptions import InternalServiceError
28
29
  from ..timezones import naivenow
29
- from . import logger
30
30
  from .enums import WorkerState
31
31
  from .job import Job
32
32
 
33
+ logger = getLogger(__name__)
34
+
33
35
 
34
36
  class TaskProtocol(Protocol):
35
37
  job_kwargs: dict
@@ -246,7 +248,6 @@ class AbstractConsumer(AbstractWorker):
246
248
  result = await self.save_result(
247
249
  job,
248
250
  result_type=self.result_type_enum.SUCCESS if fields else self.result_type_enum.ABSENT,
249
- result_details=None if fields else repr(fields),
250
251
  **fields or {},
251
252
  )
252
253
  idle = False
@@ -270,7 +271,7 @@ class AbstractEngine(ABC):
270
271
  workers: list[AbstractWorker]
271
272
  queue: Queue = None
272
273
  queue_sem: Semaphore
273
- shutdown_on: Iterable[int] = (signal.SIGINT, signal.SIGTERM)
274
+ shutdown_on: Iterable[signal.Signals] = (signal.SIGINT, signal.SIGTERM)
274
275
  shutdown_requested = False
275
276
  shutdown_tasks: ClassVar[dict[bool, asyncio.Task]] = {}
276
277
  if sys.platform == "win32":
@@ -322,7 +323,7 @@ class AbstractEngine(ABC):
322
323
  for sig in self.shutdown_on:
323
324
  loop.add_signal_handler(sig, self.shutdown_handler, sig)
324
325
 
325
- def shutdown_handler(self, sig):
326
+ def shutdown_handler(self, sig: signal.Signals) -> None:
326
327
  sig_name = sig.name
327
328
  graceful = not self.shutdown_requested
328
329
  logger.info("%s.shutdown_handler(%s) %s", self, sig_name, "GRACEFUL" if graceful else "FORCED")
@@ -1,6 +1,7 @@
1
1
  from abc import abstractmethod
2
2
  from asyncio import CancelledError
3
3
  from datetime import timedelta
4
+ from logging import getLogger
4
5
  from random import SystemRandom
5
6
 
6
7
  from asgiref.sync import sync_to_async
@@ -11,11 +12,12 @@ from django.db.models import F
11
12
  from django.db.models import Q
12
13
 
13
14
  from ..timezones import utcnow
14
- from . import logger
15
15
  from .engine import AbstractConsumer
16
16
  from .engine import AbstractProducer
17
17
  from .job import Job
18
18
 
19
+ logger = getLogger(__name__)
20
+
19
21
 
20
22
  def get_random_float(system_random=SystemRandom()): # noqa: B008
21
23
  return system_random.random()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csu
3
- Version: 2.2.4
3
+ Version: 2.3.0
4
4
  Summary: Clean Slate Utils - bunch of utility code, mostly Django/DRF specific.
5
5
  Author-email: Ionel Cristian Mărieș <contact@ionelmc.ro>
6
6
  License-Expression: BSD-2-Clause
@@ -77,9 +77,9 @@ Overview
77
77
  .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
78
78
  :alt: Supported implementations
79
79
  :target: https://pypi.org/project/csu
80
- .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.2.4.svg
80
+ .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.3.0.svg
81
81
  :alt: Commits since latest release
82
- :target: https://github.com/ionelmc/python-csu/compare/v2.2.4...main
82
+ :target: https://github.com/ionelmc/python-csu/compare/v2.3.0...main
83
83
  .. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
84
84
  :alt: Scrutinizer Status
85
85
  :target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
@@ -0,0 +1,164 @@
1
+ import signal
2
+ import sys
3
+ import time
4
+
5
+ from process_tests import TestProcess
6
+ from process_tests import dump_on_error
7
+ from process_tests import wait_for_strings
8
+
9
+
10
+ def test_noskip():
11
+ with TestProcess(sys.executable, "-mexampleworker", "3", "0", "1000", "4") as target, dump_on_error(target.read):
12
+ wait_for_strings(
13
+ target.read,
14
+ 5,
15
+ "INFO:csu.worker.engine:ExampleConsumer(1/UNKNOWN) started.",
16
+ "INFO:csu.worker.engine:ExampleConsumer(2/UNKNOWN) started.",
17
+ "INFO:csu.worker.engine:ExampleConsumer(3/UNKNOWN) started.",
18
+ "INFO:csu.worker.engine:ExampleProducer(1/UNKNOWN) started.",
19
+ "INFO:csu.worker.engine:ExampleProducer(1/UNKNOWN).run() started.",
20
+ "PRODUCER fetch_task @ 1 RETURN",
21
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
22
+ "INFO:csu.worker.engine:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
23
+ "PRODUCER fetch_task @ 2 RETURN",
24
+ "INFO:csu.worker.engine:ExampleEngine(4w/1q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=2))",
25
+ "INFO:csu.worker.engine:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=2).done of NoOpTask(id=2).",
26
+ "PRODUCER fetch_task @ 3 RETURN",
27
+ "INFO:csu.worker.engine:ExampleEngine(4w/2q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
28
+ "INFO:csu.worker.engine:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
29
+ "INFO:csu.worker.engine:ExampleConsumer(1/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
30
+ "INFO:csu.worker.engine:ExampleConsumer(1/WORKING).run() - performing...",
31
+ "CONSUMER 0 perform_work JOB 1 START",
32
+ "INFO:csu.worker.engine:ExampleConsumer(2/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=2)",
33
+ "INFO:csu.worker.engine:ExampleConsumer(2/WORKING).run() - performing...",
34
+ "CONSUMER 1 perform_work JOB 2 START",
35
+ "INFO:csu.worker.engine:ExampleConsumer(3/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
36
+ "INFO:csu.worker.engine:ExampleConsumer(3/WORKING).run() - performing...",
37
+ "CONSUMER 2 perform_work JOB 3 START",
38
+ "CONSUMER 0 perform_work JOB 1 DONE",
39
+ "CONSUMER 0 save_result JOB 1",
40
+ "CONSUMER 1 perform_work JOB 2 DONE",
41
+ "CONSUMER 1 save_result JOB 2",
42
+ "CONSUMER 2 perform_work JOB 3 DONE",
43
+ "CONSUMER 2 save_result JOB 3",
44
+ "PRODUCER fetch_task @ 4 RETURN",
45
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=4))",
46
+ "INFO:csu.worker.engine:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=4).done of NoOpTask(id=4).",
47
+ "PRODUCER fetch_task @ 5 EXHAUSTED",
48
+ "INFO:csu.worker.engine:ExampleConsumer(1/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=4)",
49
+ "INFO:csu.worker.engine:ExampleConsumer(1/WORKING).run() - performing...",
50
+ "CONSUMER 0 perform_work JOB 4 START",
51
+ "PRODUCER fetch_task @ 6 EXHAUSTED",
52
+ "CONSUMER 0 perform_work JOB 4 DONE",
53
+ "CONSUMER 0 save_result JOB 4",
54
+ "PRODUCER fetch_task @ 7 EXHAUSTED",
55
+ )
56
+
57
+
58
+ def test_skip():
59
+ with TestProcess(sys.executable, "-mexampleworker", "3", "2", "100", "4") as target, dump_on_error(target.read):
60
+ wait_for_strings(
61
+ target.read,
62
+ 5,
63
+ "INFO:csu.worker.engine:ExampleConsumer(1/UNKNOWN) started.",
64
+ "INFO:csu.worker.engine:ExampleConsumer(2/UNKNOWN) started.",
65
+ "INFO:csu.worker.engine:ExampleConsumer(3/UNKNOWN) started.",
66
+ "INFO:csu.worker.engine:ExampleProducer(1/UNKNOWN) started.",
67
+ "INFO:csu.worker.engine:ExampleProducer(1/UNKNOWN).run() started.",
68
+ "PRODUCER fetch_task @ 1 RETURN",
69
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
70
+ "INFO:csu.worker.engine:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
71
+ "PRODUCER fetch_task @ 2 SKIP",
72
+ "INFO:csu.worker.engine:ExampleConsumer(1/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
73
+ "INFO:csu.worker.engine:ExampleConsumer(1/WORKING).run() - performing...",
74
+ "CONSUMER 0 perform_work JOB 1 START",
75
+ "CONSUMER 0 perform_work JOB 1 DONE",
76
+ "CONSUMER 0 save_result JOB 1",
77
+ "PRODUCER fetch_task @ 3 RETURN",
78
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
79
+ "INFO:csu.worker.engine:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
80
+ "PRODUCER fetch_task @ 4 SKIP",
81
+ "INFO:csu.worker.engine:ExampleConsumer(2/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
82
+ "INFO:csu.worker.engine:ExampleConsumer(2/WORKING).run() - performing...",
83
+ "CONSUMER 1 perform_work JOB 3 START",
84
+ "CONSUMER 1 perform_work JOB 3 DONE",
85
+ "CONSUMER 1 save_result JOB 3",
86
+ "PRODUCER fetch_task @ 5 EXHAUSTED",
87
+ )
88
+
89
+
90
+ def test_graceful_shutdown():
91
+ with TestProcess(sys.executable, "-mexampleworker", "3", "0", "1000", "100") as target, dump_on_error(target.read):
92
+ wait_for_strings(
93
+ target.read,
94
+ 15,
95
+ "PRODUCER fetch_task @ 1 RETURN",
96
+ )
97
+ target.proc.send_signal(signal.SIGINT)
98
+ wait_for_strings(
99
+ target.read,
100
+ 15,
101
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).shutdown_handler(SIGINT) GRACEFUL",
102
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).stop(graceful=True)",
103
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) stats: {'ExampleConsumer': {'WORKING': 3}, 'ExampleProducer': {'WAITING': 1}, 'ExampleEngine': {'JOBS': 3, 'QSIZE': 0, 'SEMVAL': 0}}",
104
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) worker 1: <__main__.ExampleConsumer object at ",
105
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) worker 2: <__main__.ExampleConsumer object at ",
106
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) worker 3: <__main__.ExampleConsumer object at ",
107
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) worker 4: <__main__.ExampleProducer object at ",
108
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) requesting workers to stop and awaiting 1 producers...",
109
+ "CONSUMER 0 perform_work JOB 1 DONE",
110
+ "CONSUMER 0 save_result JOB 1",
111
+ "INFO:csu.worker.engine:ExampleProducer(1/WAITING).on_exit(",
112
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) flushing queue (0 items) and awaiting consumers...",
113
+ "INFO:csu.worker.engine:ExampleConsumer(1/SHUTDOWN).on_exit(",
114
+ "INFO:csu.worker.engine:ExampleConsumer(2/SHUTDOWN).on_exit(",
115
+ "INFO:csu.worker.engine:ExampleConsumer(3/SHUTDOWN).on_exit(",
116
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) shutdown complete.",
117
+ "DONE: {'ExampleConsumer': {'EXITED': 3}, 'ExampleProducer': {'EXITED': 1}, 'ExampleEngine': {'JOBS': 3, 'QSIZE': 0, 'SEMVAL': 2}}",
118
+ )
119
+
120
+
121
+ def test_forced_shutdown():
122
+ with TestProcess(sys.executable, "-mexampleworker", "3", "0", "2000", "100") as target, dump_on_error(target.read):
123
+ wait_for_strings(
124
+ target.read,
125
+ 15,
126
+ "PRODUCER fetch_task @ 1 RETURN",
127
+ )
128
+ target.proc.send_signal(signal.SIGINT)
129
+ time.sleep(1)
130
+ target.proc.send_signal(signal.SIGTERM)
131
+ wait_for_strings(
132
+ target.read,
133
+ 15,
134
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).shutdown_handler(SIGINT) GRACEFUL",
135
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).stop(graceful=True)",
136
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q) requesting workers to stop and awaiting 1 producers...",
137
+ "INFO:csu.worker.engine:ExampleEngine(4w/0q).shutdown_handler(SIGTERM) FORCED",
138
+ "CONSUMER 0 perform_work JOB 1 DONE",
139
+ "CONSUMER 1 perform_work JOB 2 DONE",
140
+ "CONSUMER 2 perform_work JOB 3 DONE",
141
+ "DONE: {'ExampleConsumer': {'CANCELED': 3}, 'ExampleProducer': {'CANCELED': 1}, 'ExampleEngine': {'JOBS': 3, 'QSIZE': 0, 'SEMVAL': 0}}",
142
+ )
143
+
144
+
145
+ def test_sem_rollback():
146
+ with TestProcess(sys.executable, "-mexampleworker", "1", "2", "10", "10") as target, dump_on_error(target.read):
147
+ wait_for_strings(
148
+ target.read,
149
+ 15,
150
+ "PRODUCER fetch_task @ 1 RETURN",
151
+ "CONSUMER 0 perform_work JOB 1 START",
152
+ "CONSUMER 0 perform_work JOB 1 DONE",
153
+ "CONSUMER 0 save_result JOB 1",
154
+ "PRODUCER fetch_task @ 2 SKIP",
155
+ "PRODUCER fetch_task @ 3 RETURN",
156
+ "CONSUMER 0 perform_work JOB 3 START",
157
+ "CONSUMER 0 perform_work JOB 3 DONE",
158
+ "CONSUMER 0 save_result JOB 3",
159
+ "PRODUCER fetch_task @ 4 SKIP",
160
+ "PRODUCER fetch_task @ 5 RETURN",
161
+ "CONSUMER 0 perform_work JOB 5 START",
162
+ "CONSUMER 0 perform_work JOB 5 DONE",
163
+ "CONSUMER 0 save_result JOB 5",
164
+ )
@@ -59,4 +59,4 @@ async def test_cancel():
59
59
  with pytest.raises(TimeoutError):
60
60
  await asyncio.wait_for(job1.done, 0.1)
61
61
  result = await asyncio.wait_for(job2.done, 1)
62
- assert result == {"result_type": ResultType.SUCCESS, "result_details": None, "time": 0}
62
+ assert result == {"result_type": ResultType.SUCCESS, "time": 0}
@@ -1,36 +0,0 @@
1
- from asgiref.typing import ASGIReceiveCallable
2
- from asgiref.typing import ASGISendCallable
3
- from asgiref.typing import Scope
4
- from django.core.asgi import get_asgi_application
5
-
6
- from . import logger
7
-
8
-
9
- def get_worker_lifespan_application():
10
- django_application = get_asgi_application()
11
-
12
- async def application_with_worker_lifespan(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
13
- if scope["type"] == "lifespan":
14
- try:
15
- from .registry import REGISTRY # noqa:PLC0415
16
-
17
- while True:
18
- message = await receive()
19
- message_type = message["type"]
20
- if message_type == "lifespan.startup":
21
- logger.info("Found %s registered engines.", len(REGISTRY))
22
- for module_name, engine in REGISTRY.items():
23
- logger.info("Starting engine for %s: %s...", module_name, engine)
24
- await engine.start()
25
- await send({"type": "lifespan.startup.complete"})
26
- if message_type == "lifespan.shutdown":
27
- for engine in REGISTRY.values():
28
- await engine.stop()
29
- await send({"type": "lifespan.shutdown.complete"})
30
- return
31
- except Exception as exc:
32
- logger.exception("Failed setting up lifespan: %r", exc)
33
- else:
34
- await django_application(scope, receive, send)
35
-
36
- return application_with_worker_lifespan
@@ -1,164 +0,0 @@
1
- import signal
2
- import sys
3
- import time
4
-
5
- from process_tests import TestProcess
6
- from process_tests import dump_on_error
7
- from process_tests import wait_for_strings
8
-
9
-
10
- def test_noskip():
11
- with TestProcess(sys.executable, "-mexampleworker", "3", "0", "1000", "4") as target, dump_on_error(target.read):
12
- wait_for_strings(
13
- target.read,
14
- 5,
15
- "INFO:worker:ExampleConsumer(1/UNKNOWN) started.",
16
- "INFO:worker:ExampleConsumer(2/UNKNOWN) started.",
17
- "INFO:worker:ExampleConsumer(3/UNKNOWN) started.",
18
- "INFO:worker:ExampleProducer(1/UNKNOWN) started.",
19
- "INFO:worker:ExampleProducer(1/UNKNOWN).run() started.",
20
- "PRODUCER fetch_task @ 1 RETURN",
21
- "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
22
- "INFO:worker:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
23
- "PRODUCER fetch_task @ 2 RETURN",
24
- "INFO:worker:ExampleEngine(4w/1q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=2))",
25
- "INFO:worker:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=2).done of NoOpTask(id=2).",
26
- "PRODUCER fetch_task @ 3 RETURN",
27
- "INFO:worker:ExampleEngine(4w/2q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
28
- "INFO:worker:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
29
- "INFO:worker:ExampleConsumer(1/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
30
- "INFO:worker:ExampleConsumer(1/WORKING).run() - performing...",
31
- "CONSUMER 0 perform_work JOB 1 START",
32
- "INFO:worker:ExampleConsumer(2/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=2)",
33
- "INFO:worker:ExampleConsumer(2/WORKING).run() - performing...",
34
- "CONSUMER 1 perform_work JOB 2 START",
35
- "INFO:worker:ExampleConsumer(3/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
36
- "INFO:worker:ExampleConsumer(3/WORKING).run() - performing...",
37
- "CONSUMER 2 perform_work JOB 3 START",
38
- "CONSUMER 0 perform_work JOB 1 DONE",
39
- "CONSUMER 0 save_result JOB 1",
40
- "CONSUMER 1 perform_work JOB 2 DONE",
41
- "CONSUMER 1 save_result JOB 2",
42
- "CONSUMER 2 perform_work JOB 3 DONE",
43
- "CONSUMER 2 save_result JOB 3",
44
- "PRODUCER fetch_task @ 4 RETURN",
45
- "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=4))",
46
- "INFO:worker:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=4).done of NoOpTask(id=4).",
47
- "PRODUCER fetch_task @ 5 EXHAUSTED",
48
- "INFO:worker:ExampleConsumer(1/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=4)",
49
- "INFO:worker:ExampleConsumer(1/WORKING).run() - performing...",
50
- "CONSUMER 0 perform_work JOB 4 START",
51
- "PRODUCER fetch_task @ 6 EXHAUSTED",
52
- "CONSUMER 0 perform_work JOB 4 DONE",
53
- "CONSUMER 0 save_result JOB 4",
54
- "PRODUCER fetch_task @ 7 EXHAUSTED",
55
- )
56
-
57
-
58
- def test_skip():
59
- with TestProcess(sys.executable, "-mexampleworker", "3", "2", "100", "4") as target, dump_on_error(target.read):
60
- wait_for_strings(
61
- target.read,
62
- 5,
63
- "INFO:worker:ExampleConsumer(1/UNKNOWN) started.",
64
- "INFO:worker:ExampleConsumer(2/UNKNOWN) started.",
65
- "INFO:worker:ExampleConsumer(3/UNKNOWN) started.",
66
- "INFO:worker:ExampleProducer(1/UNKNOWN) started.",
67
- "INFO:worker:ExampleProducer(1/UNKNOWN).run() started.",
68
- "PRODUCER fetch_task @ 1 RETURN",
69
- "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
70
- "INFO:worker:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
71
- "PRODUCER fetch_task @ 2 SKIP",
72
- "INFO:worker:ExampleConsumer(1/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
73
- "INFO:worker:ExampleConsumer(1/WORKING).run() - performing...",
74
- "CONSUMER 0 perform_work JOB 1 START",
75
- "CONSUMER 0 perform_work JOB 1 DONE",
76
- "CONSUMER 0 save_result JOB 1",
77
- "PRODUCER fetch_task @ 3 RETURN",
78
- "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
79
- "INFO:worker:ExampleProducer(1/WORKING).run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
80
- "PRODUCER fetch_task @ 4 SKIP",
81
- "INFO:worker:ExampleConsumer(2/READY).run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
82
- "INFO:worker:ExampleConsumer(2/WORKING).run() - performing...",
83
- "CONSUMER 1 perform_work JOB 3 START",
84
- "CONSUMER 1 perform_work JOB 3 DONE",
85
- "CONSUMER 1 save_result JOB 3",
86
- "PRODUCER fetch_task @ 5 EXHAUSTED",
87
- )
88
-
89
-
90
- def test_graceful_shutdown():
91
- with TestProcess(sys.executable, "-mexampleworker", "3", "0", "1000", "100") as target, dump_on_error(target.read):
92
- wait_for_strings(
93
- target.read,
94
- 15,
95
- "PRODUCER fetch_task @ 1 RETURN",
96
- )
97
- target.proc.send_signal(signal.SIGINT)
98
- wait_for_strings(
99
- target.read,
100
- 15,
101
- "INFO:worker:ExampleEngine(4w/0q).shutdown_handler(SIGINT) GRACEFUL",
102
- "INFO:worker:ExampleEngine(4w/0q).stop(graceful=True)",
103
- "INFO:worker:ExampleEngine(4w/0q) stats: {'ExampleConsumer': {'WORKING': 3}, 'ExampleProducer': {'WAITING': 1}, 'ExampleEngine': {'JOBS': 3, 'QSIZE': 0, 'SEMVAL': 0}}",
104
- "INFO:worker:ExampleEngine(4w/0q) worker 1: <__main__.ExampleConsumer object at ",
105
- "INFO:worker:ExampleEngine(4w/0q) worker 2: <__main__.ExampleConsumer object at ",
106
- "INFO:worker:ExampleEngine(4w/0q) worker 3: <__main__.ExampleConsumer object at ",
107
- "INFO:worker:ExampleEngine(4w/0q) worker 4: <__main__.ExampleProducer object at ",
108
- "INFO:worker:ExampleEngine(4w/0q) requesting workers to stop and awaiting 1 producers...",
109
- "CONSUMER 0 perform_work JOB 1 DONE",
110
- "CONSUMER 0 save_result JOB 1",
111
- "INFO:worker:ExampleProducer(1/WAITING).on_exit(",
112
- "INFO:worker:ExampleEngine(4w/0q) flushing queue (0 items) and awaiting consumers...",
113
- "INFO:worker:ExampleConsumer(1/SHUTDOWN).on_exit(",
114
- "INFO:worker:ExampleConsumer(2/SHUTDOWN).on_exit(",
115
- "INFO:worker:ExampleConsumer(3/SHUTDOWN).on_exit(",
116
- "INFO:worker:ExampleEngine(4w/0q) shutdown complete.",
117
- "DONE: {'ExampleConsumer': {'EXITED': 3}, 'ExampleProducer': {'EXITED': 1}, 'ExampleEngine': {'JOBS': 3, 'QSIZE': 0, 'SEMVAL': 2}}",
118
- )
119
-
120
-
121
- def test_forced_shutdown():
122
- with TestProcess(sys.executable, "-mexampleworker", "3", "0", "2000", "100") as target, dump_on_error(target.read):
123
- wait_for_strings(
124
- target.read,
125
- 15,
126
- "PRODUCER fetch_task @ 1 RETURN",
127
- )
128
- target.proc.send_signal(signal.SIGINT)
129
- time.sleep(1)
130
- target.proc.send_signal(signal.SIGTERM)
131
- wait_for_strings(
132
- target.read,
133
- 15,
134
- "INFO:worker:ExampleEngine(4w/0q).shutdown_handler(SIGINT) GRACEFUL",
135
- "INFO:worker:ExampleEngine(4w/0q).stop(graceful=True)",
136
- "INFO:worker:ExampleEngine(4w/0q) requesting workers to stop and awaiting 1 producers...",
137
- "INFO:worker:ExampleEngine(4w/0q).shutdown_handler(SIGTERM) FORCED",
138
- "CONSUMER 0 perform_work JOB 1 DONE",
139
- "CONSUMER 1 perform_work JOB 2 DONE",
140
- "CONSUMER 2 perform_work JOB 3 DONE",
141
- "DONE: {'ExampleConsumer': {'CANCELED': 3}, 'ExampleProducer': {'CANCELED': 1}, 'ExampleEngine': {'JOBS': 3, 'QSIZE': 0, 'SEMVAL': 0}}",
142
- )
143
-
144
-
145
- def test_sem_rollback():
146
- with TestProcess(sys.executable, "-mexampleworker", "1", "2", "10", "10") as target, dump_on_error(target.read):
147
- wait_for_strings(
148
- target.read,
149
- 15,
150
- "PRODUCER fetch_task @ 1 RETURN",
151
- "CONSUMER 0 perform_work JOB 1 START",
152
- "CONSUMER 0 perform_work JOB 1 DONE",
153
- "CONSUMER 0 save_result JOB 1",
154
- "PRODUCER fetch_task @ 2 SKIP",
155
- "PRODUCER fetch_task @ 3 RETURN",
156
- "CONSUMER 0 perform_work JOB 3 START",
157
- "CONSUMER 0 perform_work JOB 3 DONE",
158
- "CONSUMER 0 save_result JOB 3",
159
- "PRODUCER fetch_task @ 4 SKIP",
160
- "PRODUCER fetch_task @ 5 RETURN",
161
- "CONSUMER 0 perform_work JOB 5 START",
162
- "CONSUMER 0 perform_work JOB 5 DONE",
163
- "CONSUMER 0 save_result JOB 5",
164
- )
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
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