ddeutil-workflow 0.0.33__py3-none-any.whl → 0.0.35__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.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__init__.py +19 -10
- ddeutil/workflow/api/api.py +13 -8
- ddeutil/workflow/api/routes/__init__.py +8 -0
- ddeutil/workflow/api/routes/logs.py +36 -0
- ddeutil/workflow/api/{route.py → routes/schedules.py} +2 -131
- ddeutil/workflow/api/routes/workflows.py +137 -0
- ddeutil/workflow/audit.py +28 -37
- ddeutil/workflow/{hook.py → caller.py} +27 -27
- ddeutil/workflow/conf.py +47 -12
- ddeutil/workflow/job.py +149 -138
- ddeutil/workflow/logs.py +214 -0
- ddeutil/workflow/params.py +40 -12
- ddeutil/workflow/result.py +40 -61
- ddeutil/workflow/scheduler.py +185 -163
- ddeutil/workflow/{stage.py → stages.py} +105 -42
- ddeutil/workflow/utils.py +20 -2
- ddeutil/workflow/workflow.py +142 -117
- {ddeutil_workflow-0.0.33.dist-info → ddeutil_workflow-0.0.35.dist-info}/METADATA +36 -32
- ddeutil_workflow-0.0.35.dist-info/RECORD +30 -0
- {ddeutil_workflow-0.0.33.dist-info → ddeutil_workflow-0.0.35.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.33.dist-info/RECORD +0 -26
- {ddeutil_workflow-0.0.33.dist-info → ddeutil_workflow-0.0.35.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.33.dist-info → ddeutil_workflow-0.0.35.dist-info}/top_level.txt +0 -0
ddeutil/workflow/scheduler.py
CHANGED
@@ -4,18 +4,18 @@
|
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
"""
|
7
|
-
The main schedule running is
|
8
|
-
multiprocess of
|
9
|
-
config by
|
7
|
+
The main schedule running is `schedule_runner` function that trigger the
|
8
|
+
multiprocess of `schedule_control` function for listing schedules on the
|
9
|
+
config by `Loader.finds(Schedule)`.
|
10
10
|
|
11
|
-
The
|
12
|
-
functions;
|
11
|
+
The `schedule_control` is the scheduler function that release 2 schedule
|
12
|
+
functions; `workflow_task`, and `workflow_monitor`.
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
`schedule_control` ---( Every minute at :02 )--> `schedule_task`
|
15
|
+
---( Every 5 minutes )--> `monitor`
|
16
16
|
|
17
|
-
The
|
18
|
-
for multithreading strategy. This
|
17
|
+
The `schedule_task` will run `task.release` method in threading object
|
18
|
+
for multithreading strategy. This `release` method will run only one crontab
|
19
19
|
value with the on field.
|
20
20
|
"""
|
21
21
|
from __future__ import annotations
|
@@ -55,7 +55,7 @@ from .audit import Audit, get_audit
|
|
55
55
|
from .conf import Loader, config, get_logger
|
56
56
|
from .cron import On
|
57
57
|
from .exceptions import ScheduleException, WorkflowException
|
58
|
-
from .result import Result
|
58
|
+
from .result import Result, Status
|
59
59
|
from .utils import batch, delay
|
60
60
|
from .workflow import Release, ReleaseQueue, Workflow, WorkflowTask
|
61
61
|
|
@@ -134,7 +134,7 @@ class ScheduleWorkflow(BaseModel):
|
|
134
134
|
on: list[str] = [on]
|
135
135
|
|
136
136
|
if any(not isinstance(n, (dict, str)) for n in on):
|
137
|
-
raise TypeError("The
|
137
|
+
raise TypeError("The `on` key should be list of str or dict")
|
138
138
|
|
139
139
|
# NOTE: Pass on value to Loader and keep on model object to on
|
140
140
|
# field.
|
@@ -314,25 +314,19 @@ class Schedule(BaseModel):
|
|
314
314
|
*,
|
315
315
|
stop: datetime | None = None,
|
316
316
|
externals: DictData | None = None,
|
317
|
-
|
318
|
-
|
317
|
+
audit: type[Audit] | None = None,
|
318
|
+
parent_run_id: str | None = None,
|
319
|
+
) -> Result: # pragma: no cov
|
319
320
|
"""Pending this schedule tasks with the schedule package.
|
320
321
|
|
321
322
|
:param stop: A datetime value that use to stop running schedule.
|
322
323
|
:param externals: An external parameters that pass to Loader.
|
323
|
-
:param
|
324
|
-
writing its release
|
324
|
+
:param audit: An audit class that use on the workflow task release for
|
325
|
+
writing its release audit context.
|
326
|
+
:param parent_run_id: A parent workflow running ID for this release.
|
325
327
|
"""
|
326
|
-
|
327
|
-
|
328
|
-
except ImportError:
|
329
|
-
raise ImportError(
|
330
|
-
"Should install schedule package before use this method."
|
331
|
-
) from None
|
332
|
-
|
333
|
-
# NOTE: Get default logging.
|
334
|
-
log: type[Audit] = log or get_audit()
|
335
|
-
scheduler: Scheduler = Scheduler()
|
328
|
+
audit: type[Audit] = audit or get_audit()
|
329
|
+
result: Result = Result().set_parent_run_id(parent_run_id)
|
336
330
|
|
337
331
|
# NOTE: Create the start and stop datetime.
|
338
332
|
start_date: datetime = datetime.now(tz=config.tz)
|
@@ -346,66 +340,23 @@ class Schedule(BaseModel):
|
|
346
340
|
second=0, microsecond=0
|
347
341
|
) + timedelta(minutes=1)
|
348
342
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
stop=stop_date,
|
359
|
-
queue=queue,
|
360
|
-
threads=threads,
|
361
|
-
log=log,
|
362
|
-
)
|
363
|
-
.tag("control")
|
364
|
-
)
|
365
|
-
|
366
|
-
# NOTE: Checking zombie task with schedule job will start every 5 minute at
|
367
|
-
# :10 seconds.
|
368
|
-
(
|
369
|
-
scheduler.every(5)
|
370
|
-
.minutes.at(":10")
|
371
|
-
.do(
|
372
|
-
monitor,
|
373
|
-
threads=threads,
|
374
|
-
)
|
375
|
-
.tag("monitor")
|
376
|
-
)
|
377
|
-
|
378
|
-
# NOTE: Start running schedule
|
379
|
-
logger.info(
|
380
|
-
f"[SCHEDULE]: Schedule with stopper: {stop_date:%Y-%m-%d %H:%M:%S}"
|
343
|
+
scheduler_pending(
|
344
|
+
tasks=self.tasks(
|
345
|
+
start_date_waiting, queue=queue, externals=externals
|
346
|
+
),
|
347
|
+
stop=stop_date,
|
348
|
+
queue=queue,
|
349
|
+
threads=threads,
|
350
|
+
result=result,
|
351
|
+
audit=audit,
|
381
352
|
)
|
382
353
|
|
383
|
-
|
384
|
-
scheduler.run_pending()
|
385
|
-
time.sleep(1)
|
354
|
+
return result.catch(status=Status.SUCCESS)
|
386
355
|
|
387
|
-
# NOTE: Break the scheduler when the control job does not exist.
|
388
|
-
if not scheduler.get_jobs("control"):
|
389
|
-
scheduler.clear("monitor")
|
390
356
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
"running in background."
|
395
|
-
)
|
396
|
-
delay(10)
|
397
|
-
monitor(threads)
|
398
|
-
|
399
|
-
break
|
400
|
-
|
401
|
-
logger.warning(
|
402
|
-
f"[SCHEDULE]: Queue: {[list(queue[wf].queue) for wf in queue]}"
|
403
|
-
)
|
404
|
-
|
405
|
-
|
406
|
-
ResultOrCancelJob = Union[type[CancelJob], Result]
|
407
|
-
ReturnCancelJob = Callable[P, ResultOrCancelJob]
|
408
|
-
DecoratorCancelJob = Callable[[ReturnCancelJob], ReturnCancelJob]
|
357
|
+
ResultOrCancel = Union[type[CancelJob], Result]
|
358
|
+
ReturnResultOrCancel = Callable[P, ResultOrCancel]
|
359
|
+
DecoratorCancelJob = Callable[[ReturnResultOrCancel], ReturnResultOrCancel]
|
409
360
|
|
410
361
|
|
411
362
|
def catch_exceptions(cancel_on_failure: bool = False) -> DecoratorCancelJob:
|
@@ -418,10 +369,12 @@ def catch_exceptions(cancel_on_failure: bool = False) -> DecoratorCancelJob:
|
|
418
369
|
:rtype: DecoratorCancelJob
|
419
370
|
"""
|
420
371
|
|
421
|
-
def decorator(
|
372
|
+
def decorator(
|
373
|
+
func: ReturnResultOrCancel,
|
374
|
+
) -> ReturnResultOrCancel: # pragma: no cov
|
422
375
|
|
423
376
|
@wraps(func)
|
424
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) ->
|
377
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> ResultOrCancel:
|
425
378
|
try:
|
426
379
|
return func(*args, **kwargs)
|
427
380
|
except Exception as err:
|
@@ -438,8 +391,9 @@ def catch_exceptions(cancel_on_failure: bool = False) -> DecoratorCancelJob:
|
|
438
391
|
class ReleaseThread(TypedDict):
|
439
392
|
"""TypeDict for the release thread."""
|
440
393
|
|
441
|
-
thread: Thread
|
394
|
+
thread: Optional[Thread]
|
442
395
|
start_date: datetime
|
396
|
+
release_date: datetime
|
443
397
|
|
444
398
|
|
445
399
|
ReleaseThreads = dict[str, ReleaseThread]
|
@@ -451,8 +405,9 @@ def schedule_task(
|
|
451
405
|
stop: datetime,
|
452
406
|
queue: dict[str, ReleaseQueue],
|
453
407
|
threads: ReleaseThreads,
|
454
|
-
|
455
|
-
|
408
|
+
audit: type[Audit],
|
409
|
+
parent_run_id: str | None = None,
|
410
|
+
) -> ResultOrCancel:
|
456
411
|
"""Schedule task function that generate thread of workflow task release
|
457
412
|
method in background. This function do the same logic as the workflow poke
|
458
413
|
method, but it runs with map of schedules and the on values.
|
@@ -464,10 +419,12 @@ def schedule_task(
|
|
464
419
|
:param stop: A stop datetime object that force stop running scheduler.
|
465
420
|
:param queue: A mapping of alias name and ReleaseQueue object.
|
466
421
|
:param threads: A mapping of alias name and Thread object.
|
467
|
-
:param
|
422
|
+
:param audit: An audit class that want to make audit object.
|
423
|
+
:param parent_run_id: A parent workflow running ID for this release.
|
468
424
|
|
469
|
-
:rtype:
|
425
|
+
:rtype: ResultOrCancel
|
470
426
|
"""
|
427
|
+
result: Result = Result().set_parent_run_id(parent_run_id)
|
471
428
|
current_date: datetime = datetime.now(tz=config.tz)
|
472
429
|
if current_date > stop.replace(tzinfo=config.tz):
|
473
430
|
return CancelJob
|
@@ -490,14 +447,16 @@ def schedule_task(
|
|
490
447
|
q: ReleaseQueue = queue[task.alias]
|
491
448
|
|
492
449
|
# NOTE: Start adding queue and move the runner date in the WorkflowTask.
|
493
|
-
task.queue(stop, q,
|
450
|
+
task.queue(stop, q, audit=audit)
|
494
451
|
|
495
452
|
# NOTE: Get incoming datetime queue.
|
496
|
-
|
453
|
+
result.trace.debug(
|
454
|
+
f"[WORKFLOW]: Queue: {task.alias!r} : {list(q.queue)}"
|
455
|
+
)
|
497
456
|
|
498
457
|
# VALIDATE: Check the queue is empty or not.
|
499
458
|
if not q.is_queued:
|
500
|
-
|
459
|
+
result.trace.warning(
|
501
460
|
f"[WORKFLOW]: Queue is empty for : {task.alias!r} : "
|
502
461
|
f"{task.runner.cron}"
|
503
462
|
)
|
@@ -508,7 +467,7 @@ def schedule_task(
|
|
508
467
|
second=0, microsecond=0
|
509
468
|
)
|
510
469
|
if (first_date := q.first_queue.date) > current_release:
|
511
|
-
|
470
|
+
result.trace.debug(
|
512
471
|
f"[WORKFLOW]: Skip schedule "
|
513
472
|
f"{first_date:%Y-%m-%d %H:%M:%S} for : {task.alias!r}"
|
514
473
|
)
|
@@ -523,7 +482,7 @@ def schedule_task(
|
|
523
482
|
release: Release = heappop(q.queue)
|
524
483
|
heappush(q.running, release)
|
525
484
|
|
526
|
-
|
485
|
+
result.trace.info(
|
527
486
|
f"[WORKFLOW]: Start thread: '{task.alias}|"
|
528
487
|
f"{release.date:%Y%m%d%H%M}'"
|
529
488
|
)
|
@@ -533,7 +492,7 @@ def schedule_task(
|
|
533
492
|
thread_name: str = f"{task.alias}|{release.date:%Y%m%d%H%M}"
|
534
493
|
thread: Thread = Thread(
|
535
494
|
target=catch_exceptions(cancel_on_failure=True)(task.release),
|
536
|
-
kwargs={"release": release, "queue": q, "
|
495
|
+
kwargs={"release": release, "queue": q, "audit": audit},
|
537
496
|
name=thread_name,
|
538
497
|
daemon=True,
|
539
498
|
)
|
@@ -541,88 +500,76 @@ def schedule_task(
|
|
541
500
|
threads[thread_name] = {
|
542
501
|
"thread": thread,
|
543
502
|
"start_date": datetime.now(tz=config.tz),
|
503
|
+
"release_date": release.date,
|
544
504
|
}
|
545
505
|
|
546
506
|
thread.start()
|
547
507
|
|
548
508
|
delay()
|
549
509
|
|
550
|
-
|
510
|
+
result.trace.debug(
|
511
|
+
f"[SCHEDULE]: End schedule task at {current_date:%Y-%m-%d %H:%M:%S} "
|
512
|
+
f"{'=' * 60}"
|
513
|
+
)
|
514
|
+
return result.catch(
|
515
|
+
status=Status.SUCCESS, context={"task_date": current_date}
|
516
|
+
)
|
551
517
|
|
552
518
|
|
553
|
-
def monitor(
|
519
|
+
def monitor(
|
520
|
+
threads: ReleaseThreads,
|
521
|
+
parent_run_id: str | None = None,
|
522
|
+
) -> None: # pragma: no cov
|
554
523
|
"""Monitoring function that running every five minute for track long-running
|
555
524
|
thread instance from the schedule_control function that run every minute.
|
556
525
|
|
557
526
|
:param threads: A mapping of Thread object and its name.
|
527
|
+
:param parent_run_id: A parent workflow running ID for this release.
|
528
|
+
|
558
529
|
:type threads: ReleaseThreads
|
559
530
|
"""
|
560
|
-
|
531
|
+
result: Result = Result().set_parent_run_id(parent_run_id)
|
532
|
+
result.trace.debug("[MONITOR]: Start checking long running schedule task.")
|
561
533
|
|
562
534
|
snapshot_threads: list[str] = list(threads.keys())
|
563
|
-
for
|
535
|
+
for thread_name in snapshot_threads:
|
564
536
|
|
565
|
-
thread_release: ReleaseThread = threads[
|
537
|
+
thread_release: ReleaseThread = threads[thread_name]
|
566
538
|
|
567
539
|
# NOTE: remove the thread that running success.
|
568
|
-
|
569
|
-
|
540
|
+
thread = thread_release["thread"]
|
541
|
+
if thread and (not thread_release["thread"].is_alive()):
|
542
|
+
thread_release["thread"] = None
|
570
543
|
|
571
544
|
|
572
|
-
def
|
573
|
-
|
574
|
-
stop: datetime
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
the background.
|
545
|
+
def scheduler_pending(
|
546
|
+
tasks: list[WorkflowTask],
|
547
|
+
stop: datetime,
|
548
|
+
queue: dict[str, ReleaseQueue],
|
549
|
+
threads: ReleaseThreads,
|
550
|
+
result: Result,
|
551
|
+
audit: type[Audit],
|
552
|
+
) -> Result: # pragma: no cov
|
553
|
+
"""Scheduler pending function.
|
582
554
|
|
583
|
-
:param
|
584
|
-
:param stop: A datetime
|
585
|
-
:param
|
586
|
-
:param
|
587
|
-
|
555
|
+
:param tasks: A list of WorkflowTask object.
|
556
|
+
:param stop: A stop datetime object that force stop running scheduler.
|
557
|
+
:param queue: A mapping of alias name and ReleaseQueue object.
|
558
|
+
:param threads: A mapping of alias name and Thread object.
|
559
|
+
:param result: A result object.
|
560
|
+
:param audit: An audit class that want to make audit object.
|
588
561
|
|
589
|
-
:rtype:
|
562
|
+
:rtype: Result
|
590
563
|
"""
|
591
|
-
# NOTE: Lazy import Scheduler object from the schedule package.
|
592
564
|
try:
|
593
565
|
from schedule import Scheduler
|
594
566
|
except ImportError:
|
595
567
|
raise ImportError(
|
596
|
-
"Should install schedule package before use this
|
568
|
+
"Should install schedule package before use this method."
|
597
569
|
) from None
|
598
570
|
|
599
|
-
# NOTE: Get default logging.
|
600
|
-
log: type[Audit] = log or get_audit()
|
601
571
|
scheduler: Scheduler = Scheduler()
|
602
572
|
|
603
|
-
# NOTE: Create the start and stop datetime.
|
604
|
-
start_date: datetime = datetime.now(tz=config.tz)
|
605
|
-
stop_date: datetime = stop or (start_date + config.stop_boundary_delta)
|
606
|
-
|
607
|
-
# IMPORTANT: Create main mapping of queue and thread object.
|
608
|
-
queue: dict[str, ReleaseQueue] = {}
|
609
|
-
threads: ReleaseThreads = {}
|
610
|
-
|
611
|
-
start_date_waiting: datetime = start_date.replace(
|
612
|
-
second=0, microsecond=0
|
613
|
-
) + timedelta(minutes=1)
|
614
|
-
|
615
|
-
tasks: list[WorkflowTask] = []
|
616
|
-
for name in schedules:
|
617
|
-
schedule: Schedule = Schedule.from_loader(name, externals=externals)
|
618
|
-
tasks.extend(
|
619
|
-
schedule.tasks(
|
620
|
-
start_date_waiting,
|
621
|
-
queue=queue,
|
622
|
-
externals=externals,
|
623
|
-
),
|
624
|
-
)
|
625
|
-
|
626
573
|
# NOTE: This schedule job will start every minute at :02 seconds.
|
627
574
|
(
|
628
575
|
scheduler.every(1)
|
@@ -630,10 +577,11 @@ def schedule_control(
|
|
630
577
|
.do(
|
631
578
|
schedule_task,
|
632
579
|
tasks=tasks,
|
633
|
-
stop=
|
580
|
+
stop=stop,
|
634
581
|
queue=queue,
|
635
582
|
threads=threads,
|
636
|
-
|
583
|
+
audit=audit,
|
584
|
+
parent_run_id=result.parent_run_id,
|
637
585
|
)
|
638
586
|
.tag("control")
|
639
587
|
)
|
@@ -651,9 +599,8 @@ def schedule_control(
|
|
651
599
|
)
|
652
600
|
|
653
601
|
# NOTE: Start running schedule
|
654
|
-
|
655
|
-
f"[SCHEDULE]: Schedule
|
656
|
-
f"{stop_date:%Y-%m-%d %H:%M:%S}"
|
602
|
+
result.trace.info(
|
603
|
+
f"[SCHEDULE]: Schedule with stopper: {stop:%Y-%m-%d %H:%M:%S}"
|
657
604
|
)
|
658
605
|
|
659
606
|
while True:
|
@@ -664,8 +611,8 @@ def schedule_control(
|
|
664
611
|
if not scheduler.get_jobs("control"):
|
665
612
|
scheduler.clear("monitor")
|
666
613
|
|
667
|
-
while len(threads) > 0:
|
668
|
-
|
614
|
+
while len([t for t in threads.values() if t["thread"]]) > 0:
|
615
|
+
result.trace.warning(
|
669
616
|
"[SCHEDULE]: Waiting schedule release thread that still "
|
670
617
|
"running in background."
|
671
618
|
)
|
@@ -674,17 +621,87 @@ def schedule_control(
|
|
674
621
|
|
675
622
|
break
|
676
623
|
|
677
|
-
|
624
|
+
result.trace.warning(
|
678
625
|
f"[SCHEDULE]: Queue: {[list(queue[wf].queue) for wf in queue]}"
|
679
626
|
)
|
680
|
-
return
|
627
|
+
return result.catch(
|
628
|
+
status=Status.SUCCESS,
|
629
|
+
context={
|
630
|
+
"threads": [
|
631
|
+
{
|
632
|
+
"name": thread,
|
633
|
+
"start_date": threads[thread]["start_date"],
|
634
|
+
"release_date": threads[thread]["release_date"],
|
635
|
+
}
|
636
|
+
for thread in threads
|
637
|
+
],
|
638
|
+
},
|
639
|
+
)
|
640
|
+
|
641
|
+
|
642
|
+
def schedule_control(
|
643
|
+
schedules: list[str],
|
644
|
+
stop: datetime | None = None,
|
645
|
+
externals: DictData | None = None,
|
646
|
+
*,
|
647
|
+
audit: type[Audit] | None = None,
|
648
|
+
parent_run_id: str | None = None,
|
649
|
+
) -> Result: # pragma: no cov
|
650
|
+
"""Scheduler control function that run the chuck of schedules every minute
|
651
|
+
and this function release monitoring thread for tracking undead thread in
|
652
|
+
the background.
|
653
|
+
|
654
|
+
:param schedules: A list of workflow names that want to schedule running.
|
655
|
+
:param stop: A datetime value that use to stop running schedule.
|
656
|
+
:param externals: An external parameters that pass to Loader.
|
657
|
+
:param audit: An audit class that use on the workflow task release for
|
658
|
+
writing its release audit context.
|
659
|
+
:param parent_run_id: A parent workflow running ID for this release.
|
660
|
+
|
661
|
+
:rtype: Result
|
662
|
+
"""
|
663
|
+
audit: type[Audit] = audit or get_audit()
|
664
|
+
result: Result = Result().set_parent_run_id(parent_run_id)
|
665
|
+
|
666
|
+
# NOTE: Create the start and stop datetime.
|
667
|
+
start_date: datetime = datetime.now(tz=config.tz)
|
668
|
+
stop_date: datetime = stop or (start_date + config.stop_boundary_delta)
|
669
|
+
|
670
|
+
# IMPORTANT: Create main mapping of queue and thread object.
|
671
|
+
queue: dict[str, ReleaseQueue] = {}
|
672
|
+
threads: ReleaseThreads = {}
|
673
|
+
|
674
|
+
start_date_waiting: datetime = start_date.replace(
|
675
|
+
second=0, microsecond=0
|
676
|
+
) + timedelta(minutes=1)
|
677
|
+
|
678
|
+
tasks: list[WorkflowTask] = []
|
679
|
+
for name in schedules:
|
680
|
+
tasks.extend(
|
681
|
+
Schedule.from_loader(name, externals=externals).tasks(
|
682
|
+
start_date_waiting,
|
683
|
+
queue=queue,
|
684
|
+
externals=externals,
|
685
|
+
),
|
686
|
+
)
|
687
|
+
|
688
|
+
scheduler_pending(
|
689
|
+
tasks=tasks,
|
690
|
+
stop=stop_date,
|
691
|
+
queue=queue,
|
692
|
+
threads=threads,
|
693
|
+
result=result,
|
694
|
+
audit=audit,
|
695
|
+
)
|
696
|
+
|
697
|
+
return result.catch(status=Status.SUCCESS, context={"schedules": schedules})
|
681
698
|
|
682
699
|
|
683
700
|
def schedule_runner(
|
684
701
|
stop: datetime | None = None,
|
685
702
|
externals: DictData | None = None,
|
686
703
|
excluded: list[str] | None = None,
|
687
|
-
) ->
|
704
|
+
) -> Result: # pragma: no cov
|
688
705
|
"""Schedule runner function it the multiprocess controller function for
|
689
706
|
split the setting schedule to the `schedule_control` function on the
|
690
707
|
process pool. It chunks schedule configs that exists in config
|
@@ -696,20 +713,22 @@ def schedule_runner(
|
|
696
713
|
|
697
714
|
This function will get all workflows that include on value that was
|
698
715
|
created in config path and chuck it with application config variable
|
699
|
-
|
716
|
+
`WORKFLOW_APP_MAX_SCHEDULE_PER_PROCESS` env var to multiprocess executor
|
700
717
|
pool.
|
701
718
|
|
702
719
|
The current workflow logic that split to process will be below diagram:
|
703
720
|
|
704
|
-
MAIN ==> process 01 ==> schedule
|
705
|
-
|
706
|
-
==> schedule
|
707
|
-
|
721
|
+
MAIN ==> process 01 ==> schedule ==> thread 01 --> 01
|
722
|
+
==> thread 01 --> 02
|
723
|
+
==> schedule ==> thread 02 --> 01
|
724
|
+
==> thread 02 --> 02
|
725
|
+
==> ...
|
708
726
|
==> process 02 ==> ...
|
709
727
|
|
710
|
-
:rtype:
|
728
|
+
:rtype: Result
|
711
729
|
"""
|
712
|
-
|
730
|
+
result: Result = Result()
|
731
|
+
context: DictData = {"schedules": [], "threads": []}
|
713
732
|
|
714
733
|
with ProcessPoolExecutor(
|
715
734
|
max_workers=config.max_schedule_process,
|
@@ -721,6 +740,7 @@ def schedule_runner(
|
|
721
740
|
schedules=[load[0] for load in loader],
|
722
741
|
stop=stop,
|
723
742
|
externals=(externals or {}),
|
743
|
+
parent_run_id=result.parent_run_id,
|
724
744
|
)
|
725
745
|
for loader in batch(
|
726
746
|
Loader.finds(Schedule, excluded=excluded),
|
@@ -735,6 +755,8 @@ def schedule_runner(
|
|
735
755
|
logger.error(str(err))
|
736
756
|
raise WorkflowException(str(err)) from err
|
737
757
|
|
738
|
-
|
758
|
+
rs: Result = future.result(timeout=1)
|
759
|
+
context["schedule"].extend(rs.context.get("schedules", []))
|
760
|
+
context["threads"].extend(rs.context.get("threads", []))
|
739
761
|
|
740
|
-
return
|
762
|
+
return result.catch(status=0, context=context)
|