DefStream 0.1.24__110-py3-none-any.whl

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 (55) hide show
  1. DefStream/Broker/Configuration.py +17 -0
  2. DefStream/Broker/Connection.py +141 -0
  3. DefStream/Broker/Consumer.py +59 -0
  4. DefStream/Broker/Errors.py +14 -0
  5. DefStream/Broker/Event.py +35 -0
  6. DefStream/Broker/Implements/__init__.py +1 -0
  7. DefStream/Broker/Priority.py +26 -0
  8. DefStream/Broker/Protocol.py +54 -0
  9. DefStream/Broker/__init__.py +55 -0
  10. DefStream/Descriptor/DataObject.py +409 -0
  11. DefStream/Descriptor/Types.py +248 -0
  12. DefStream/Descriptor/Value.py +188 -0
  13. DefStream/Descriptor/__init__.py +172 -0
  14. DefStream/Inspector/Extension.py +69 -0
  15. DefStream/Inspector/Extensions/Metrics.py +316 -0
  16. DefStream/Inspector/Extensions/Ping.py +107 -0
  17. DefStream/Inspector/Extensions/Play.html +373 -0
  18. DefStream/Inspector/Extensions/Play.py +415 -0
  19. DefStream/Inspector/Extensions/Specifications.py +176 -0
  20. DefStream/Inspector/Extensions/Status.html +206 -0
  21. DefStream/Inspector/Extensions/Status.py +124 -0
  22. DefStream/Inspector/Extensions/__init__.py +13 -0
  23. DefStream/Inspector/Inspector.py +357 -0
  24. DefStream/Inspector/Response.py +14 -0
  25. DefStream/Inspector/__init__.py +9 -0
  26. DefStream/Validator/Pattern.py +36 -0
  27. DefStream/Validator/Patterns/Array.py +30 -0
  28. DefStream/Validator/Patterns/Common.py +92 -0
  29. DefStream/Validator/Patterns/Compare.py +180 -0
  30. DefStream/Validator/Patterns/Condition.py +60 -0
  31. DefStream/Validator/Patterns/DataObject.py +93 -0
  32. DefStream/Validator/Patterns/DateTime.py +111 -0
  33. DefStream/Validator/Patterns/Number.py +43 -0
  34. DefStream/Validator/Patterns/Object.py +158 -0
  35. DefStream/Validator/Patterns/Size.py +110 -0
  36. DefStream/Validator/Patterns/String.py +92 -0
  37. DefStream/Validator/Patterns/Type.py +355 -0
  38. DefStream/Validator/Patterns/__init__.py +137 -0
  39. DefStream/Validator/Validate.py +29 -0
  40. DefStream/Validator/Validator.py +30 -0
  41. DefStream/Validator/__init__.py +11 -0
  42. DefStream/Worker/Executor.py +525 -0
  43. DefStream/Worker/Message.py +180 -0
  44. DefStream/Worker/Pool.py +282 -0
  45. DefStream/Worker/Run.py +357 -0
  46. DefStream/Worker/Specification.py +87 -0
  47. DefStream/Worker/Startup.py +174 -0
  48. DefStream/Worker/Worker.py +571 -0
  49. DefStream/Worker/__init__.py +47 -0
  50. DefStream/__init__.py +0 -0
  51. defstream-0.1.24.dist-info/METADATA +94 -0
  52. defstream-0.1.24.dist-info/RECORD +55 -0
  53. defstream-0.1.24.dist-info/WHEEL +6 -0
  54. defstream-0.1.24.dist-info/licenses/LICENSE +674 -0
  55. defstream-0.1.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,137 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # COMMON
4
+ from .Common import (
5
+ SetDefault,
6
+ IsOptional,
7
+ IsNotToNone,
8
+ IsNotEmpty,
9
+ )
10
+
11
+ # SIZE
12
+ from .Size import (
13
+ IsSizeOf,
14
+ IsSizeIn,
15
+ IsMinSizeOf,
16
+ IsMaxSizeOf,
17
+ )
18
+
19
+ # COMPARE
20
+ from .Compare import (
21
+ IsEqualTo,
22
+ IsNotEqualTo,
23
+ IsGreaterThan,
24
+ IsGreaterEqualTo,
25
+ IsLessEqualTo,
26
+ IsLessThan,
27
+ IsIn,
28
+ IsNotIn,
29
+ )
30
+
31
+ # CONDITION
32
+ from .Condition import (
33
+ All,
34
+ Any,
35
+ )
36
+
37
+ # TYPE
38
+ from .Type import (
39
+ IsTypeOf,
40
+ IsBool,
41
+ IsInteger,
42
+ IsFloat,
43
+ IsNumber,
44
+ IsString,
45
+ IsList,
46
+ IsTuple,
47
+ IsSet,
48
+ IsArray,
49
+ IsObject,
50
+ IsByteString,
51
+ IsByteArray,
52
+ IsDecimal,
53
+ IsDataObject,
54
+ ToTypeOf,
55
+ ToBool,
56
+ ToInteger,
57
+ ToFloat,
58
+ ToString,
59
+ ToList,
60
+ ToTuple,
61
+ ToSet,
62
+ ToObject,
63
+ ToByteString,
64
+ ToByteArray,
65
+ ToDecimal,
66
+ ToDataObject,
67
+ )
68
+ from .DateTime import (
69
+ IsDateTime,
70
+ IsDate,
71
+ IsTime,
72
+ ToDateTime,
73
+ ToDate,
74
+ ToTime,
75
+ )
76
+
77
+
78
+ __all__ = (
79
+ # COMMON
80
+ 'SetDefault',
81
+ 'IsOptional',
82
+ 'IsNotToNone',
83
+ 'IsNotEmpty',
84
+ # SIZE
85
+ 'IsSizeOf',
86
+ 'IsSizeIn',
87
+ 'IsMinSizeOf',
88
+ 'IsMaxSizeOf',
89
+ # COMPARE
90
+ 'IsEqualTo',
91
+ 'IsNotEqualTo',
92
+ 'IsGreaterThan',
93
+ 'IsGreaterEqualTo',
94
+ 'IsLessEqualTo',
95
+ 'IsLessThan',
96
+ 'IsIn',
97
+ 'IsNotIn',
98
+ # CONDITION
99
+ 'All',
100
+ 'Any',
101
+ # TYPE
102
+ 'IsTypeOf',
103
+ 'IsBool',
104
+ 'IsInteger',
105
+ 'IsFloat',
106
+ 'IsNumber',
107
+ 'IsString',
108
+ 'IsList',
109
+ 'IsTuple',
110
+ 'IsSet',
111
+ 'IsArray',
112
+ 'IsObject',
113
+ 'IsByteString',
114
+ 'IsByteArray',
115
+ 'IsDecimal',
116
+ 'IsDataObject',
117
+ 'ToTypeOf',
118
+ 'ToBool',
119
+ 'ToInteger',
120
+ 'ToFloat',
121
+ 'ToString',
122
+ 'ToList',
123
+ 'ToTuple',
124
+ 'ToSet',
125
+ 'ToObject',
126
+ 'ToByteString',
127
+ 'ToByteArray',
128
+ 'ToDecimal',
129
+ 'ToDataObject',
130
+ # DateTime
131
+ 'IsDateTime',
132
+ 'IsDate',
133
+ 'IsTime',
134
+ 'ToDateTime',
135
+ 'ToDate',
136
+ 'ToTime',
137
+ )
@@ -0,0 +1,29 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .Validator import Validator
4
+ from .Patterns import IsObject
5
+ from .Patterns.Object import IsMappingOf
6
+
7
+ from functools import wraps
8
+ from inspect import signature
9
+
10
+ __all__ = (
11
+ 'Validate',
12
+ )
13
+
14
+
15
+ class Validate(object):
16
+ """Decorator for Validator of Function and Method"""
17
+ def __init__(self, mappings: dict):
18
+ self.mappings = mappings
19
+ return
20
+
21
+ def __call__(self, fn: callable):
22
+ @wraps(fn)
23
+ def wrapper(*args, **kwargs):
24
+ validator = Validator(IsObject(IsMappingOf(self.mappings)))
25
+ bound = signature(fn).bind(*args, **kwargs)
26
+ bound.apply_defaults()
27
+ bound.arguments = validator(dict(bound.arguments))
28
+ return fn(*bound.args, **bound.kwargs)
29
+ return wrapper
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .Pattern import Pattern
4
+
5
+ __all__ = (
6
+ 'Validator',
7
+ )
8
+
9
+
10
+ class Validator(object):
11
+ """
12
+ Validator Class
13
+ """
14
+ def __init__(self, *args):
15
+ for pattern in args:
16
+ if not isinstance(pattern, Pattern):
17
+ raise RuntimeError('{} must be based {}'.format(pattern, Pattern))
18
+ self.patterns = args
19
+ return
20
+
21
+ def __call__(self, parameter):
22
+ for pattern in self.patterns:
23
+ parameter = pattern(parameter)
24
+ return parameter
25
+
26
+ def __repr__(self):
27
+ return 'Validator({})'.format(
28
+ ', '.join([repr(p) for p in self.patterns])
29
+ )
30
+
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .Validator import Validator
4
+ from .Pattern import Pattern
5
+ from .Validate import Validate
6
+
7
+ __all__ = (
8
+ 'Validator',
9
+ 'Pattern',
10
+ 'Validate',
11
+ )
@@ -0,0 +1,525 @@
1
+ from concurrent.futures import (
2
+ Executor as PyExecutor,
3
+ Future,
4
+ ThreadPoolExecutor,
5
+ )
6
+ from multiprocessing import get_context
7
+ from multiprocessing.process import BaseProcess
8
+ from multiprocessing.synchronize import Event as ProcessEvent
9
+ from logging import getLogger
10
+ from multiprocessing.managers import SyncManager
11
+ from pickle import (
12
+ dumps,
13
+ loads,
14
+ )
15
+ from queue import (
16
+ Empty,
17
+ Queue,
18
+ )
19
+ from signal import (
20
+ SIGINT,
21
+ SIG_IGN,
22
+ signal,
23
+ )
24
+ from threading import (
25
+ Event,
26
+ Lock,
27
+ Thread,
28
+ )
29
+ from traceback import format_exc
30
+ from typing import (
31
+ Any,
32
+ Callable,
33
+ Dict,
34
+ List,
35
+ Optional,
36
+ Sequence,
37
+ Set,
38
+ TypeVar,
39
+ Union,
40
+ )
41
+
42
+ __all__ = (
43
+ 'Executor',
44
+ )
45
+
46
+ Result = TypeVar('Result')
47
+
48
+
49
+ def initialize_process() -> None:
50
+ # 부모 프로세스가 종료 요청과 정리를 조정한다.
51
+ signal(
52
+ SIGINT,
53
+ SIG_IGN,
54
+ )
55
+
56
+
57
+ def execute_loop(
58
+ incoming: Any,
59
+ outgoing: Any,
60
+ setups: Sequence[Callable[[], None]],
61
+ cleanups: Sequence[Callable[[], None]],
62
+ serialized: bool,
63
+ stopping: Union[Event, ProcessEvent],
64
+ draining: Optional[ProcessEvent] = None,
65
+ ) -> None:
66
+ if serialized:
67
+ initialize_process()
68
+ logger = Executor.logger
69
+ ready = False
70
+ try:
71
+ for setup in setups:
72
+ logger.debug(
73
+ 'Starting execution environment setup: hook=%s',
74
+ type(setup).__qualname__,
75
+ )
76
+ setup()
77
+ logger.debug(
78
+ 'Execution environment setup completed: hook=%s',
79
+ type(setup).__qualname__,
80
+ )
81
+ ready = True
82
+ logger.debug(
83
+ 'Execution unit ready: serialized=%s',
84
+ serialized,
85
+ )
86
+ while True:
87
+ item = incoming.get()
88
+ if item is None or stopping.is_set():
89
+ if draining is not None:
90
+ draining.set()
91
+ return
92
+ key, payload = item
93
+ logger.debug(
94
+ 'Execution unit task received: key=%s',
95
+ key,
96
+ )
97
+ try:
98
+ function, args, kwargs = loads(payload) if serialized else payload
99
+ result = function(
100
+ *args,
101
+ **kwargs,
102
+ )
103
+ except BaseException as error:
104
+ logger.debug(
105
+ 'Execution unit task failed: key=%s error_type=%s',
106
+ key,
107
+ type(error).__qualname__,
108
+ )
109
+ outcome = (
110
+ False,
111
+ error,
112
+ format_exc() if serialized else None,
113
+ )
114
+ else:
115
+ outcome = (
116
+ True,
117
+ result,
118
+ None,
119
+ )
120
+ if serialized:
121
+ try:
122
+ outcome = dumps(outcome)
123
+ except Exception as error:
124
+ logger.error(
125
+ 'Task outcome serialization failed: key=%s error_type=%s',
126
+ key,
127
+ type(error).__qualname__,
128
+ )
129
+ outcome = dumps((
130
+ False,
131
+ RuntimeError('Cannot serialize task outcome: ' + repr(error)),
132
+ format_exc(),
133
+ ))
134
+ outgoing.put((
135
+ key,
136
+ outcome,
137
+ ))
138
+ logger.debug(
139
+ 'Execution unit result sent: key=%s',
140
+ key,
141
+ )
142
+ finally:
143
+ if ready:
144
+ logger.debug(
145
+ 'Starting execution environment cleanup: hooks=%s',
146
+ len(cleanups),
147
+ )
148
+ run_cleanups(cleanups)
149
+ logger.debug('Execution environment cleanup completed')
150
+
151
+
152
+ def run_cleanups(
153
+ cleanups: Sequence[Callable[..., None]],
154
+ *args: Any,
155
+ ) -> None:
156
+ failure: Optional[BaseException] = None
157
+ for cleanup in cleanups:
158
+ try:
159
+ cleanup(*args)
160
+ except BaseException as error:
161
+ if failure is not None:
162
+ error.__context__ = failure
163
+ failure = error
164
+ if failure is not None:
165
+ raise failure
166
+
167
+
168
+ def execute_process(
169
+ incoming: Any,
170
+ outgoing: Any,
171
+ setups: Sequence[Callable[[], None]],
172
+ cleanups: Sequence[Callable[[], None]],
173
+ serialized: bool,
174
+ stopping: Union[Event, ProcessEvent],
175
+ draining: ProcessEvent,
176
+ completed: ProcessEvent,
177
+ ) -> None:
178
+ try:
179
+ execute_loop(
180
+ incoming,
181
+ outgoing,
182
+ setups,
183
+ cleanups,
184
+ serialized,
185
+ stopping,
186
+ draining,
187
+ )
188
+ except BaseException as error:
189
+ traceback = format_exc()
190
+ try:
191
+ outcome = dumps((
192
+ False,
193
+ (
194
+ error,
195
+ draining.is_set(),
196
+ ),
197
+ traceback,
198
+ ))
199
+ except Exception:
200
+ Executor.logger.exception('Execution environment error serialization failed')
201
+ outcome = dumps((
202
+ False,
203
+ (
204
+ RuntimeError('Cannot serialize execution environment error'),
205
+ draining.is_set(),
206
+ ),
207
+ traceback,
208
+ ))
209
+ outgoing.put((
210
+ None,
211
+ outcome,
212
+ ))
213
+ finally:
214
+ completed.set()
215
+ return
216
+
217
+
218
+ class Executor(PyExecutor):
219
+ logger = getLogger('Worker.Executor')
220
+
221
+ def __init__(
222
+ self,
223
+ size: int,
224
+ setups: Sequence[Callable[[], None]],
225
+ cleanups: Sequence[Callable[[], None]],
226
+ process: bool = False,
227
+ ):
228
+ self.size = size
229
+ self.setups = setups
230
+ self.cleanups = cleanups
231
+ self.process = process
232
+ self.lock = Lock()
233
+ self.pending: Dict[int, Future[Any]] = {}
234
+ self.workers: List[Future] = []
235
+ self.processes: List[BaseProcess] = []
236
+ self.draining: Dict[int, ProcessEvent] = {}
237
+ self.completed: Dict[int, ProcessEvent] = {}
238
+ self.process_lock = Lock()
239
+ self.stopping: Union[Event, ProcessEvent] = Event()
240
+ self.closed = False
241
+ self.failure: Optional[BaseException] = None
242
+ self.manager: Optional[SyncManager] = None
243
+ self.executor: Optional[PyExecutor] = None
244
+ self.monitor: Optional[Thread] = None
245
+ self.incoming: Any = None
246
+ self.outgoing: Any = None
247
+ self.sequence = 0
248
+
249
+ def start(self) -> None:
250
+ self.logger.info(
251
+ 'Starting Executor: concurrency=%s process=%s',
252
+ self.size,
253
+ self.process,
254
+ )
255
+ try:
256
+ context = get_context('spawn')
257
+ if self.process:
258
+ self.stopping = context.Event()
259
+ self.manager = context.Manager()
260
+ self.incoming = self.manager.Queue()
261
+ self.outgoing = self.manager.Queue()
262
+ else:
263
+ self.incoming = Queue()
264
+ self.outgoing = Queue()
265
+ self.executor = ThreadPoolExecutor(self.size)
266
+ for _ in range(self.size):
267
+ parameters = (
268
+ self.incoming,
269
+ self.outgoing,
270
+ self.setups,
271
+ self.cleanups,
272
+ self.process,
273
+ self.stopping,
274
+ )
275
+ if self.process:
276
+ draining = context.Event()
277
+ completed = context.Event()
278
+ process = context.Process(
279
+ target=execute_process,
280
+ args=parameters + (
281
+ draining,
282
+ completed,
283
+ ),
284
+ )
285
+ process.start()
286
+ self.processes.append(process)
287
+ if process.pid is None:
288
+ raise RuntimeError('Started process has no PID')
289
+ # Keep named semaphores alive until spawned children have exited.
290
+ self.draining[process.pid] = draining
291
+ self.completed[process.pid] = completed
292
+ else:
293
+ if self.executor is None:
294
+ raise RuntimeError('Thread executor is not initialized')
295
+ self.workers.append(self.executor.submit(
296
+ execute_loop,
297
+ *parameters,
298
+ ))
299
+ monitor = Thread(target=self.collect)
300
+ monitor.start()
301
+ self.monitor = monitor
302
+ self.logger.info('Executor execution environment ready')
303
+ except BaseException:
304
+ self.logger.exception('Executor startup failed')
305
+ self.closed = True
306
+ try:
307
+ for _ in self.workers:
308
+ self.incoming.put(None)
309
+ finally:
310
+ try:
311
+ if self.executor is not None:
312
+ self.executor.shutdown(wait=True)
313
+ for process in self.processes:
314
+ process.terminate()
315
+ process.join()
316
+ finally:
317
+ if self.manager is not None:
318
+ self.manager.shutdown()
319
+ raise
320
+
321
+ def submit(
322
+ self,
323
+ fn: Callable[..., Result],
324
+ /,
325
+ *args: Any,
326
+ **kwargs: Any,
327
+ ) -> Future[Result]:
328
+ payload = (
329
+ fn,
330
+ args,
331
+ kwargs,
332
+ )
333
+ if self.process:
334
+ payload = dumps(payload)
335
+ with self.lock:
336
+ if self.closed:
337
+ raise RuntimeError('Pool is closed')
338
+ if self.failure is not None:
339
+ raise RuntimeError('Pool execution environment failed') from self.failure
340
+ if self.monitor is None:
341
+ self.start()
342
+ key = self.sequence
343
+ self.sequence += 1
344
+ future: Future[Result] = Future()
345
+ future.set_running_or_notify_cancel()
346
+ self.pending[key] = future
347
+ self.logger.debug(
348
+ 'Dispatching task to execution environment: key=%s pending=%s',
349
+ key,
350
+ len(self.pending),
351
+ )
352
+ try:
353
+ self.incoming.put((
354
+ key,
355
+ payload,
356
+ ))
357
+ except BaseException:
358
+ del self.pending[key]
359
+ raise
360
+ return future
361
+
362
+ def collect(self) -> None:
363
+ try:
364
+ self.collect_results()
365
+ except BaseException as error:
366
+ self.fail(error)
367
+
368
+ def fail(
369
+ self,
370
+ error: BaseException,
371
+ *,
372
+ report: bool = True,
373
+ ) -> None:
374
+ with self.lock:
375
+ first_failure = self.failure is None
376
+ self.failure = error
377
+ pending = tuple(self.pending.values())
378
+ self.pending.clear()
379
+ if self.process:
380
+ self.stopping.set()
381
+ self.stop_processes()
382
+ if report and (first_failure or self.stopping.is_set()):
383
+ self.logger.error(
384
+ 'Executor execution environment failed: pending=%s',
385
+ len(pending),
386
+ exc_info=(
387
+ type(error),
388
+ error,
389
+ error.__traceback__,
390
+ ),
391
+ )
392
+ for future in pending:
393
+ future.set_exception(error)
394
+
395
+ def collect_results(self) -> None:
396
+ observed: Set[Future] = set()
397
+ while True:
398
+ no_outcome = False
399
+ try:
400
+ key, outcome = self.outgoing.get(timeout=0.05)
401
+ except Empty:
402
+ no_outcome = True
403
+ else:
404
+ success, value, traceback = loads(outcome) if self.process else outcome
405
+ if key is None:
406
+ error, draining = value
407
+ if traceback is not None:
408
+ error.__cause__ = RuntimeError('Remote execution traceback:\n' + traceback)
409
+ if draining and self.closed and not self.stopping.is_set():
410
+ with self.lock:
411
+ self.failure = error
412
+ self.logger.error('Execution environment cleanup failed', exc_info=(
413
+ type(error),
414
+ error,
415
+ error.__traceback__,
416
+ ))
417
+ else:
418
+ self.fail(error)
419
+ continue
420
+ self.logger.debug(
421
+ 'Execution result collected: key=%s success=%s',
422
+ key,
423
+ success,
424
+ )
425
+ with self.lock:
426
+ future = self.pending.pop(
427
+ key,
428
+ None,
429
+ )
430
+ if future is not None:
431
+ if success:
432
+ future.set_result(value)
433
+ else:
434
+ if traceback is not None:
435
+ value.__cause__ = RuntimeError('Remote execution traceback:\n' + traceback)
436
+ future.set_exception(value)
437
+ for worker in self.workers:
438
+ if worker.done() and worker not in observed:
439
+ observed.add(worker)
440
+ failure: Optional[BaseException] = worker.exception()
441
+ if failure is not None:
442
+ self.fail(failure)
443
+ if not self.stopping.is_set() and any(
444
+ process.pid is not None and process.exitcode is not None and (
445
+ process.exitcode != 0 or not self.completed[process.pid].is_set()
446
+ )
447
+ for process in self.processes
448
+ ):
449
+ self.fail(RuntimeError('Pool execution process failed'))
450
+ with self.lock:
451
+ finished = (
452
+ not self.pending
453
+ and all(worker.done() for worker in self.workers)
454
+ and all(not process.is_alive() for process in self.processes)
455
+ and (not self.process or no_outcome)
456
+ )
457
+ if finished:
458
+ return
459
+
460
+ def shutdown(
461
+ self,
462
+ wait: bool = True,
463
+ *,
464
+ cancel_futures: bool = False,
465
+ ) -> None:
466
+ self.logger.info(
467
+ 'Executor shutdown requested: wait=%s',
468
+ wait,
469
+ )
470
+ with self.lock:
471
+ if not self.closed:
472
+ self.closed = True
473
+ if self.monitor is not None:
474
+ for _ in range(self.size):
475
+ self.incoming.put(None)
476
+ executor = self.executor
477
+ if self.monitor is None:
478
+ self.logger.debug('Closing an unstarted Executor')
479
+ return
480
+ if executor is not None:
481
+ executor.shutdown(wait=wait)
482
+ if wait:
483
+ for process in self.processes:
484
+ process.join()
485
+ if self.monitor is not None:
486
+ self.monitor.join()
487
+ if self.manager is not None:
488
+ self.manager.shutdown()
489
+ if self.failure is not None:
490
+ raise RuntimeError('Pool execution environment failed') from self.failure
491
+ self.logger.info('Executor shutdown completed')
492
+ return
493
+
494
+ def replacement(self) -> 'Executor':
495
+ return Executor(
496
+ self.size,
497
+ self.setups,
498
+ self.cleanups,
499
+ process=self.process,
500
+ )
501
+
502
+ def stop_processes(self) -> None:
503
+ """Reap surviving processes before publishing failures for their tasks."""
504
+ with self.process_lock:
505
+ for process in self.processes:
506
+ if process.is_alive():
507
+ process.kill()
508
+ for process in self.processes:
509
+ process.join()
510
+ return
511
+
512
+ def abort(self) -> None:
513
+ """Discard this execution generation without waiting for running threads."""
514
+ self.stopping.set()
515
+ if self.process:
516
+ self.stop_processes()
517
+ self.logger.warning('Discarding execution generation: pending=%s', len(self.pending))
518
+ self.fail(RuntimeError('Execution generation discarded'), report=False)
519
+ self.shutdown(wait=False)
520
+ if self.process:
521
+ if self.monitor is not None:
522
+ self.monitor.join()
523
+ if self.manager is not None:
524
+ self.manager.shutdown()
525
+ return