orionis 0.521.0__py3-none-any.whl → 0.523.0__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.
- orionis/console/commands/scheduler_work.py +12 -10
- orionis/console/tasks/schedule.py +500 -125
- orionis/foundation/application.py +66 -12
- orionis/foundation/config/session/entities/session.py +2 -0
- orionis/metadata/framework.py +1 -1
- orionis/services/introspection/dataclass/__init__.py +0 -0
- orionis/services/introspection/dataclass/attributes.py +30 -0
- {orionis-0.521.0.dist-info → orionis-0.523.0.dist-info}/METADATA +1 -1
- {orionis-0.521.0.dist-info → orionis-0.523.0.dist-info}/RECORD +13 -11
- {orionis-0.521.0.dist-info → orionis-0.523.0.dist-info}/WHEEL +0 -0
- {orionis-0.521.0.dist-info → orionis-0.523.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.521.0.dist-info → orionis-0.523.0.dist-info}/top_level.txt +0 -0
- {orionis-0.521.0.dist-info → orionis-0.523.0.dist-info}/zip-safe +0 -0
|
@@ -30,7 +30,9 @@ from orionis.console.entities.job_executed import JobExecuted
|
|
|
30
30
|
from orionis.console.entities.job_max_instances import JobMaxInstances
|
|
31
31
|
from orionis.console.entities.job_missed import JobMissed
|
|
32
32
|
from orionis.console.entities.job_modified import JobModified
|
|
33
|
+
from orionis.console.entities.job_pause import JobPause
|
|
33
34
|
from orionis.console.entities.job_removed import JobRemoved
|
|
35
|
+
from orionis.console.entities.job_resume import JobResume
|
|
34
36
|
from orionis.console.entities.job_submitted import JobSubmitted
|
|
35
37
|
from orionis.console.entities.scheduler_paused import SchedulerPaused
|
|
36
38
|
from orionis.console.entities.scheduler_resumed import SchedulerResumed
|
|
@@ -40,7 +42,6 @@ from orionis.console.enums.listener import ListeningEvent
|
|
|
40
42
|
from orionis.console.enums.event import Event as EventEntity
|
|
41
43
|
from orionis.console.exceptions import CLIOrionisRuntimeError
|
|
42
44
|
from orionis.console.exceptions.cli_orionis_value_error import CLIOrionisValueError
|
|
43
|
-
from orionis.console.output.contracts.console import IConsole
|
|
44
45
|
from orionis.foundation.contracts.application import IApplication
|
|
45
46
|
from orionis.services.log.contracts.log_service import ILogger
|
|
46
47
|
|
|
@@ -50,7 +51,6 @@ class Scheduler(ISchedule):
|
|
|
50
51
|
self,
|
|
51
52
|
reactor: IReactor,
|
|
52
53
|
app: IApplication,
|
|
53
|
-
console: IConsole,
|
|
54
54
|
rich_console: Console
|
|
55
55
|
) -> None:
|
|
56
56
|
"""
|
|
@@ -74,10 +74,7 @@ class Scheduler(ISchedule):
|
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
76
|
# Store the application instance for configuration access.
|
|
77
|
-
self.__app = app
|
|
78
|
-
|
|
79
|
-
# Store the console instance for output operations.
|
|
80
|
-
self.__console = console
|
|
77
|
+
self.__app: IApplication = app
|
|
81
78
|
|
|
82
79
|
# Store the rich console instance for advanced output formatting.
|
|
83
80
|
self.__rich_console = rich_console
|
|
@@ -99,7 +96,7 @@ class Scheduler(ISchedule):
|
|
|
99
96
|
self.__logger: ILogger = self.__app.make('x-orionis.services.log.log_service')
|
|
100
97
|
|
|
101
98
|
# Store the reactor instance for command management.
|
|
102
|
-
self.__reactor = reactor
|
|
99
|
+
self.__reactor: IReactor = reactor
|
|
103
100
|
|
|
104
101
|
# Retrieve and store all available commands from the reactor.
|
|
105
102
|
self.__available_commands = self.__getCommands()
|
|
@@ -113,10 +110,12 @@ class Scheduler(ISchedule):
|
|
|
113
110
|
# Initialize the listeners dictionary to manage event listeners.
|
|
114
111
|
self.__listeners: Dict[str, callable] = {}
|
|
115
112
|
|
|
113
|
+
# Initialize set to track jobs paused by pauseEverythingAt
|
|
114
|
+
self.__paused_by_pause_everything: set = set()
|
|
115
|
+
|
|
116
116
|
# Add this line to the existing __init__ method
|
|
117
117
|
self._stop_event: Optional[asyncio.Event] = None
|
|
118
118
|
|
|
119
|
-
|
|
120
119
|
def __getCurrentTime(
|
|
121
120
|
self
|
|
122
121
|
) -> str:
|
|
@@ -137,6 +136,9 @@ class Scheduler(ISchedule):
|
|
|
137
136
|
tz = pytz.timezone(self.__app.config("app.timezone", "UTC"))
|
|
138
137
|
now = datetime.now(tz)
|
|
139
138
|
|
|
139
|
+
# Log the timezone assignment for debugging purposes
|
|
140
|
+
self.__logger.info(f"Timezone assigned to the scheduler: {self.__app.config("app.timezone", "UTC")}")
|
|
141
|
+
|
|
140
142
|
# Format the current time as a string
|
|
141
143
|
return now.strftime("%Y-%m-%d %H:%M:%S")
|
|
142
144
|
|
|
@@ -267,7 +269,7 @@ class Scheduler(ISchedule):
|
|
|
267
269
|
"""
|
|
268
270
|
|
|
269
271
|
# Prevent adding new commands while the scheduler is running
|
|
270
|
-
if self.
|
|
272
|
+
if self.isRunning():
|
|
271
273
|
raise CLIOrionisRuntimeError("Cannot add new commands while the scheduler is running.")
|
|
272
274
|
|
|
273
275
|
# Validate that the command signature is a non-empty string
|
|
@@ -282,8 +284,10 @@ class Scheduler(ISchedule):
|
|
|
282
284
|
if not self.__isAvailable(signature):
|
|
283
285
|
raise CLIOrionisValueError(f"The command '{signature}' is not available or does not exist.")
|
|
284
286
|
|
|
285
|
-
#
|
|
287
|
+
# Import Event here to avoid circular dependency issues
|
|
286
288
|
from orionis.console.tasks.event import Event
|
|
289
|
+
|
|
290
|
+
# Store the command and its arguments for scheduling
|
|
287
291
|
self.__events[signature] = Event(
|
|
288
292
|
signature=signature,
|
|
289
293
|
args=args or [],
|
|
@@ -315,8 +319,6 @@ class Scheduler(ISchedule):
|
|
|
315
319
|
"""
|
|
316
320
|
|
|
317
321
|
self.__scheduler.add_listener(self.__startedListener, EVENT_SCHEDULER_STARTED)
|
|
318
|
-
self.__scheduler.add_listener(self.__pausedListener, EVENT_SCHEDULER_PAUSED)
|
|
319
|
-
self.__scheduler.add_listener(self.__resumedListener, EVENT_SCHEDULER_RESUMED)
|
|
320
322
|
self.__scheduler.add_listener(self.__shutdownListener, EVENT_SCHEDULER_SHUTDOWN)
|
|
321
323
|
self.__scheduler.add_listener(self.__errorListener, EVENT_JOB_ERROR)
|
|
322
324
|
self.__scheduler.add_listener(self.__submittedListener, EVENT_JOB_SUBMITTED)
|
|
@@ -367,6 +369,8 @@ class Scheduler(ISchedule):
|
|
|
367
369
|
|
|
368
370
|
# Check if a listener is registered for the specified event
|
|
369
371
|
if scheduler_event in self.__listeners:
|
|
372
|
+
|
|
373
|
+
# Retrieve the listener for the specified event
|
|
370
374
|
listener = self.__listeners[scheduler_event]
|
|
371
375
|
|
|
372
376
|
# Ensure the listener is callable before invoking it
|
|
@@ -376,26 +380,55 @@ class Scheduler(ISchedule):
|
|
|
376
380
|
|
|
377
381
|
# If the listener is a coroutine, schedule it as an asyncio task
|
|
378
382
|
if asyncio.iscoroutinefunction(listener):
|
|
383
|
+
|
|
384
|
+
# Try to get the running event loop
|
|
379
385
|
try:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
|
|
389
|
+
# Try to get the current running event loop
|
|
390
|
+
loop = asyncio.get_running_loop()
|
|
391
|
+
loop.create_task(listener(event_data, self))
|
|
392
|
+
|
|
393
|
+
except RuntimeError:
|
|
394
|
+
|
|
395
|
+
# If no event loop is running, create a new one
|
|
396
|
+
loop = asyncio.new_event_loop()
|
|
397
|
+
asyncio.set_event_loop(loop)
|
|
398
|
+
loop.create_task(listener(event_data, self))
|
|
399
|
+
|
|
400
|
+
# If no event loop is running, log a warning instead of creating one
|
|
383
401
|
except RuntimeError:
|
|
384
|
-
|
|
385
|
-
|
|
402
|
+
|
|
403
|
+
# Message indicating that the listener could not be invoked
|
|
404
|
+
error_msg = f"Cannot run async listener for '{scheduler_event}': no event loop running"
|
|
405
|
+
|
|
406
|
+
# Log the error message
|
|
407
|
+
self.__logger.error(error_msg)
|
|
408
|
+
|
|
409
|
+
# Raise an error to inform the caller that the listener could not be invoked
|
|
410
|
+
raise CLIOrionisRuntimeError(error_msg)
|
|
411
|
+
|
|
386
412
|
# Otherwise, invoke the listener directly as a regular function
|
|
387
413
|
else:
|
|
414
|
+
|
|
415
|
+
# Call the regular listener function directly
|
|
388
416
|
listener(event_data, self)
|
|
389
417
|
|
|
390
418
|
except Exception as e:
|
|
391
419
|
|
|
392
|
-
#
|
|
393
|
-
|
|
420
|
+
# Re-raise CLIOrionisRuntimeError exceptions
|
|
421
|
+
if isinstance(e, CLIOrionisRuntimeError):
|
|
422
|
+
raise e
|
|
423
|
+
|
|
424
|
+
# Construct the error message
|
|
425
|
+
error_msg = f"An error occurred while invoking the listener for event '{scheduler_event}': {str(e)}"
|
|
426
|
+
|
|
427
|
+
# Log the error message
|
|
428
|
+
self.__logger.error(error_msg)
|
|
394
429
|
|
|
395
430
|
# Raise a runtime error if listener invocation fails
|
|
396
|
-
raise CLIOrionisRuntimeError(
|
|
397
|
-
f"An error occurred while invoking the listener for event '{scheduler_event}': {str(e)}"
|
|
398
|
-
)
|
|
431
|
+
raise CLIOrionisRuntimeError(error_msg)
|
|
399
432
|
|
|
400
433
|
def __taskCallableListener(
|
|
401
434
|
self,
|
|
@@ -453,19 +486,64 @@ class Scheduler(ISchedule):
|
|
|
453
486
|
|
|
454
487
|
# Check if the listener has a method corresponding to the event type
|
|
455
488
|
if hasattr(listener, scheduler_event) and callable(getattr(listener, scheduler_event)):
|
|
489
|
+
|
|
490
|
+
# Retrieve the method from the listener
|
|
456
491
|
listener_method = getattr(listener, scheduler_event)
|
|
457
492
|
|
|
493
|
+
# Try to invoke the listener method
|
|
458
494
|
try:
|
|
495
|
+
|
|
459
496
|
# Invoke the listener method, handling both coroutine and regular functions
|
|
460
497
|
if asyncio.iscoroutinefunction(listener_method):
|
|
461
|
-
|
|
462
|
-
|
|
498
|
+
|
|
499
|
+
# Try to get the running event loop
|
|
500
|
+
try:
|
|
501
|
+
|
|
502
|
+
try:
|
|
503
|
+
|
|
504
|
+
# Try to get the current running event loop
|
|
505
|
+
loop = asyncio.get_running_loop()
|
|
506
|
+
loop.create_task(listener_method(event_data, self))
|
|
507
|
+
|
|
508
|
+
except RuntimeError:
|
|
509
|
+
|
|
510
|
+
# If no event loop is running, create a new one
|
|
511
|
+
loop = asyncio.new_event_loop()
|
|
512
|
+
asyncio.set_event_loop(loop)
|
|
513
|
+
loop.create_task(listener_method(event_data, self))
|
|
514
|
+
|
|
515
|
+
# If no event loop is running, log a warning
|
|
516
|
+
except RuntimeError:
|
|
517
|
+
|
|
518
|
+
# Construct the error message
|
|
519
|
+
error_msg = f"Cannot run async listener for '{scheduler_event}' on job '{event_data.job_id}': no event loop running"
|
|
520
|
+
|
|
521
|
+
# Log the error message
|
|
522
|
+
self.__logger.error(error_msg)
|
|
523
|
+
|
|
524
|
+
# Raise an error to inform the caller that the listener could not be invoked
|
|
525
|
+
raise CLIOrionisRuntimeError(error_msg)
|
|
526
|
+
|
|
527
|
+
# Call the regular listener method directly
|
|
463
528
|
else:
|
|
464
|
-
|
|
529
|
+
|
|
530
|
+
# Invoke the listener method directly
|
|
465
531
|
listener_method(event_data, self)
|
|
532
|
+
|
|
466
533
|
except Exception as e:
|
|
467
|
-
|
|
468
|
-
|
|
534
|
+
|
|
535
|
+
# Re-raise CLIOrionisRuntimeError exceptions
|
|
536
|
+
if isinstance(e, CLIOrionisRuntimeError):
|
|
537
|
+
raise e
|
|
538
|
+
|
|
539
|
+
# Construct the error message
|
|
540
|
+
error_msg = (f"An error occurred while invoking the listener for event '{scheduler_event}' on job '{event_data.job_id}': {str(e)}")
|
|
541
|
+
|
|
542
|
+
# Log the error message
|
|
543
|
+
self.__logger.error(error_msg)
|
|
544
|
+
|
|
545
|
+
# Raise a runtime error if listener invocation fails
|
|
546
|
+
raise CLIOrionisRuntimeError(error_msg)
|
|
469
547
|
|
|
470
548
|
def __startedListener(
|
|
471
549
|
self,
|
|
@@ -495,9 +573,6 @@ class Scheduler(ISchedule):
|
|
|
495
573
|
# Get the current time in the configured timezone
|
|
496
574
|
now = self.__getCurrentTime()
|
|
497
575
|
|
|
498
|
-
# Log an informational message indicating that the scheduler has started
|
|
499
|
-
self.__logger.info(f"Orionis Scheduler started successfully at {now}.")
|
|
500
|
-
|
|
501
576
|
# Display a start message for the scheduler worker on the rich console
|
|
502
577
|
# Add a blank line for better formatting
|
|
503
578
|
self.__rich_console.line()
|
|
@@ -522,6 +597,9 @@ class Scheduler(ISchedule):
|
|
|
522
597
|
# Check if a listener is registered for the scheduler started event
|
|
523
598
|
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_STARTED)
|
|
524
599
|
|
|
600
|
+
# Log an informational message indicating that the scheduler has started
|
|
601
|
+
self.__logger.info(f"Orionis Scheduler started successfully at {now}.")
|
|
602
|
+
|
|
525
603
|
def __pausedListener(
|
|
526
604
|
self,
|
|
527
605
|
event: SchedulerPaused
|
|
@@ -550,15 +628,12 @@ class Scheduler(ISchedule):
|
|
|
550
628
|
# Get the current time in the configured timezone
|
|
551
629
|
now = self.__getCurrentTime()
|
|
552
630
|
|
|
553
|
-
# Create a paused message
|
|
554
|
-
message = f"Orionis Scheduler paused successfully at {now}."
|
|
555
|
-
|
|
556
|
-
# Log an informational message indicating that the scheduler has been paused
|
|
557
|
-
self.__logger.info(message)
|
|
558
|
-
|
|
559
631
|
# Check if a listener is registered for the scheduler paused event
|
|
560
632
|
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_PAUSED)
|
|
561
633
|
|
|
634
|
+
# Log an informational message indicating that the scheduler has been paused
|
|
635
|
+
self.__logger.info(f"Orionis Scheduler paused successfully at {now}.")
|
|
636
|
+
|
|
562
637
|
def __resumedListener(
|
|
563
638
|
self,
|
|
564
639
|
event: SchedulerResumed
|
|
@@ -587,15 +662,12 @@ class Scheduler(ISchedule):
|
|
|
587
662
|
# Get the current time in the configured timezone
|
|
588
663
|
now = self.__getCurrentTime()
|
|
589
664
|
|
|
590
|
-
# Create a resumed message
|
|
591
|
-
message = f"Orionis Scheduler resumed successfully at {now}."
|
|
592
|
-
|
|
593
|
-
# Log an informational message indicating that the scheduler has resumed
|
|
594
|
-
self.__logger.info(message)
|
|
595
|
-
|
|
596
665
|
# Check if a listener is registered for the scheduler resumed event
|
|
597
666
|
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_RESUMED)
|
|
598
667
|
|
|
668
|
+
# Log an informational message indicating that the scheduler has resumed
|
|
669
|
+
self.__logger.info(f"Orionis Scheduler resumed successfully at {now}.")
|
|
670
|
+
|
|
599
671
|
def __shutdownListener(
|
|
600
672
|
self,
|
|
601
673
|
event: SchedulerShutdown
|
|
@@ -624,15 +696,12 @@ class Scheduler(ISchedule):
|
|
|
624
696
|
# Get the current time in the configured timezone
|
|
625
697
|
now = self.__getCurrentTime()
|
|
626
698
|
|
|
627
|
-
# Create a shutdown message
|
|
628
|
-
message = f"Orionis Scheduler shut down successfully at {now}."
|
|
629
|
-
|
|
630
|
-
# Log an informational message indicating that the scheduler has shut down
|
|
631
|
-
self.__logger.info(message)
|
|
632
|
-
|
|
633
699
|
# Check if a listener is registered for the scheduler shutdown event
|
|
634
700
|
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_SHUTDOWN)
|
|
635
701
|
|
|
702
|
+
# Log an informational message indicating that the scheduler has shut down
|
|
703
|
+
self.__logger.info(f"Orionis Scheduler shut down successfully at {now}.")
|
|
704
|
+
|
|
636
705
|
def __errorListener(
|
|
637
706
|
self,
|
|
638
707
|
event: JobError
|
|
@@ -658,11 +727,8 @@ class Scheduler(ISchedule):
|
|
|
658
727
|
and listener invocation for the job error event.
|
|
659
728
|
"""
|
|
660
729
|
|
|
661
|
-
# Create an error message
|
|
662
|
-
message = f"Task {event.job_id} raised an exception: {event.exception}"
|
|
663
|
-
|
|
664
730
|
# Log an error message indicating that the job raised an exception
|
|
665
|
-
self.__logger.error(
|
|
731
|
+
self.__logger.error(f"Task {event.job_id} raised an exception: {event.exception}")
|
|
666
732
|
|
|
667
733
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
668
734
|
self.__taskCallableListener(event, ListeningEvent.JOB_ON_FAILURE)
|
|
@@ -695,11 +761,8 @@ class Scheduler(ISchedule):
|
|
|
695
761
|
and listener invocation for the job submission event.
|
|
696
762
|
"""
|
|
697
763
|
|
|
698
|
-
# Create a submission message
|
|
699
|
-
message = f"Task {event.job_id} submitted to executor."
|
|
700
|
-
|
|
701
764
|
# Log an informational message indicating that the job has been submitted
|
|
702
|
-
self.__logger.info(
|
|
765
|
+
self.__logger.info(f"Task {event.job_id} submitted to executor.")
|
|
703
766
|
|
|
704
767
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
705
768
|
self.__taskCallableListener(event, ListeningEvent.JOB_BEFORE)
|
|
@@ -730,11 +793,8 @@ class Scheduler(ISchedule):
|
|
|
730
793
|
and listener invocation for the job execution event.
|
|
731
794
|
"""
|
|
732
795
|
|
|
733
|
-
# Create an execution message
|
|
734
|
-
message = f"Task {event.job_id} executed."
|
|
735
|
-
|
|
736
796
|
# Log an informational message indicating that the job has been executed
|
|
737
|
-
self.__logger.info(
|
|
797
|
+
self.__logger.info(f"Task {event.job_id} executed.")
|
|
738
798
|
|
|
739
799
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
740
800
|
self.__taskCallableListener(event, ListeningEvent.JOB_AFTER)
|
|
@@ -765,11 +825,8 @@ class Scheduler(ISchedule):
|
|
|
765
825
|
and listener invocation for the missed job event.
|
|
766
826
|
"""
|
|
767
827
|
|
|
768
|
-
# Create a missed job message
|
|
769
|
-
message = f"Task {event.job_id} was missed. It was scheduled to run at {event.scheduled_run_time}."
|
|
770
|
-
|
|
771
828
|
# Log a warning indicating that the job was missed
|
|
772
|
-
self.__logger.warning(
|
|
829
|
+
self.__logger.warning(f"Task {event.job_id} was missed. It was scheduled to run at {event.scheduled_run_time}.")
|
|
773
830
|
|
|
774
831
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
775
832
|
self.__taskCallableListener(event, ListeningEvent.JOB_ON_MISSED)
|
|
@@ -800,11 +857,8 @@ class Scheduler(ISchedule):
|
|
|
800
857
|
and listener invocation for the job max instances event.
|
|
801
858
|
"""
|
|
802
859
|
|
|
803
|
-
# Create a max instances error message
|
|
804
|
-
message = f"Task {event.job_id} exceeded maximum instances"
|
|
805
|
-
|
|
806
860
|
# Log an error message indicating that the job exceeded maximum instances
|
|
807
|
-
self.__logger.error(
|
|
861
|
+
self.__logger.error(f"Task {event.job_id} exceeded maximum instances")
|
|
808
862
|
|
|
809
863
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
810
864
|
self.__taskCallableListener(event, ListeningEvent.JOB_ON_MAXINSTANCES)
|
|
@@ -835,16 +889,12 @@ class Scheduler(ISchedule):
|
|
|
835
889
|
and listener invocation for the job modified event.
|
|
836
890
|
"""
|
|
837
891
|
|
|
838
|
-
# Create a modified message
|
|
839
|
-
message = f"Task {event.job_id} has been modified."
|
|
840
|
-
|
|
841
|
-
# Log an informational message indicating that the job has been modified
|
|
842
|
-
self.__logger.info(message)
|
|
843
|
-
|
|
844
892
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
845
893
|
if event.next_run_time is None:
|
|
894
|
+
self.__logger.info(f"Task {event.job_id} has been paused.")
|
|
846
895
|
self.__taskCallableListener(event, ListeningEvent.JOB_ON_PAUSED)
|
|
847
896
|
else:
|
|
897
|
+
self.__logger.info(f"Task {event.job_id} has been resumed.")
|
|
848
898
|
self.__taskCallableListener(event, ListeningEvent.JOB_ON_RESUMED)
|
|
849
899
|
|
|
850
900
|
def __removedListener(
|
|
@@ -872,11 +922,8 @@ class Scheduler(ISchedule):
|
|
|
872
922
|
listener for the job removal event.
|
|
873
923
|
"""
|
|
874
924
|
|
|
875
|
-
# Create a message indicating that the job has been removed
|
|
876
|
-
message = f"Task {event.job_id} has been removed."
|
|
877
|
-
|
|
878
925
|
# Log the removal of the job
|
|
879
|
-
self.__logger.info(
|
|
926
|
+
self.__logger.info(f"Task {event.job_id} has been removed.")
|
|
880
927
|
|
|
881
928
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
882
929
|
self.__taskCallableListener(event, ListeningEvent.JOB_ON_REMOVED)
|
|
@@ -903,27 +950,43 @@ class Scheduler(ISchedule):
|
|
|
903
950
|
# Iterate through all scheduled jobs in the AsyncIOScheduler
|
|
904
951
|
for signature, event in self.__events.items():
|
|
905
952
|
|
|
906
|
-
|
|
907
|
-
|
|
953
|
+
try:
|
|
954
|
+
# Convert the event to its entity representation
|
|
955
|
+
entity = event.toEntity()
|
|
956
|
+
|
|
957
|
+
# Add the job to the internal jobs list
|
|
958
|
+
self.__jobs.append(entity)
|
|
959
|
+
|
|
960
|
+
# Create a unique key for the job based on its signature
|
|
961
|
+
def create_job_func(cmd, args_list):
|
|
962
|
+
return lambda: self.__reactor.call(cmd, args_list)
|
|
963
|
+
|
|
964
|
+
# Add the job to the scheduler with the specified trigger and parameters
|
|
965
|
+
self.__scheduler.add_job(
|
|
966
|
+
func=create_job_func(signature, list(entity.args)),
|
|
967
|
+
trigger=entity.trigger,
|
|
968
|
+
id=signature,
|
|
969
|
+
name=signature,
|
|
970
|
+
replace_existing=True
|
|
971
|
+
)
|
|
908
972
|
|
|
909
|
-
|
|
910
|
-
|
|
973
|
+
# If a listener is associated with the event, register it
|
|
974
|
+
if entity.listener:
|
|
975
|
+
self.setListener(signature, entity.listener)
|
|
911
976
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
return lambda: self.__reactor.call(cmd, args_list)
|
|
977
|
+
# Log the successful loading of the scheduled event
|
|
978
|
+
self.__logger.debug(f"Scheduled event '{signature}' loaded successfully.")
|
|
915
979
|
|
|
916
|
-
|
|
917
|
-
func=create_job_func(signature, list(entity.args)),
|
|
918
|
-
trigger=entity.trigger,
|
|
919
|
-
id=signature,
|
|
920
|
-
name=signature,
|
|
921
|
-
replace_existing=True
|
|
922
|
-
)
|
|
980
|
+
except Exception as e:
|
|
923
981
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
982
|
+
# Construct the error message
|
|
983
|
+
error_msg = f"Failed to load scheduled event '{signature}': {str(e)}"
|
|
984
|
+
|
|
985
|
+
# Log the error message
|
|
986
|
+
self.__logger.error(error_msg)
|
|
987
|
+
|
|
988
|
+
# Raise a runtime error if loading the scheduled event fails
|
|
989
|
+
raise CLIOrionisRuntimeError(error_msg)
|
|
927
990
|
|
|
928
991
|
def setListener(
|
|
929
992
|
self,
|
|
@@ -1001,7 +1064,7 @@ class Scheduler(ISchedule):
|
|
|
1001
1064
|
Raises
|
|
1002
1065
|
------
|
|
1003
1066
|
ValueError
|
|
1004
|
-
If the 'at' parameter is not a valid datetime object.
|
|
1067
|
+
If the 'at' parameter is not a valid datetime object or is not in the future.
|
|
1005
1068
|
CLIOrionisRuntimeError
|
|
1006
1069
|
If the scheduler is not running or if an error occurs during job scheduling.
|
|
1007
1070
|
"""
|
|
@@ -1010,27 +1073,78 @@ class Scheduler(ISchedule):
|
|
|
1010
1073
|
if not isinstance(at, datetime):
|
|
1011
1074
|
raise ValueError("The 'at' parameter must be a datetime object.")
|
|
1012
1075
|
|
|
1013
|
-
#
|
|
1014
|
-
|
|
1015
|
-
|
|
1076
|
+
# Define a function to pause the scheduler
|
|
1077
|
+
def schedule_pause():
|
|
1078
|
+
|
|
1079
|
+
# Only pause jobs if the scheduler is currently running
|
|
1080
|
+
if self.isRunning():
|
|
1081
|
+
|
|
1082
|
+
# Clear the set of previously paused jobs
|
|
1083
|
+
self.__paused_by_pause_everything.clear()
|
|
1084
|
+
|
|
1085
|
+
# Get all jobs from the scheduler
|
|
1086
|
+
all_jobs = self.__scheduler.get_jobs()
|
|
1087
|
+
|
|
1088
|
+
# Filter out system jobs (pause, resume, shutdown tasks)
|
|
1089
|
+
system_job_ids = {
|
|
1090
|
+
"scheduler_pause_at",
|
|
1091
|
+
"scheduler_resume_at",
|
|
1092
|
+
"scheduler_shutdown_at"
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
# Pause only user jobs, not system jobs
|
|
1096
|
+
for job in all_jobs:
|
|
1097
|
+
|
|
1098
|
+
# Pause the job only if it is not a system job
|
|
1099
|
+
if job.id not in system_job_ids:
|
|
1100
|
+
|
|
1101
|
+
# Pause the job and add its ID to the set of paused jobs
|
|
1102
|
+
try:
|
|
1103
|
+
|
|
1104
|
+
# Pause the job in the scheduler
|
|
1105
|
+
self.__scheduler.pause_job(job.id)
|
|
1106
|
+
self.__paused_by_pause_everything.add(job.id)
|
|
1107
|
+
|
|
1108
|
+
# Excute the paused listener
|
|
1109
|
+
self.__pausedListener(JobPause(
|
|
1110
|
+
code=EVENT_SCHEDULER_PAUSED,
|
|
1111
|
+
job_id=job.id if hasattr(job, 'id') else None,
|
|
1112
|
+
alias=job.name if hasattr(job, 'name') else None,
|
|
1113
|
+
jobstore=job.jobstore if hasattr(job, 'jobstore') else None,
|
|
1114
|
+
))
|
|
1115
|
+
|
|
1116
|
+
except Exception as e:
|
|
1117
|
+
|
|
1118
|
+
# Log a warning if pausing a job fails, but continue with others
|
|
1119
|
+
self.__logger.warning(f"Failed to pause job '{job.id}': {str(e)}")
|
|
1120
|
+
|
|
1121
|
+
# Log that all user jobs have been paused
|
|
1122
|
+
self.__logger.info("All user jobs have been paused. System jobs remain active.")
|
|
1016
1123
|
|
|
1017
1124
|
try:
|
|
1125
|
+
|
|
1018
1126
|
# Remove any existing pause job to avoid conflicts
|
|
1019
1127
|
try:
|
|
1020
|
-
self.__scheduler.remove_job(
|
|
1128
|
+
self.__scheduler.remove_job("scheduler_pause_at")
|
|
1129
|
+
|
|
1130
|
+
# If the job doesn't exist, it's fine to proceed
|
|
1021
1131
|
except:
|
|
1022
|
-
pass
|
|
1132
|
+
pass
|
|
1023
1133
|
|
|
1024
1134
|
# Add a job to the scheduler to pause it at the specified datetime
|
|
1025
1135
|
self.__scheduler.add_job(
|
|
1026
|
-
func=
|
|
1136
|
+
func=schedule_pause, # Function to pause the scheduler
|
|
1027
1137
|
trigger=DateTrigger(run_date=at), # Trigger type is 'date' for one-time execution
|
|
1028
|
-
id=
|
|
1138
|
+
id="scheduler_pause_at", # Unique job ID for pausing the scheduler
|
|
1029
1139
|
name="Pause Scheduler", # Descriptive name for the job
|
|
1030
1140
|
replace_existing=True # Replace any existing job with the same ID
|
|
1031
1141
|
)
|
|
1032
1142
|
|
|
1143
|
+
# Log the scheduled pause
|
|
1144
|
+
self.__logger.info(f"Scheduler pause scheduled for {at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
1145
|
+
|
|
1033
1146
|
except Exception as e:
|
|
1147
|
+
|
|
1034
1148
|
# Handle exceptions that may occur during job scheduling
|
|
1035
1149
|
raise CLIOrionisRuntimeError(f"Failed to schedule scheduler pause: {str(e)}") from e
|
|
1036
1150
|
|
|
@@ -1060,7 +1174,7 @@ class Scheduler(ISchedule):
|
|
|
1060
1174
|
Raises
|
|
1061
1175
|
------
|
|
1062
1176
|
ValueError
|
|
1063
|
-
If the 'at' parameter is not a valid datetime object.
|
|
1177
|
+
If the 'at' parameter is not a valid datetime object or is not in the future.
|
|
1064
1178
|
CLIOrionisRuntimeError
|
|
1065
1179
|
If the scheduler is not running or if an error occurs during job scheduling.
|
|
1066
1180
|
"""
|
|
@@ -1069,33 +1183,72 @@ class Scheduler(ISchedule):
|
|
|
1069
1183
|
if not isinstance(at, datetime):
|
|
1070
1184
|
raise ValueError("The 'at' parameter must be a datetime object.")
|
|
1071
1185
|
|
|
1072
|
-
#
|
|
1073
|
-
|
|
1074
|
-
|
|
1186
|
+
# Define a function to resume the scheduler
|
|
1187
|
+
def schedule_resume():
|
|
1188
|
+
|
|
1189
|
+
# Only resume jobs if the scheduler is currently running
|
|
1190
|
+
if self.isRunning():
|
|
1191
|
+
|
|
1192
|
+
# Resume only jobs that were paused by pauseEverythingAt
|
|
1193
|
+
if self.__paused_by_pause_everything:
|
|
1194
|
+
|
|
1195
|
+
# Iterate through the set of paused job IDs and resume each one
|
|
1196
|
+
for job_id in list(self.__paused_by_pause_everything):
|
|
1197
|
+
|
|
1198
|
+
try:
|
|
1199
|
+
|
|
1200
|
+
# Resume the job and log the action
|
|
1201
|
+
self.__scheduler.resume_job(job_id)
|
|
1202
|
+
self.__logger.info(f"User job '{job_id}' has been resumed.")
|
|
1203
|
+
|
|
1204
|
+
# Excute the resumed listener
|
|
1205
|
+
self.__resumedListener(JobResume(
|
|
1206
|
+
code=EVENT_SCHEDULER_RESUMED,
|
|
1207
|
+
job_id=job_id
|
|
1208
|
+
))
|
|
1209
|
+
|
|
1210
|
+
except Exception as e:
|
|
1211
|
+
|
|
1212
|
+
# Log a warning if resuming a job fails, but continue with others
|
|
1213
|
+
self.__logger.warning(f"Failed to resume job '{job_id}': {str(e)}")
|
|
1214
|
+
|
|
1215
|
+
# Clear the set after resuming all jobs
|
|
1216
|
+
self.__paused_by_pause_everything.clear()
|
|
1217
|
+
|
|
1218
|
+
# Log that all previously paused jobs have been resumed
|
|
1219
|
+
self.__logger.info("All previously paused user jobs have been resumed.")
|
|
1075
1220
|
|
|
1076
1221
|
try:
|
|
1222
|
+
|
|
1077
1223
|
# Remove any existing resume job to avoid conflicts
|
|
1078
1224
|
try:
|
|
1079
|
-
self.__scheduler.remove_job(
|
|
1225
|
+
self.__scheduler.remove_job("scheduler_resume_at")
|
|
1226
|
+
|
|
1227
|
+
# If the job doesn't exist, it's fine to proceed
|
|
1080
1228
|
except:
|
|
1081
|
-
pass
|
|
1229
|
+
pass
|
|
1082
1230
|
|
|
1083
1231
|
# Add a job to the scheduler to resume it at the specified datetime
|
|
1084
1232
|
self.__scheduler.add_job(
|
|
1085
|
-
func=
|
|
1233
|
+
func=schedule_resume, # Function to resume the scheduler
|
|
1086
1234
|
trigger=DateTrigger(run_date=at), # Trigger type is 'date' for one-time execution
|
|
1087
|
-
id=
|
|
1235
|
+
id="scheduler_resume_at", # Unique job ID for resuming the scheduler
|
|
1088
1236
|
name="Resume Scheduler", # Descriptive name for the job
|
|
1089
1237
|
replace_existing=True # Replace any existing job with the same ID
|
|
1090
1238
|
)
|
|
1091
1239
|
|
|
1240
|
+
# Log the scheduled resume
|
|
1241
|
+
self.__logger.info(f"Scheduler resume scheduled for {at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
1242
|
+
|
|
1092
1243
|
except Exception as e:
|
|
1244
|
+
|
|
1093
1245
|
# Handle exceptions that may occur during job scheduling
|
|
1094
1246
|
raise CLIOrionisRuntimeError(f"Failed to schedule scheduler resume: {str(e)}") from e
|
|
1095
1247
|
|
|
1096
1248
|
def shutdownEverythingAt(
|
|
1097
1249
|
self,
|
|
1098
|
-
at: datetime
|
|
1250
|
+
at: datetime,
|
|
1251
|
+
wait: bool = True
|
|
1099
1252
|
) -> None:
|
|
1100
1253
|
"""
|
|
1101
1254
|
Schedule the scheduler to shut down all operations at a specific datetime.
|
|
@@ -1109,6 +1262,9 @@ class Scheduler(ISchedule):
|
|
|
1109
1262
|
at : datetime
|
|
1110
1263
|
The datetime at which the scheduler should be shut down. Must be a valid
|
|
1111
1264
|
datetime object.
|
|
1265
|
+
wait : bool, optional
|
|
1266
|
+
Whether to wait for currently running jobs to complete before shutdown.
|
|
1267
|
+
Default is True.
|
|
1112
1268
|
|
|
1113
1269
|
Returns
|
|
1114
1270
|
-------
|
|
@@ -1119,7 +1275,8 @@ class Scheduler(ISchedule):
|
|
|
1119
1275
|
Raises
|
|
1120
1276
|
------
|
|
1121
1277
|
ValueError
|
|
1122
|
-
If the 'at' parameter is not a valid datetime object
|
|
1278
|
+
If the 'at' parameter is not a valid datetime object or 'wait' is not boolean,
|
|
1279
|
+
or if the scheduled time is not in the future.
|
|
1123
1280
|
CLIOrionisRuntimeError
|
|
1124
1281
|
If the scheduler is not running or if an error occurs during job scheduling.
|
|
1125
1282
|
"""
|
|
@@ -1128,27 +1285,47 @@ class Scheduler(ISchedule):
|
|
|
1128
1285
|
if not isinstance(at, datetime):
|
|
1129
1286
|
raise ValueError("The 'at' parameter must be a datetime object.")
|
|
1130
1287
|
|
|
1131
|
-
#
|
|
1132
|
-
if not
|
|
1133
|
-
raise
|
|
1288
|
+
# Validate that the 'wait' parameter is a boolean
|
|
1289
|
+
if not isinstance(wait, bool):
|
|
1290
|
+
raise ValueError("The 'wait' parameter must be a boolean value.")
|
|
1291
|
+
|
|
1292
|
+
# Define a function to shut down the scheduler
|
|
1293
|
+
def schedule_shutdown():
|
|
1294
|
+
|
|
1295
|
+
# Only shut down the scheduler if it is currently running
|
|
1296
|
+
if self.isRunning():
|
|
1297
|
+
|
|
1298
|
+
# Execute the shutdown process
|
|
1299
|
+
self.__scheduler.shutdown(wait=wait)
|
|
1300
|
+
|
|
1301
|
+
# Signal the stop event to break the wait in start()
|
|
1302
|
+
if self._stop_event and not self._stop_event.is_set():
|
|
1303
|
+
self._stop_event.set()
|
|
1134
1304
|
|
|
1135
1305
|
try:
|
|
1306
|
+
|
|
1136
1307
|
# Remove any existing shutdown job to avoid conflicts
|
|
1137
1308
|
try:
|
|
1138
|
-
self.__scheduler.remove_job(
|
|
1309
|
+
self.__scheduler.remove_job("scheduler_shutdown_at")
|
|
1310
|
+
|
|
1311
|
+
# If the job doesn't exist, it's fine to proceed
|
|
1139
1312
|
except:
|
|
1140
|
-
pass
|
|
1313
|
+
pass
|
|
1141
1314
|
|
|
1142
1315
|
# Add a job to the scheduler to shut it down at the specified datetime
|
|
1143
1316
|
self.__scheduler.add_job(
|
|
1144
|
-
func=
|
|
1317
|
+
func=schedule_shutdown, # Function to shut down the scheduler
|
|
1145
1318
|
trigger=DateTrigger(run_date=at), # Trigger type is 'date' for one-time execution
|
|
1146
|
-
id=
|
|
1319
|
+
id="scheduler_shutdown_at", # Unique job ID for shutting down the scheduler
|
|
1147
1320
|
name="Shutdown Scheduler", # Descriptive name for the job
|
|
1148
1321
|
replace_existing=True # Replace any existing job with the same ID
|
|
1149
1322
|
)
|
|
1150
1323
|
|
|
1324
|
+
# Log the scheduled shutdown
|
|
1325
|
+
self.__logger.info(f"Scheduler shutdown scheduled for {at.strftime('%Y-%m-%d %H:%M:%S')} (wait={wait})")
|
|
1326
|
+
|
|
1151
1327
|
except Exception as e:
|
|
1328
|
+
|
|
1152
1329
|
# Handle exceptions that may occur during job scheduling
|
|
1153
1330
|
raise CLIOrionisRuntimeError(f"Failed to schedule scheduler shutdown: {str(e)}") from e
|
|
1154
1331
|
|
|
@@ -1173,6 +1350,7 @@ class Scheduler(ISchedule):
|
|
|
1173
1350
|
If the scheduler fails to start due to missing an asyncio event loop or other runtime issues.
|
|
1174
1351
|
"""
|
|
1175
1352
|
try:
|
|
1353
|
+
|
|
1176
1354
|
# Ensure the method is called within an asyncio event loop
|
|
1177
1355
|
loop = asyncio.get_running_loop()
|
|
1178
1356
|
|
|
@@ -1186,11 +1364,11 @@ class Scheduler(ISchedule):
|
|
|
1186
1364
|
self.__subscribeListeners()
|
|
1187
1365
|
|
|
1188
1366
|
# Start the scheduler if it is not already running
|
|
1189
|
-
if not self.
|
|
1367
|
+
if not self.isRunning():
|
|
1190
1368
|
self.__scheduler.start()
|
|
1191
1369
|
|
|
1192
1370
|
# Log that the scheduler is now active and waiting for events
|
|
1193
|
-
self.__logger.info("Scheduler is now active and waiting for events...")
|
|
1371
|
+
self.__logger.info("Orionis Scheduler is now active and waiting for events...")
|
|
1194
1372
|
|
|
1195
1373
|
try:
|
|
1196
1374
|
# Wait for the stop event to be set, which signals a shutdown
|
|
@@ -1198,27 +1376,32 @@ class Scheduler(ISchedule):
|
|
|
1198
1376
|
await self._stop_event.wait()
|
|
1199
1377
|
|
|
1200
1378
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
1379
|
+
|
|
1201
1380
|
# Handle graceful shutdown when an interruption signal is received
|
|
1202
1381
|
self.__logger.info("Received shutdown signal, stopping scheduler...")
|
|
1203
1382
|
await self.shutdown(wait=True)
|
|
1204
1383
|
|
|
1205
1384
|
except Exception as e:
|
|
1385
|
+
|
|
1206
1386
|
# Log and raise any unexpected exceptions during scheduler operation
|
|
1207
1387
|
self.__logger.error(f"Error during scheduler operation: {str(e)}")
|
|
1208
1388
|
raise CLIOrionisRuntimeError(f"Scheduler operation failed: {str(e)}") from e
|
|
1209
1389
|
|
|
1210
1390
|
finally:
|
|
1391
|
+
|
|
1211
1392
|
# Ensure the scheduler is shut down properly, even if an error occurs
|
|
1212
1393
|
if self.__scheduler.running:
|
|
1213
1394
|
await self.shutdown(wait=False)
|
|
1214
1395
|
|
|
1215
1396
|
except RuntimeError as e:
|
|
1397
|
+
|
|
1216
1398
|
# Handle the case where no asyncio event loop is running
|
|
1217
1399
|
if "no running event loop" in str(e):
|
|
1218
1400
|
raise CLIOrionisRuntimeError("Scheduler must be started within an asyncio event loop") from e
|
|
1219
1401
|
raise CLIOrionisRuntimeError(f"Failed to start the scheduler: {str(e)}") from e
|
|
1220
1402
|
|
|
1221
1403
|
except Exception as e:
|
|
1404
|
+
|
|
1222
1405
|
# Raise a runtime error for any other issues during startup
|
|
1223
1406
|
raise CLIOrionisRuntimeError(f"Failed to start the scheduler: {str(e)}") from e
|
|
1224
1407
|
|
|
@@ -1251,10 +1434,11 @@ class Scheduler(ISchedule):
|
|
|
1251
1434
|
raise ValueError("The 'wait' parameter must be a boolean value.")
|
|
1252
1435
|
|
|
1253
1436
|
# If the scheduler is not running, there's nothing to shut down
|
|
1254
|
-
if not self.
|
|
1437
|
+
if not self.isRunning():
|
|
1255
1438
|
return
|
|
1256
1439
|
|
|
1257
1440
|
try:
|
|
1441
|
+
|
|
1258
1442
|
# Log the shutdown process
|
|
1259
1443
|
self.__logger.info(f"Shutting down scheduler (wait={wait})...")
|
|
1260
1444
|
|
|
@@ -1269,9 +1453,12 @@ class Scheduler(ISchedule):
|
|
|
1269
1453
|
if wait:
|
|
1270
1454
|
await asyncio.sleep(0.1)
|
|
1271
1455
|
|
|
1456
|
+
# Log the successful shutdown
|
|
1272
1457
|
self.__logger.info("Scheduler shutdown completed successfully.")
|
|
1273
1458
|
|
|
1274
1459
|
except Exception as e:
|
|
1460
|
+
|
|
1461
|
+
# Handle exceptions that may occur during shutdown
|
|
1275
1462
|
raise CLIOrionisRuntimeError(f"Failed to shut down the scheduler: {str(e)}") from e
|
|
1276
1463
|
|
|
1277
1464
|
def pause(self, signature: str) -> bool:
|
|
@@ -1403,7 +1590,7 @@ class Scheduler(ISchedule):
|
|
|
1403
1590
|
|
|
1404
1591
|
# Iterate through the internal jobs list to find and remove the job
|
|
1405
1592
|
for job in self.__jobs:
|
|
1406
|
-
if job
|
|
1593
|
+
if job.signature == signature:
|
|
1407
1594
|
self.__jobs.remove(job) # Remove the job from the internal list
|
|
1408
1595
|
break
|
|
1409
1596
|
|
|
@@ -1469,4 +1656,192 @@ class Scheduler(ISchedule):
|
|
|
1469
1656
|
})
|
|
1470
1657
|
|
|
1471
1658
|
# Return the list of scheduled job details
|
|
1472
|
-
return events
|
|
1659
|
+
return events
|
|
1660
|
+
|
|
1661
|
+
def cancelScheduledPause(self) -> bool:
|
|
1662
|
+
"""
|
|
1663
|
+
Cancel a previously scheduled pause operation.
|
|
1664
|
+
|
|
1665
|
+
This method attempts to remove a job from the scheduler that was set to pause
|
|
1666
|
+
the scheduler at a specific time. If the job exists, it is removed, and a log entry
|
|
1667
|
+
is created to indicate the cancellation. If no such job exists, the method returns False.
|
|
1668
|
+
|
|
1669
|
+
Returns
|
|
1670
|
+
-------
|
|
1671
|
+
bool
|
|
1672
|
+
True if the scheduled pause job was successfully cancelled.
|
|
1673
|
+
False if no pause job was found or an error occurred during the cancellation process.
|
|
1674
|
+
"""
|
|
1675
|
+
try:
|
|
1676
|
+
|
|
1677
|
+
# Remove any listener associated with the pause event
|
|
1678
|
+
listener = ListeningEvent.SCHEDULER_PAUSED.value
|
|
1679
|
+
if listener in self.__listeners:
|
|
1680
|
+
del self.__listeners[listener]
|
|
1681
|
+
|
|
1682
|
+
# Attempt to remove the pause job with the specific ID
|
|
1683
|
+
# if it exists
|
|
1684
|
+
try:
|
|
1685
|
+
self.__scheduler.remove_job("scheduler_pause_at")
|
|
1686
|
+
finally:
|
|
1687
|
+
pass
|
|
1688
|
+
|
|
1689
|
+
# Log the successful cancellation of the pause operation
|
|
1690
|
+
self.__logger.info("Scheduled pause operation cancelled.")
|
|
1691
|
+
|
|
1692
|
+
# Return True to indicate the pause job was successfully cancelled
|
|
1693
|
+
return True
|
|
1694
|
+
|
|
1695
|
+
except:
|
|
1696
|
+
# Return False if the pause job does not exist or an error occurred
|
|
1697
|
+
return False
|
|
1698
|
+
|
|
1699
|
+
def cancelScheduledResume(self) -> bool:
|
|
1700
|
+
"""
|
|
1701
|
+
Cancel a previously scheduled resume operation.
|
|
1702
|
+
|
|
1703
|
+
This method attempts to remove a job from the scheduler that was set to resume
|
|
1704
|
+
the scheduler at a specific time. If the job exists, it is removed, and a log entry
|
|
1705
|
+
is created to indicate the cancellation. If no such job exists, the method returns False.
|
|
1706
|
+
|
|
1707
|
+
Returns
|
|
1708
|
+
-------
|
|
1709
|
+
bool
|
|
1710
|
+
True if the scheduled resume job was successfully cancelled.
|
|
1711
|
+
False if no resume job was found or an error occurred during the cancellation process.
|
|
1712
|
+
"""
|
|
1713
|
+
try:
|
|
1714
|
+
|
|
1715
|
+
# Remove any listener associated with the resume event
|
|
1716
|
+
listener = ListeningEvent.SCHEDULER_RESUMED.value
|
|
1717
|
+
if listener in self.__listeners:
|
|
1718
|
+
del self.__listeners[listener]
|
|
1719
|
+
|
|
1720
|
+
# Attempt to remove the resume job with the specific ID
|
|
1721
|
+
# if it exists
|
|
1722
|
+
try:
|
|
1723
|
+
self.__scheduler.remove_job("scheduler_resume_at")
|
|
1724
|
+
finally:
|
|
1725
|
+
pass
|
|
1726
|
+
|
|
1727
|
+
# Log the successful cancellation of the resume operation
|
|
1728
|
+
self.__logger.info("Scheduled resume operation cancelled.")
|
|
1729
|
+
|
|
1730
|
+
# Return True to indicate the resume job was successfully cancelled
|
|
1731
|
+
return True
|
|
1732
|
+
|
|
1733
|
+
except:
|
|
1734
|
+
|
|
1735
|
+
# Return False if the resume job does not exist or an error occurred
|
|
1736
|
+
return False
|
|
1737
|
+
|
|
1738
|
+
def cancelScheduledShutdown(self) -> bool:
|
|
1739
|
+
"""
|
|
1740
|
+
Cancel a previously scheduled shutdown operation.
|
|
1741
|
+
|
|
1742
|
+
This method attempts to remove a job from the scheduler that was set to shut down
|
|
1743
|
+
the scheduler at a specific time. If the job exists, it is removed, and a log entry
|
|
1744
|
+
is created to indicate the cancellation. If no such job exists, the method returns False.
|
|
1745
|
+
|
|
1746
|
+
Returns
|
|
1747
|
+
-------
|
|
1748
|
+
bool
|
|
1749
|
+
True if the scheduled shutdown job was successfully cancelled.
|
|
1750
|
+
False if no shutdown job was found or an error occurred during the cancellation process.
|
|
1751
|
+
"""
|
|
1752
|
+
try:
|
|
1753
|
+
|
|
1754
|
+
# Remove any listener associated with the shutdown event
|
|
1755
|
+
listener = ListeningEvent.SCHEDULER_SHUTDOWN.value
|
|
1756
|
+
if listener in self.__listeners:
|
|
1757
|
+
del self.__listeners[listener]
|
|
1758
|
+
|
|
1759
|
+
# Attempt to remove the shutdown job with the specific ID
|
|
1760
|
+
# if it exists
|
|
1761
|
+
try:
|
|
1762
|
+
self.__scheduler.remove_job("scheduler_shutdown_at")
|
|
1763
|
+
finally:
|
|
1764
|
+
pass
|
|
1765
|
+
|
|
1766
|
+
# Log the successful cancellation of the shutdown operation
|
|
1767
|
+
self.__logger.info("Scheduled shutdown operation cancelled.")
|
|
1768
|
+
|
|
1769
|
+
# Return True to indicate the shutdown job was successfully cancelled
|
|
1770
|
+
return True
|
|
1771
|
+
|
|
1772
|
+
except:
|
|
1773
|
+
|
|
1774
|
+
# Return False if the shutdown job does not exist or an error occurred
|
|
1775
|
+
return False
|
|
1776
|
+
|
|
1777
|
+
def isRunning(self) -> bool:
|
|
1778
|
+
"""
|
|
1779
|
+
Determine if the scheduler is currently active and running.
|
|
1780
|
+
|
|
1781
|
+
This method checks the internal state of the AsyncIOScheduler instance to determine
|
|
1782
|
+
whether it is currently running. The scheduler is considered running if it has been
|
|
1783
|
+
started and has not been paused or shut down.
|
|
1784
|
+
|
|
1785
|
+
Returns
|
|
1786
|
+
-------
|
|
1787
|
+
bool
|
|
1788
|
+
True if the scheduler is running, False otherwise.
|
|
1789
|
+
"""
|
|
1790
|
+
|
|
1791
|
+
# Return the running state of the scheduler
|
|
1792
|
+
return self.__scheduler.running
|
|
1793
|
+
|
|
1794
|
+
def forceStop(self) -> None:
|
|
1795
|
+
"""
|
|
1796
|
+
Forcefully stop the scheduler immediately without waiting for jobs to complete.
|
|
1797
|
+
|
|
1798
|
+
This method shuts down the AsyncIOScheduler instance without waiting for currently
|
|
1799
|
+
running jobs to finish. It is intended for emergency situations where an immediate
|
|
1800
|
+
stop is required. The method also signals the internal stop event to ensure that
|
|
1801
|
+
the scheduler's main loop is interrupted and the application can proceed with
|
|
1802
|
+
shutdown procedures.
|
|
1803
|
+
|
|
1804
|
+
Returns
|
|
1805
|
+
-------
|
|
1806
|
+
None
|
|
1807
|
+
This method does not return any value. It forcefully stops the scheduler and
|
|
1808
|
+
signals the stop event.
|
|
1809
|
+
"""
|
|
1810
|
+
|
|
1811
|
+
# Check if the scheduler is currently running
|
|
1812
|
+
if self.__scheduler.running:
|
|
1813
|
+
# Shut down the scheduler immediately without waiting for jobs to complete
|
|
1814
|
+
self.__scheduler.shutdown(wait=False)
|
|
1815
|
+
|
|
1816
|
+
# Check if the stop event exists and has not already been set
|
|
1817
|
+
if self._stop_event and not self._stop_event.is_set():
|
|
1818
|
+
# Signal the stop event to interrupt the scheduler's main loop
|
|
1819
|
+
self._stop_event.set()
|
|
1820
|
+
|
|
1821
|
+
def stop(self) -> None:
|
|
1822
|
+
"""
|
|
1823
|
+
Stop the scheduler synchronously by setting the stop event.
|
|
1824
|
+
|
|
1825
|
+
This method signals the scheduler to stop by setting the internal stop event.
|
|
1826
|
+
It can be called from non-async contexts to initiate a shutdown. If the asyncio
|
|
1827
|
+
event loop is running, the stop event is set in a thread-safe manner. Otherwise,
|
|
1828
|
+
the stop event is set directly.
|
|
1829
|
+
|
|
1830
|
+
Returns
|
|
1831
|
+
-------
|
|
1832
|
+
None
|
|
1833
|
+
This method does not return any value. It signals the scheduler to stop.
|
|
1834
|
+
"""
|
|
1835
|
+
|
|
1836
|
+
# Check if the stop event exists and has not already been set
|
|
1837
|
+
if self._stop_event and not self._stop_event.is_set():
|
|
1838
|
+
|
|
1839
|
+
# Get the current asyncio event loop
|
|
1840
|
+
loop = asyncio.get_event_loop()
|
|
1841
|
+
|
|
1842
|
+
# If the event loop is running, set the stop event in a thread-safe manner
|
|
1843
|
+
if loop.is_running():
|
|
1844
|
+
loop.call_soon_threadsafe(self._stop_event.set)
|
|
1845
|
+
else:
|
|
1846
|
+
# Otherwise, set the stop event directly
|
|
1847
|
+
self._stop_event.set()
|