@@ -488,8 +488,6 @@ class OMCSessionRunData:
488488 cmd_model_executable : Optional [str ] = None
489489 # additional library search path; this is mainly needed if OMCProcessLocal is run on Windows
490490 cmd_library_path : Optional [str ] = None
491- # command timeout
492- cmd_timeout : Optional [float ] = 10.0
493491
494492 # working directory to be used on the *local* system
495493 cmd_cwd_local : Optional [str ] = None
@@ -564,13 +562,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
564562 """
565563 return self .omc_process .omc_run_data_update (omc_run_data = omc_run_data )
566564
567- @staticmethod
568- def run_model_executable (cmd_run_data : OMCSessionRunData ) -> int :
565+ def run_model_executable (self , cmd_run_data : OMCSessionRunData ) -> int :
569566 """
570567 Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
571568 keep instances of over classes around.
572569 """
573- return OMCSession .run_model_executable (cmd_run_data = cmd_run_data )
570+ return self . omc_process .run_model_executable (cmd_run_data = cmd_run_data )
574571
575572 def execute (self , command : str ):
576573 return self .omc_process .execute (command = command )
@@ -667,12 +664,12 @@ def __init__(
667664 self ._omc_zmq : Optional [zmq .Socket [bytes ]] = None
668665
669666 # setup log file - this file must be closed in the destructor
670- logfile = self ._temp_dir / (self ._omc_filebase + ".log" )
667+ self . _omc_logfile = self ._temp_dir / (self ._omc_filebase + ".log" )
671668 self ._omc_loghandle : Optional [io .TextIOWrapper ] = None
672669 try :
673- self ._omc_loghandle = open (file = logfile , mode = "w+" , encoding = "utf-8" )
670+ self ._omc_loghandle = open (file = self . _omc_logfile , mode = "w+" , encoding = "utf-8" )
674671 except OSError as ex :
675- raise OMCSessionException (f"Cannot open log file { logfile } ." ) from ex
672+ raise OMCSessionException (f"Cannot open log file { self . _omc_logfile } ." ) from ex
676673
677674 # variables to store compiled re expressions use in self.sendExpression()
678675 self ._re_log_entries : Optional [re .Pattern [str ]] = None
@@ -685,6 +682,9 @@ def __post_init__(self) -> None:
685682 """
686683 Create the connection to the OMC server using ZeroMQ.
687684 """
685+ # set_timeout() is used to define the value of _timeout as it includes additional checks
686+ self .set_timeout (timeout = self ._timeout )
687+
688688 port = self .get_port ()
689689 if not isinstance (port , str ):
690690 raise OMCSessionException (f"Invalid content for port: { port } " )
@@ -727,6 +727,44 @@ def __del__(self):
727727 finally :
728728 self ._omc_process = None
729729
730+ def _timeout_loop (
731+ self ,
732+ timeout : Optional [float ] = None ,
733+ timestep : float = 0.1 ,
734+ ):
735+ """
736+ Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is
737+ returned, i.e. the first False will stop the while loop.
738+ """
739+
740+ if timeout is None :
741+ timeout = self ._timeout
742+ if timeout <= 0 :
743+ raise OMCSessionException (f"Invalid timeout: { timeout } " )
744+
745+ timer = 0.0
746+ yield True
747+ while True :
748+ timer += timestep
749+ if timer > timeout :
750+ break
751+ time .sleep (timestep )
752+ yield True
753+ yield False
754+
755+ def set_timeout (self , timeout : Optional [float ] = None ) -> float :
756+ """
757+ Set the timeout to be used for OMC communication (OMCSession).
758+
759+ The defined value is set and the current value is returned. If None is provided as argument, nothing is changed.
760+ """
761+ retval = self ._timeout
762+ if timeout is not None :
763+ if timeout <= 0.0 :
764+ raise OMCSessionException (f"Invalid timeout value: { timeout } !" )
765+ self ._timeout = timeout
766+ return retval
767+
730768 @staticmethod
731769 def escape_str (value : str ) -> str :
732770 """
@@ -778,11 +816,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
778816
779817 return tempdir
780818
781- @staticmethod
782- def run_model_executable (cmd_run_data : OMCSessionRunData ) -> int :
819+ def run_model_executable (self , cmd_run_data : OMCSessionRunData ) -> int :
783820 """
784- Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
785- keep instances of over classes around.
821+ Run the command defined in cmd_run_data.
786822 """
787823
788824 my_env = os .environ .copy ()
@@ -799,7 +835,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
799835 text = True ,
800836 env = my_env ,
801837 cwd = cmd_run_data .cmd_cwd_local ,
802- timeout = cmd_run_data . cmd_timeout ,
838+ timeout = self . _timeout ,
803839 check = True ,
804840 )
805841 stdout = cmdres .stdout .strip ()
@@ -833,34 +869,28 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
833869 Caller should only check for OMCSessionException.
834870 """
835871
836- # this is needed if the class is not fully initialized or in the process of deletion
837- if hasattr (self , '_timeout' ):
838- timeout = self ._timeout
839- else :
840- timeout = 1.0
841-
842872 if self ._omc_zmq is None :
843873 raise OMCSessionException ("No OMC running. Please create a new instance of OMCSession!" )
844874
845875 logger .debug ("sendExpression(%r, parsed=%r)" , command , parsed )
846876
847- attempts = 0
848- while True :
877+ loop = self . _timeout_loop ( timestep = 0.05 )
878+ while next ( loop ) :
849879 try :
850880 self ._omc_zmq .send_string (str (command ), flags = zmq .NOBLOCK )
851881 break
852882 except zmq .error .Again :
853883 pass
854- attempts += 1
855- if attempts >= 50 :
856- # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
857- try :
858- log_content = self . get_log ()
859- except OMCSessionException :
860- log_content = 'log not available'
861- raise OMCSessionException (f"No connection with OMC (timeout= { timeout } ). "
862- f"Log-file says: \n { log_content } " )
863- time . sleep ( timeout / 50.0 )
884+ else :
885+ # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
886+ try :
887+ log_content = self . get_log ()
888+ except OMCSessionException :
889+ log_content = 'log not available'
890+
891+ logger . error (f"OMC did not start. Log-file says: \n { log_content } " )
892+ raise OMCSessionException ( f"No connection with OMC (timeout= { self . _timeout } ). " )
893+
864894 if command == "quit()" :
865895 self ._omc_zmq .close ()
866896 self ._omc_zmq = None
@@ -956,7 +986,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
956986 raise OMCSessionException (f"OMC error occurred for 'sendExpression({ command } , { parsed } ):\n "
957987 f"{ msg_long_str } " )
958988
959- if parsed is False :
989+ if not parsed :
960990 return result
961991
962992 try :
@@ -1105,25 +1135,20 @@ def _omc_port_get(self) -> str:
11051135 port = None
11061136
11071137 # See if the omc server is running
1108- attempts = 0
1109- while True :
1138+ loop = self . _timeout_loop ( timestep = 0.1 )
1139+ while next ( loop ) :
11101140 omc_portfile_path = self ._get_portfile_path ()
1111-
11121141 if omc_portfile_path is not None and omc_portfile_path .is_file ():
11131142 # Read the port file
11141143 with open (file = omc_portfile_path , mode = 'r' , encoding = "utf-8" ) as f_p :
11151144 port = f_p .readline ()
11161145 break
1117-
11181146 if port is not None :
11191147 break
1120-
1121- attempts += 1
1122- if attempts == 80.0 :
1123- raise OMCSessionException (f"OMC Server did not start (timeout={ self ._timeout } ). "
1124- f"Could not open file { omc_portfile_path } . "
1125- f"Log-file says:\n { self .get_log ()} " )
1126- time .sleep (self ._timeout / 80.0 )
1148+ else :
1149+ logger .error (f"OMC server did not start. Log-file says:\n { self .get_log ()} " )
1150+ raise OMCSessionException (f"OMC Server did not start (timeout={ self ._timeout } , "
1151+ f"logfile={ repr (self ._omc_logfile )} )." )
11271152
11281153 logger .info (f"Local OMC Server is up and running at ZMQ port { port } "
11291154 f"pid={ self ._omc_process .pid if isinstance (self ._omc_process , subprocess .Popen ) else '?' } " )
@@ -1204,8 +1229,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12041229 if sys .platform == 'win32' :
12051230 raise NotImplementedError ("Docker not supported on win32!" )
12061231
1207- docker_process = None
1208- for _ in range ( 0 , 40 ):
1232+ loop = self . _timeout_loop ( timestep = 0.2 )
1233+ while next ( loop ):
12091234 docker_top = subprocess .check_output (["docker" , "top" , docker_cid ]).decode ().strip ()
12101235 docker_process = None
12111236 for line in docker_top .split ("\n " ):
@@ -1216,10 +1241,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12161241 except psutil .NoSuchProcess as ex :
12171242 raise OMCSessionException (f"Could not find PID { docker_top } - "
12181243 "is this a docker instance spawned without --pid=host?" ) from ex
1219-
12201244 if docker_process is not None :
12211245 break
1222- time .sleep (self ._timeout / 40.0 )
1246+ else :
1247+ logger .error (f"Docker did not start. Log-file says:\n { self .get_log ()} " )
1248+ raise OMCSessionException (f"Docker based OMC Server did not start (timeout={ self ._timeout } )." )
12231249
12241250 return docker_process
12251251
@@ -1241,8 +1267,8 @@ def _omc_port_get(self) -> str:
12411267 raise OMCSessionException (f"Invalid docker container ID: { self ._docker_container_id } " )
12421268
12431269 # See if the omc server is running
1244- attempts = 0
1245- while True :
1270+ loop = self . _timeout_loop ( timestep = 0.1 )
1271+ while next ( loop ) :
12461272 omc_portfile_path = self ._get_portfile_path ()
12471273 if omc_portfile_path is not None :
12481274 try :
@@ -1253,16 +1279,12 @@ def _omc_port_get(self) -> str:
12531279 port = output .decode ().strip ()
12541280 except subprocess .CalledProcessError :
12551281 pass
1256-
12571282 if port is not None :
12581283 break
1259-
1260- attempts += 1
1261- if attempts == 80.0 :
1262- raise OMCSessionException (f"Docker based OMC Server did not start (timeout={ self ._timeout } ). "
1263- f"Could not open port file { omc_portfile_path } . "
1264- f"Log-file says:\n { self .get_log ()} " )
1265- time .sleep (self ._timeout / 80.0 )
1284+ else :
1285+ logger .error (f"Docker did not start. Log-file says:\n { self .get_log ()} " )
1286+ raise OMCSessionException (f"Docker based OMC Server did not start (timeout={ self ._timeout } , "
1287+ f"logfile={ repr (self ._omc_logfile )} )." )
12661288
12671289 logger .info (f"Docker based OMC Server is up and running at port { port } " )
12681290
@@ -1430,25 +1452,24 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]:
14301452 raise OMCSessionException (f"Invalid content for docker container ID file path: { docker_cid_file } " )
14311453
14321454 docker_cid = None
1433- for _ in range (0 , 40 ):
1455+ loop = self ._timeout_loop (timestep = 0.1 )
1456+ while next (loop ):
14341457 try :
14351458 with open (file = docker_cid_file , mode = "r" , encoding = "utf-8" ) as fh :
14361459 docker_cid = fh .read ().strip ()
14371460 except IOError :
14381461 pass
1439- if docker_cid :
1462+ if docker_cid is not None :
14401463 break
1441- time .sleep (self ._timeout / 40.0 )
1442-
1443- if docker_cid is None :
1464+ else :
14441465 logger .error (f"Docker did not start. Log-file says:\n { self .get_log ()} " )
14451466 raise OMCSessionException (f"Docker did not start (timeout={ self ._timeout } might be too short "
14461467 "especially if you did not docker pull the image before this command)." )
14471468
14481469 docker_process = self ._docker_process_get (docker_cid = docker_cid )
14491470 if docker_process is None :
1450- raise OMCSessionException (f"Docker top did not contain omc process { self ._random_string } . "
1451- f"Log-file says: \n { self .get_log () } " )
1471+ logger . error (f"Docker did not start. Log-file says: \n { self .get_log () } " )
1472+ raise OMCSessionException ( f"Docker top did not contain omc process { self ._random_string } . " )
14521473
14531474 return omc_process , docker_process , docker_cid
14541475
@@ -1600,12 +1621,11 @@ def _omc_process_get(self) -> subprocess.Popen:
16001621 return omc_process
16011622
16021623 def _omc_port_get (self ) -> str :
1603- omc_portfile_path : Optional [pathlib .Path ] = None
16041624 port = None
16051625
16061626 # See if the omc server is running
1607- attempts = 0
1608- while True :
1627+ loop = self . _timeout_loop ( timestep = 0.1 )
1628+ while next ( loop ) :
16091629 try :
16101630 omc_portfile_path = self ._get_portfile_path ()
16111631 if omc_portfile_path is not None :
@@ -1616,16 +1636,12 @@ def _omc_port_get(self) -> str:
16161636 port = output .decode ().strip ()
16171637 except subprocess .CalledProcessError :
16181638 pass
1619-
16201639 if port is not None :
16211640 break
1622-
1623- attempts += 1
1624- if attempts == 80.0 :
1625- raise OMCSessionException (f"WSL based OMC Server did not start (timeout={ self ._timeout } ). "
1626- f"Could not open port file { omc_portfile_path } . "
1627- f"Log-file says:\n { self .get_log ()} " )
1628- time .sleep (self ._timeout / 80.0 )
1641+ else :
1642+ logger .error (f"WSL based OMC server did not start. Log-file says:\n { self .get_log ()} " )
1643+ raise OMCSessionException (f"WSL based OMC Server did not start (timeout={ self ._timeout } , "
1644+ f"logfile={ repr (self ._omc_logfile )} )." )
16291645
16301646 logger .info (f"WSL based OMC Server is up and running at ZMQ port { port } "
16311647 f"pid={ self ._omc_process .pid if isinstance (self ._omc_process , subprocess .Popen ) else '?' } " )
0 commit comments