csu 2.1.4__tar.gz → 2.2.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.
- {csu-2.1.4 → csu-2.2.0}/.bumpversion.cfg +1 -1
- {csu-2.1.4 → csu-2.2.0}/.cookiecutterrc +1 -1
- {csu-2.1.4 → csu-2.2.0}/PKG-INFO +3 -3
- {csu-2.1.4 → csu-2.2.0}/README.rst +2 -2
- {csu-2.1.4 → csu-2.2.0}/pyproject.toml +1 -1
- {csu-2.1.4 → csu-2.2.0}/src/csu/__init__.py +1 -1
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/engine.py +57 -50
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/enums.py +1 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu.egg-info/PKG-INFO +3 -3
- {csu-2.1.4 → csu-2.2.0}/tests/exampleworker.py +11 -10
- csu-2.2.0/tests/test_exampleworker.py +143 -0
- csu-2.1.4/tests/test_exampleworker.py +0 -87
- {csu-2.1.4 → csu-2.2.0}/.coveragerc +0 -0
- {csu-2.1.4 → csu-2.2.0}/.editorconfig +0 -0
- {csu-2.1.4 → csu-2.2.0}/.github/workflows/github-actions.yml +0 -0
- {csu-2.1.4 → csu-2.2.0}/.pre-commit-config.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/.taplo.toml +0 -0
- {csu-2.1.4 → csu-2.2.0}/AUTHORS.rst +0 -0
- {csu-2.1.4 → csu-2.2.0}/CHANGELOG.rst +0 -0
- {csu-2.1.4 → csu-2.2.0}/CONTRIBUTING.rst +0 -0
- {csu-2.1.4 → csu-2.2.0}/LICENSE +0 -0
- {csu-2.1.4 → csu-2.2.0}/MANIFEST.in +0 -0
- {csu-2.1.4 → csu-2.2.0}/ci/bootstrap.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/ci/requirements.txt +0 -0
- {csu-2.1.4 → csu-2.2.0}/ci/templates/.github/workflows/github-actions.yml +0 -0
- {csu-2.1.4 → csu-2.2.0}/pytest.ini +0 -0
- {csu-2.1.4 → csu-2.2.0}/setup.cfg +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/admin.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/conf.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/consts.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/__init__.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/auth.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/fields.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/forms.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/perms.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/phonenumber.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/serializers.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/test_auth.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/test_fields.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/test_forms.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/test_phonenumber.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/test_serializers.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/drf/views.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/enums.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/env.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/exceptions.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/export.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/fixups.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/forms/__init__.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/forms/crispy.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/forms/fields.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/gettext.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/gettext_lazy.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/logging.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/management.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/models.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/query.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/routers.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/service.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/templates/api_exception.html +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_consts.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_env.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_exceptions.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_logging.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_management.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_service.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_timezones.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_utils.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/test_xml.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/timezones.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/utils.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/views.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/__init__.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/admin.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/asgi.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/job.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/models.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/worker/registry.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/wsgi.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu/xml.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu.egg-info/SOURCES.txt +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu.egg-info/dependency_links.txt +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu.egg-info/requires.txt +0 -0
- {csu-2.1.4 → csu-2.2.0}/src/csu.egg-info/top_level.txt +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service/test_decoding.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service/test_error.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service/test_logging.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service/test_redirects.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/conftest.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/test_models.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/test_service.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/test_service_auth.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/test_views.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/test_worker.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/testproject/__init__.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/testproject/fixtures/testuser.json +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/testproject/models.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/testproject/settings.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tests/testproject/urls.py +0 -0
- {csu-2.1.4 → csu-2.2.0}/tox.ini +0 -0
{csu-2.1.4 → csu-2.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csu
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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.
|
|
80
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.2.0.svg
|
|
81
81
|
:alt: Commits since latest release
|
|
82
|
-
:target: https://github.com/ionelmc/python-csu/compare/v2.
|
|
82
|
+
:target: https://github.com/ionelmc/python-csu/compare/v2.2.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.
|
|
37
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.2.0.svg
|
|
38
38
|
:alt: Commits since latest release
|
|
39
|
-
:target: https://github.com/ionelmc/python-csu/compare/v2.
|
|
39
|
+
:target: https://github.com/ionelmc/python-csu/compare/v2.2.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/
|
|
@@ -4,11 +4,12 @@ import traceback
|
|
|
4
4
|
from abc import ABC
|
|
5
5
|
from abc import abstractmethod
|
|
6
6
|
from asyncio import CancelledError
|
|
7
|
-
from asyncio import Event
|
|
8
7
|
from asyncio import Future
|
|
9
8
|
from asyncio import Queue
|
|
9
|
+
from asyncio import Semaphore
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from collections.abc import AsyncIterator
|
|
12
|
+
from collections.abc import Iterable
|
|
12
13
|
from contextlib import asynccontextmanager
|
|
13
14
|
from datetime import datetime
|
|
14
15
|
from datetime import time
|
|
@@ -17,6 +18,7 @@ from enum import Flag
|
|
|
17
18
|
from enum import auto
|
|
18
19
|
from functools import partial
|
|
19
20
|
from random import randint
|
|
21
|
+
from typing import ClassVar
|
|
20
22
|
from typing import Protocol
|
|
21
23
|
|
|
22
24
|
from datetimerange import DateTimeRange
|
|
@@ -46,17 +48,19 @@ class AbstractWorker(ABC):
|
|
|
46
48
|
state: WorkerState = WorkerState.UNKNOWN
|
|
47
49
|
runner: asyncio.Task | None = None
|
|
48
50
|
shutdown_requested = False
|
|
51
|
+
id_counter: ClassVar = 0
|
|
52
|
+
id = "?"
|
|
49
53
|
|
|
50
54
|
def __str__(self):
|
|
51
|
-
return f"{type(self).__name__}({self.state.name})"
|
|
52
|
-
|
|
53
|
-
def __repr__(self):
|
|
54
|
-
return f"{type(self).__name__}({id(self)}, state={self.state.name})"
|
|
55
|
+
return f"{type(self).__name__}({self.id}/{self.state.name})"
|
|
55
56
|
|
|
56
57
|
def report(self, inspect: defaultdict[str, int]):
|
|
57
58
|
inspect[self.state.name] += 1
|
|
58
59
|
|
|
59
60
|
def start(self):
|
|
61
|
+
cls = type(self)
|
|
62
|
+
cls.id_counter += 1
|
|
63
|
+
self.id = cls.id_counter
|
|
60
64
|
self.runner = asyncio.create_task(self.run())
|
|
61
65
|
# noinspection PyTypeChecker
|
|
62
66
|
self.runner.add_done_callback(self.on_exit)
|
|
@@ -113,18 +117,6 @@ class SleepingTimeRangeMixin:
|
|
|
113
117
|
await asyncio.sleep(delta)
|
|
114
118
|
|
|
115
119
|
|
|
116
|
-
class NoPrefetchingProducerMixin:
|
|
117
|
-
state: WorkerState
|
|
118
|
-
engine: AbstractEngine
|
|
119
|
-
sleeping_timerange: tuple[time, time]
|
|
120
|
-
|
|
121
|
-
async def before(self):
|
|
122
|
-
# noinspection PyUnresolvedReferences
|
|
123
|
-
await super().before()
|
|
124
|
-
self.state = WorkerState.WAITING
|
|
125
|
-
await self.engine.wait_worker_ready()
|
|
126
|
-
|
|
127
|
-
|
|
128
120
|
class SleepingCooldown(Flag):
|
|
129
121
|
IDLE = auto()
|
|
130
122
|
BUSY = auto()
|
|
@@ -172,15 +164,16 @@ class AbstractProducer(AbstractWorker):
|
|
|
172
164
|
def __init__(self, engine: AbstractEngine):
|
|
173
165
|
self.engine = engine
|
|
174
166
|
|
|
175
|
-
def __str__(self):
|
|
176
|
-
return f"{type(self).__name__}()"
|
|
177
|
-
|
|
178
167
|
async def run(self):
|
|
179
168
|
task: TaskProtocol | None
|
|
180
169
|
logger.info("%s.run() started.", self)
|
|
181
170
|
while True:
|
|
182
171
|
self.state = WorkerState.BEFORE
|
|
183
172
|
await self.before()
|
|
173
|
+
if self.shutdown_requested:
|
|
174
|
+
break
|
|
175
|
+
self.state = WorkerState.WAITING
|
|
176
|
+
await self.engine.wait_worker_ready()
|
|
184
177
|
if self.shutdown_requested:
|
|
185
178
|
break
|
|
186
179
|
try:
|
|
@@ -197,6 +190,8 @@ class AbstractProducer(AbstractWorker):
|
|
|
197
190
|
await self.engine.push(job := self.job_class(**task.job_kwargs))
|
|
198
191
|
logger.info("%s.run() awaiting %s.done of %s.", self, job, task)
|
|
199
192
|
await task.done_watchdog(job)
|
|
193
|
+
if self.shutdown_requested:
|
|
194
|
+
break
|
|
200
195
|
self.state = WorkerState.AFTER
|
|
201
196
|
await self.after(idle=not task)
|
|
202
197
|
|
|
@@ -216,15 +211,14 @@ class AbstractConsumer(AbstractWorker):
|
|
|
216
211
|
def __init__(self, engine: AbstractEngine):
|
|
217
212
|
self.engine = engine
|
|
218
213
|
|
|
219
|
-
def __str__(self):
|
|
220
|
-
return f"{type(self).__name__}({id(self)})"
|
|
221
|
-
|
|
222
214
|
async def run(self):
|
|
223
215
|
while True:
|
|
224
|
-
if
|
|
216
|
+
if self.shutdown_requested:
|
|
217
|
+
self.state = WorkerState.SHUTDOWN
|
|
218
|
+
else:
|
|
225
219
|
self.state = WorkerState.BEFORE
|
|
226
220
|
await self.before()
|
|
227
|
-
|
|
221
|
+
self.state = WorkerState.READY
|
|
228
222
|
job: Job | None
|
|
229
223
|
async with self.engine.consume() as job:
|
|
230
224
|
if job is None:
|
|
@@ -260,8 +254,9 @@ class AbstractConsumer(AbstractWorker):
|
|
|
260
254
|
|
|
261
255
|
if self.shutdown_requested:
|
|
262
256
|
continue
|
|
263
|
-
|
|
264
|
-
|
|
257
|
+
else:
|
|
258
|
+
self.state = WorkerState.AFTER
|
|
259
|
+
await self.after(idle=idle)
|
|
265
260
|
|
|
266
261
|
@abstractmethod
|
|
267
262
|
async def perform_work(self, job: Job) -> dict:
|
|
@@ -271,15 +266,13 @@ class AbstractConsumer(AbstractWorker):
|
|
|
271
266
|
class AbstractEngine(ABC):
|
|
272
267
|
workers: list[AbstractWorker]
|
|
273
268
|
queue: Queue = None
|
|
274
|
-
|
|
275
|
-
|
|
269
|
+
queue_sem: Semaphore
|
|
270
|
+
shutdown_on: Iterable[int] = (signal.SIGINT, signal.SIGTERM)
|
|
276
271
|
shutdown_requested = False
|
|
277
|
-
|
|
272
|
+
shutdown_tasks: ClassVar[dict[bool, asyncio.Task]] = {}
|
|
278
273
|
|
|
279
274
|
def __init__(self):
|
|
280
275
|
self.workers = []
|
|
281
|
-
self.queue_awaited = Event()
|
|
282
|
-
self.queue_maxsize = 0
|
|
283
276
|
|
|
284
277
|
def __str__(self):
|
|
285
278
|
return f"{type(self).__name__}({len(self.workers)}w/{self.queue.qsize() if self.queue else '-'}q)"
|
|
@@ -293,35 +286,43 @@ class AbstractEngine(ABC):
|
|
|
293
286
|
|
|
294
287
|
async def push(self, job: Job | None) -> None:
|
|
295
288
|
logger.info("%s.push(%s)", self, job)
|
|
296
|
-
self.queue_awaited.clear() # clear it here, it's the shortest path to "consumer wakes up and calls .set()"
|
|
297
289
|
await self.queue.put(job)
|
|
298
290
|
|
|
299
291
|
@asynccontextmanager
|
|
300
292
|
async def consume(self) -> AsyncIterator[Job | None]:
|
|
301
|
-
self.
|
|
293
|
+
self.queue_sem.release()
|
|
302
294
|
yield await self.queue.get()
|
|
303
|
-
# if raised then there's no task to mark as done
|
|
295
|
+
# if raised then there's no task to mark as done (this won't be reached on exception)
|
|
304
296
|
self.queue.task_done()
|
|
305
297
|
|
|
306
298
|
async def wait_worker_ready(self):
|
|
307
299
|
# event set after consuming, cleared right before there's a task
|
|
308
|
-
await self.
|
|
300
|
+
await self.queue_sem.acquire()
|
|
309
301
|
|
|
310
302
|
def add_worker(self, worker: AbstractWorker):
|
|
311
|
-
assert self.queue is None, f"{self} is already started"
|
|
312
|
-
if isinstance(worker, AbstractConsumer):
|
|
313
|
-
self.queue_maxsize += 1
|
|
303
|
+
assert self.queue is None, f"{self} is already started, {self.queue=}"
|
|
314
304
|
self.workers.append(worker)
|
|
315
305
|
|
|
316
306
|
async def start(self):
|
|
317
|
-
self.queue
|
|
307
|
+
assert self.queue is None, f"{self} is already started, {self.queue=}"
|
|
308
|
+
self.queue = Queue()
|
|
309
|
+
self.queue_sem = Semaphore(value=0)
|
|
318
310
|
for worker in self.workers:
|
|
319
311
|
assert worker.runner is None, f"{worker=} should not be started"
|
|
320
312
|
worker.start()
|
|
321
|
-
|
|
313
|
+
for sig in self.shutdown_on:
|
|
314
|
+
signal.signal(sig, self.shutdown_handler)
|
|
322
315
|
|
|
323
316
|
def shutdown_handler(self, sig, frame):
|
|
324
|
-
|
|
317
|
+
sig_name = signal.Signals(sig).name
|
|
318
|
+
graceful = not self.shutdown_requested
|
|
319
|
+
logger.info("%s.shutdown_handler(%s) %s", self, sig_name, "GRACEFUL" if graceful else "FORCED")
|
|
320
|
+
if not graceful:
|
|
321
|
+
self.shutdown_tasks[True].cancel()
|
|
322
|
+
if graceful in self.shutdown_tasks:
|
|
323
|
+
logger.warn("%s.shutdown_handler(%s) already requested", self, sig_name)
|
|
324
|
+
else:
|
|
325
|
+
self.shutdown_tasks[graceful] = asyncio.create_task(self.stop(graceful=graceful))
|
|
325
326
|
self.shutdown_requested = True
|
|
326
327
|
|
|
327
328
|
async def stop(self, graceful=False):
|
|
@@ -330,16 +331,19 @@ class AbstractEngine(ABC):
|
|
|
330
331
|
for i, worker in enumerate(self.workers, 1):
|
|
331
332
|
logger.info("%s worker %s: %r", self, i, worker)
|
|
332
333
|
if graceful:
|
|
333
|
-
logger.info("%s waiting producers to stop...", self)
|
|
334
|
-
producers = [worker for worker in self.workers if isinstance(worker, AbstractProducer)]
|
|
335
|
-
for producer in producers:
|
|
336
|
-
producer.shutdown_requested = True
|
|
337
|
-
await asyncio.gather(*[producer.runner for producer in producers], return_exceptions=True)
|
|
338
|
-
logger.info("%s requesting all workers to shutdown...", self)
|
|
339
334
|
for worker in self.workers:
|
|
340
335
|
worker.shutdown_requested = True
|
|
336
|
+
|
|
337
|
+
producers = [worker.runner for worker in self.workers if isinstance(worker, AbstractProducer)]
|
|
338
|
+
logger.info("%s requesting workers to stop and awaiting %s producers...", self, len(producers))
|
|
339
|
+
await asyncio.gather(
|
|
340
|
+
*producers,
|
|
341
|
+
return_exceptions=True,
|
|
342
|
+
)
|
|
343
|
+
logger.info("%s flushing queue (%s items) and awaiting consumers...", self, self.queue.qsize())
|
|
344
|
+
for worker in self.workers:
|
|
341
345
|
if isinstance(worker, AbstractConsumer):
|
|
342
|
-
|
|
346
|
+
self.queue.put_nowait(None)
|
|
343
347
|
else:
|
|
344
348
|
while self.queue.qsize() > 0:
|
|
345
349
|
item = self.queue.get_nowait()
|
|
@@ -352,7 +356,10 @@ class AbstractEngine(ABC):
|
|
|
352
356
|
logger.info("%s shutdown complete.", self)
|
|
353
357
|
|
|
354
358
|
def report(self, inspect: defaultdict[str, object]):
|
|
355
|
-
inspect
|
|
359
|
+
inspect.update(
|
|
360
|
+
QSIZE="UNSTARTED" if self.queue is None else self.queue.qsize(),
|
|
361
|
+
SEMVAL=self.queue_sem._value,
|
|
362
|
+
)
|
|
356
363
|
|
|
357
364
|
def inspect(self) -> dict[str, dict[str, int]]:
|
|
358
365
|
result = defaultdict(partial(defaultdict, int))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csu
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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.
|
|
80
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.2.0.svg
|
|
81
81
|
:alt: Commits since latest release
|
|
82
|
-
:target: https://github.com/ionelmc/python-csu/compare/v2.
|
|
82
|
+
:target: https://github.com/ionelmc/python-csu/compare/v2.2.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/
|
|
@@ -9,7 +9,6 @@ from logging import getLogger
|
|
|
9
9
|
from csu.worker.engine import AbstractConsumer
|
|
10
10
|
from csu.worker.engine import AbstractEngine
|
|
11
11
|
from csu.worker.engine import AbstractProducer
|
|
12
|
-
from csu.worker.engine import NoPrefetchingProducerMixin
|
|
13
12
|
from csu.worker.engine import SleepingCooldown
|
|
14
13
|
from csu.worker.engine import SleepingCooldownMixin
|
|
15
14
|
from csu.worker.engine import TaskProtocol
|
|
@@ -48,14 +47,7 @@ class ExampleResultType(StrEnum):
|
|
|
48
47
|
ABSENT = "ABSENT"
|
|
49
48
|
|
|
50
49
|
|
|
51
|
-
if sys.argv[1] == "prefetch":
|
|
52
|
-
|
|
53
|
-
class NoPrefetchingProducerMixin:
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
|
|
57
50
|
class ExampleProducer(
|
|
58
|
-
NoPrefetchingProducerMixin,
|
|
59
51
|
SleepingCooldownMixin,
|
|
60
52
|
AbstractProducer,
|
|
61
53
|
):
|
|
@@ -104,7 +96,7 @@ class ExampleEngine(AbstractEngine):
|
|
|
104
96
|
self.producer_skip = int(producer_skip)
|
|
105
97
|
self.consumer_sleep = int(consumer_sleep) / 1000
|
|
106
98
|
self.max_jobs = int(max_jobs)
|
|
107
|
-
|
|
99
|
+
self.job_counter = 0
|
|
108
100
|
|
|
109
101
|
async def start(self):
|
|
110
102
|
for i in range(self.consumers):
|
|
@@ -119,8 +111,17 @@ class ExampleEngine(AbstractEngine):
|
|
|
119
111
|
print(self.inspect())
|
|
120
112
|
print(f"DONE: {self.inspect()}")
|
|
121
113
|
|
|
114
|
+
async def push(self, job: Job | None) -> None:
|
|
115
|
+
self.job_counter += 1
|
|
116
|
+
await super().push(job)
|
|
117
|
+
|
|
118
|
+
def report(self, inspect):
|
|
119
|
+
super().report(inspect)
|
|
120
|
+
inspect["JOBS"] = self.job_counter
|
|
121
|
+
|
|
122
122
|
|
|
123
123
|
if __name__ == "__main__":
|
|
124
|
+
print("+", " ".join(sys.argv))
|
|
124
125
|
logging.basicConfig(level=logging.DEBUG)
|
|
125
|
-
engine = ExampleEngine(*sys.argv[
|
|
126
|
+
engine = ExampleEngine(*sys.argv[1:])
|
|
126
127
|
asyncio.run(engine.start())
|
|
@@ -0,0 +1,143 @@
|
|
|
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(<Task finished name='Task-5' coro=<AbstractProducer.run() done, defined at /home/ionel/open-source/python-csu/src/csu/worker/engine.py:167> result=None>) exit with result: None",
|
|
112
|
+
"INFO:worker:ExampleEngine(4w/0q) flushing queue (0 items) and awaiting consumers...",
|
|
113
|
+
"INFO:worker:ExampleConsumer(1/SHUTDOWN).on_exit(<Task finished name='Task-2' coro=<AbstractConsumer.run() done, defined at /home/ionel/open-source/python-csu/src/csu/worker/engine.py:214> result=None>) exit with result: None",
|
|
114
|
+
"INFO:worker:ExampleConsumer(2/SHUTDOWN).on_exit(<Task finished name='Task-3' coro=<AbstractConsumer.run() done, defined at /home/ionel/open-source/python-csu/src/csu/worker/engine.py:214> result=None>) exit with result: None",
|
|
115
|
+
"INFO:worker:ExampleConsumer(3/SHUTDOWN).on_exit(<Task finished name='Task-4' coro=<AbstractConsumer.run() done, defined at /home/ionel/open-source/python-csu/src/csu/worker/engine.py:214> result=None>) exit with result: None",
|
|
116
|
+
"INFO:worker:ExampleEngine(0w/0q) shutdown complete.",
|
|
117
|
+
"DONE: {'ExampleEngine':",
|
|
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
|
+
"INFO:worker:ExampleEngine(0w/0q) shutdown complete.",
|
|
143
|
+
)
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
|
-
from process_tests import TestProcess
|
|
4
|
-
from process_tests import dump_on_error
|
|
5
|
-
from process_tests import wait_for_strings
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_noprefetch_noskip():
|
|
9
|
-
with TestProcess(sys.executable, "-mexampleworker", "nope", "3", "0", "1000", "4") as target, dump_on_error(target.read):
|
|
10
|
-
wait_for_strings(
|
|
11
|
-
target.read,
|
|
12
|
-
5,
|
|
13
|
-
"PRODUCER fetch_task @ 1 RETURN",
|
|
14
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
|
|
15
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
|
|
16
|
-
"working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
|
|
17
|
-
"- performing...",
|
|
18
|
-
"CONSUMER 0 perform_work JOB 1 START",
|
|
19
|
-
"CONSUMER 0 perform_work JOB 1 DONE",
|
|
20
|
-
"CONSUMER 0 save_result JOB 1",
|
|
21
|
-
"PRODUCER fetch_task @ 2 RETURN",
|
|
22
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=2))",
|
|
23
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=2).done of NoOpTask(id=2).",
|
|
24
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=2)",
|
|
25
|
-
").run() - performing...",
|
|
26
|
-
"CONSUMER 1 perform_work JOB 2 START",
|
|
27
|
-
"CONSUMER 1 perform_work JOB 2 DONE",
|
|
28
|
-
"CONSUMER 1 save_result JOB 2",
|
|
29
|
-
"PRODUCER fetch_task @ 3 RETURN",
|
|
30
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
|
|
31
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
|
|
32
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
|
|
33
|
-
").run() - performing...",
|
|
34
|
-
"CONSUMER 2 perform_work JOB 3 START",
|
|
35
|
-
"CONSUMER 2 perform_work JOB 3 DONE",
|
|
36
|
-
"CONSUMER 2 save_result JOB 3",
|
|
37
|
-
"PRODUCER fetch_task @ 4 RETURN",
|
|
38
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=4))",
|
|
39
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=4).done of NoOpTask(id=4).",
|
|
40
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=4)",
|
|
41
|
-
").run() - performing...",
|
|
42
|
-
"CONSUMER 0 perform_work JOB 4 START",
|
|
43
|
-
"CONSUMER 0 perform_work JOB 4 DONE",
|
|
44
|
-
"CONSUMER 0 save_result JOB 4",
|
|
45
|
-
"EXHAUSTED",
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_prefetch_skip():
|
|
50
|
-
with TestProcess(sys.executable, "-mexampleworker", "nope", "3", "0", "100", "4") as target, dump_on_error(target.read):
|
|
51
|
-
wait_for_strings(
|
|
52
|
-
target.read,
|
|
53
|
-
5,
|
|
54
|
-
"PRODUCER fetch_task @ 1 RETURN",
|
|
55
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
|
|
56
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
|
|
57
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
|
|
58
|
-
").run() - performing...",
|
|
59
|
-
"CONSUMER 0 perform_work JOB 1 START",
|
|
60
|
-
"CONSUMER 0 perform_work JOB 1 DONE",
|
|
61
|
-
"CONSUMER 0 save_result JOB 1",
|
|
62
|
-
"PRODUCER fetch_task @ 2 RETURN",
|
|
63
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=2))",
|
|
64
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=2).done of NoOpTask(id=2).",
|
|
65
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=2)",
|
|
66
|
-
").run() - performing...",
|
|
67
|
-
"CONSUMER 1 perform_work JOB 2 START",
|
|
68
|
-
"CONSUMER 1 perform_work JOB 2 DONE",
|
|
69
|
-
"CONSUMER 1 save_result JOB 2",
|
|
70
|
-
"PRODUCER fetch_task @ 3 RETURN",
|
|
71
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
|
|
72
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
|
|
73
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
|
|
74
|
-
").run() - performing...",
|
|
75
|
-
"CONSUMER 2 perform_work JOB 3 START",
|
|
76
|
-
"CONSUMER 2 perform_work JOB 3 DONE",
|
|
77
|
-
"CONSUMER 2 save_result JOB 3",
|
|
78
|
-
"PRODUCER fetch_task @ 4 RETURN",
|
|
79
|
-
"INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=4))",
|
|
80
|
-
"INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=4).done of NoOpTask(id=4).",
|
|
81
|
-
").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=4)",
|
|
82
|
-
").run() - performing...",
|
|
83
|
-
"CONSUMER 0 perform_work JOB 4 START",
|
|
84
|
-
"CONSUMER 0 perform_work JOB 4 DONE",
|
|
85
|
-
"CONSUMER 0 save_result JOB 4",
|
|
86
|
-
"PRODUCER fetch_task @ 5 EXHAUSTED",
|
|
87
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csu-2.1.4 → csu-2.2.0}/LICENSE
RENAMED
|
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
|
|
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
|
{csu-2.1.4 → csu-2.2.0}/tox.ini
RENAMED
|
File without changes
|