2929from pyrunner .core .engine import ExecutionEngine
3030from pyrunner .core .config import Config
3131from pyrunner .core .register import NodeRegister
32+ from pyrunner .core .signal import SignalHandler , SIG_ABORT , SIG_PAUSE , SIG_PULSE
3233from pyrunner .version import __version__
3334
3435from datetime import datetime as datetime
@@ -41,23 +42,25 @@ def __init__(self, **kwargs):
4142 self ._environ = os .environ .copy ()
4243 self .config = Config ()
4344 self .notification = notification .EmailNotification ()
45+ self .signal_handler = SignalHandler (self .config )
4446
4547 self .serde_obj = serde .ListSerDe ()
4648 self .register = NodeRegister ()
4749 self .engine = ExecutionEngine ()
4850
4951 self ._init_params = {
50- 'config_file' : kwargs .get ('config_file' ),
51- 'proc_file' : kwargs .get ('proc_file' ),
52- 'restart' : kwargs .get ('restart' , False ),
53- 'cvar_list' : [],
54- 'exec_proc_name' : None ,
55- 'exec_only_list' : [],
56- 'exec_disable_list' : [],
57- 'exec_from_id' : None ,
58- 'exec_to_id' : None
52+ 'config_file' : kwargs .get ('config_file' ),
53+ 'proc_file' : kwargs .get ('proc_file' ),
54+ 'restart' : kwargs .get ('restart' , False ),
55+ 'cvar_list' : [],
56+ 'exec_proc_name' : None ,
57+ 'exec_only_list' : [],
58+ 'exec_disable_list' : [],
59+ 'exec_from_id' : None ,
60+ 'exec_to_id' : None
5961 }
6062
63+ # Lifecycle hooks
6164 self ._on_create_func = None
6265 self ._on_start_func = None
6366 self ._on_restart_func = None
@@ -66,11 +69,23 @@ def __init__(self, **kwargs):
6669 self ._on_destroy_func = None
6770
6871 self .parse_args ()
72+
73+ if self .dup_proc_is_running ():
74+ raise OSError ('Another process for "{}" is already running!' .format (self .config ['app_name' ]))
6975
7076 def reset_env (self ):
7177 os .environ .clear ()
7278 os .environ .update (self ._environ )
7379
80+ def dup_proc_is_running (self ):
81+ self .signal_handler .emit (SIG_PULSE )
82+ time .sleep (1.1 )
83+ if SIG_PULSE not in self .signal_handler .peek ():
84+ print (self .signal_handler .peek ())
85+ return True
86+ else :
87+ return False
88+
7489 def load_proc_file (self , proc_file , restart = False ):
7590 if not proc_file or not os .path .isfile (proc_file ):
7691 return False
@@ -121,7 +136,7 @@ def plugin_notification(self, obj):
121136 if not isinstance (obj , notification .Notification ): raise Exception ('Notification plugin must implement the Notification interface' )
122137 self .notification = obj
123138
124- # App lifecycle hooks
139+ # App lifecycle hooks/decorators
125140 def on_create (self , func ):
126141 self ._on_create_func = func
127142 def on_start (self , func ):
@@ -192,15 +207,15 @@ def run(self):
192207
193208 emit_notification = True
194209
195- # # App lifecycle - SUCCESS
210+ # App lifecycle - SUCCESS
196211 if retcode == 0 :
197212 if self ._on_success_func :
198213 self ._on_success_func ()
199214 if not self .config ['email_on_success' ]:
200215 print ('Skipping Email Notification: Property "email_on_success" is set to FALSE.' )
201216 emit_notification = False
202- # # App lifecycle - FAIL
203- else :
217+ # App lifecycle - FAIL (<0 is for ABORT or other interrupt)
218+ elif retcode > 0 :
204219 if self ._on_fail_func :
205220 self ._on_fail_func ()
206221 if not self .config ['email_on_fail' ]:
@@ -265,7 +280,12 @@ def zip_log_files(self, exit_status):
265280
266281 try :
267282
268- suffix = 'FAILURE' if exit_status else 'SUCCESS'
283+ if exit_status == - 1 :
284+ suffix = 'ABORT'
285+ elif exit_status > 0 :
286+ suffix = 'FAILURE'
287+ else :
288+ suffix = 'SUCCESS'
269289
270290 zip_file = "{}/{}_{}_{}.zip" .format (self .config ['log_dir' ], self .config ['app_name' ], constants .EXECUTION_TIMESTAMP , suffix )
271291 print ('Zipping Up Log Files to: {}' .format (zip_file ))
@@ -356,11 +376,13 @@ def exec_from(self, id) : return self.register.exec_from(id)
356376 def exec_disable (self , id_list ) : return self .register .exec_disable (id_list )
357377
358378 def parse_args (self ):
379+ abort = False
380+
359381 opt_list = 'c:l:n:e:x:N:D:A:t:drhiv'
360382 longopt_list = [
361- 'setup' , 'help' , 'nozip' , 'interactive' ,
383+ 'setup' , 'help' , 'nozip' , 'interactive' , 'abort' ,
362384 'restart' , 'version' , 'dryrun' , 'debug' ,
363- 'preserve-context' , 'dump-logs' , 'disable-exclusive -jobs' ,
385+ 'preserve-context' , 'dump-logs' , 'allow-duplicate -jobs' ,
364386 'email=' , 'email-on-fail=' , 'email-on-success=' , 'ef=' , 'es=' ,
365387 'env=' , 'cvar=' , 'context=' ,
366388 'to=' , 'from=' , 'descendants=' , 'ancestors=' ,
@@ -417,10 +439,12 @@ def parse_args(self):
417439 self .config ['tickrate' ] = int (arg )
418440 elif opt in ['--preserve-context' ]:
419441 self .preserve_context = True
420- elif opt in ['--disable-exclusive -jobs' ]:
421- self .disable_exclusive_jobs = True
442+ elif opt in ['--allow-duplicate -jobs' ]:
443+ self ._init_params [ 'allow_duplicate_jobs' ] = True
422444 elif opt in ['--exec-proc-name' ]:
423445 self ._init_params ['exec_proc_name' ] = arg
446+ elif opt == '--abort' :
447+ abort = True
424448 elif opt in ['--serde' ]:
425449 if arg .lower () == 'json' :
426450 self .plugin_serde (serde .JsonSerDe ())
@@ -441,6 +465,11 @@ def parse_args(self):
441465 raise RuntimeError ('Config file (app_profile) has not been provided' )
442466 self .config .source_config_file (self ._init_params ['config_file' ])
443467
468+ if abort :
469+ print ('Submitting ABORT signal to running job for: {}' .format (self .config ['app_name' ]))
470+ self .signal_handler .emit (SIG_ABORT )
471+ sys .exit (0 )
472+
444473 # Check if restart is possible (ctllog/ctx files exist)
445474 if self ._init_params ['restart' ] and not self .is_restartable ():
446475 self ._init_params ['restart' ] = False
0 commit comments