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