@@ -482,8 +482,6 @@ class OMCSessionRunData:
482482 cmd_model_executable : Optional [str ] = None
483483 # additional library search path; this is mainly needed if OMCProcessLocal is run on Windows
484484 cmd_library_path : Optional [str ] = None
485- # command timeout
486- cmd_timeout : Optional [float ] = 10.0
487485
488486 # working directory to be used on the *local* system
489487 cmd_cwd_local : Optional [str ] = None
@@ -558,13 +556,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
558556 """
559557 return self .omc_process .omc_run_data_update (omc_run_data = omc_run_data )
560558
561- @staticmethod
562- def run_model_executable (cmd_run_data : OMCSessionRunData ) -> int :
559+ def run_model_executable (self , cmd_run_data : OMCSessionRunData ) -> int :
563560 """
564561 Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
565562 keep instances of over classes around.
566563 """
567- return OMCSession .run_model_executable (cmd_run_data = cmd_run_data )
564+ return self . omc_process .run_model_executable (cmd_run_data = cmd_run_data )
568565
569566 def execute (self , command : str ):
570567 return self .omc_process .execute (command = command )
@@ -661,12 +658,12 @@ def __init__(
661658 self ._omc_zmq : Optional [zmq .Socket [bytes ]] = None
662659
663660 # setup log file - this file must be closed in the destructor
664- logfile = self ._temp_dir / (self ._omc_filebase + ".log" )
661+ self . _omc_logfile = self ._temp_dir / (self ._omc_filebase + ".log" )
665662 self ._omc_loghandle : Optional [io .TextIOWrapper ] = None
666663 try :
667- self ._omc_loghandle = open (file = logfile , mode = "w+" , encoding = "utf-8" )
664+ self ._omc_loghandle = open (file = self . _omc_logfile , mode = "w+" , encoding = "utf-8" )
668665 except OSError as ex :
669- raise OMCSessionException (f"Cannot open log file { logfile } ." ) from ex
666+ raise OMCSessionException (f"Cannot open log file { self . _omc_logfile } ." ) from ex
670667
671668 # variables to store compiled re expressions use in self.sendExpression()
672669 self ._re_log_entries : Optional [re .Pattern [str ]] = None
@@ -679,6 +676,9 @@ def __post_init__(self) -> None:
679676 """
680677 Create the connection to the OMC server using ZeroMQ.
681678 """
679+ # set_timeout() is used to define the value of _timeout as it includes additional checks
680+ self .set_timeout (timeout = self ._timeout )
681+
682682 port = self .get_port ()
683683 if not isinstance (port , str ):
684684 raise OMCSessionException (f"Invalid content for port: { port } " )
@@ -721,6 +721,44 @@ def __del__(self):
721721 finally :
722722 self ._omc_process = None
723723
724+ def _timeout_loop (
725+ self ,
726+ timeout : Optional [float ] = None ,
727+ timestep : float = 0.1 ,
728+ ):
729+ """
730+ Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is
731+ returned, i.e. the first False will stop the while loop.
732+ """
733+
734+ if timeout is None :
735+ timeout = self ._timeout
736+ if timeout <= 0 :
737+ raise OMCSessionException (f"Invalid timeout: { timeout } " )
738+
739+ timer = 0.0
740+ yield True
741+ while True :
742+ timer += timestep
743+ if timer > timeout :
744+ break
745+ time .sleep (timestep )
746+ yield True
747+ yield False
748+
749+ def set_timeout (self , timeout : Optional [float ] = None ) -> float :
750+ """
751+ Set the timeout to be used for OMC communication (OMCSession).
752+
753+ The defined value is set and the current value is returned. If None is provided as argument, nothing is changed.
754+ """
755+ retval = self ._timeout
756+ if timeout is not None :
757+ if timeout <= 0.0 :
758+ raise OMCSessionException (f"Invalid timeout value: { timeout } !" )
759+ self ._timeout = timeout
760+ return retval
761+
724762 @staticmethod
725763 def escape_str (value : str ) -> str :
726764 """
@@ -772,11 +810,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
772810
773811 return tempdir
774812
775- @staticmethod
776- def run_model_executable (cmd_run_data : OMCSessionRunData ) -> int :
813+ def run_model_executable (self , cmd_run_data : OMCSessionRunData ) -> int :
777814 """
778- Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
779- keep instances of over classes around.
815+ Run the command defined in cmd_run_data.
780816 """
781817
782818 my_env = os .environ .copy ()
@@ -793,7 +829,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
793829 text = True ,
794830 env = my_env ,
795831 cwd = cmd_run_data .cmd_cwd_local ,
796- timeout = cmd_run_data . cmd_timeout ,
832+ timeout = self . _timeout ,
797833 check = True ,
798834 )
799835 stdout = cmdres .stdout .strip ()
@@ -827,34 +863,28 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
827863 Caller should only check for OMCSessionException.
828864 """
829865
830- # this is needed if the class is not fully initialized or in the process of deletion
831- if hasattr (self , '_timeout' ):
832- timeout = self ._timeout
833- else :
834- timeout = 1.0
835-
836866 if self ._omc_zmq is None :
837867 raise OMCSessionException ("No OMC running. Please create a new instance of OMCSession!" )
838868
839869 logger .debug ("sendExpression(%r, parsed=%r)" , command , parsed )
840870
841- attempts = 0
842- while True :
871+ loop = self . _timeout_loop ( timestep = 0.05 )
872+ while next ( loop ) :
843873 try :
844874 self ._omc_zmq .send_string (str (command ), flags = zmq .NOBLOCK )
845875 break
846876 except zmq .error .Again :
847877 pass
848- attempts += 1
849- if attempts >= 50 :
850- # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
851- try :
852- log_content = self . get_log ()
853- except OMCSessionException :
854- log_content = 'log not available'
855- raise OMCSessionException (f"No connection with OMC (timeout= { timeout } ). "
856- f"Log-file says: \n { log_content } " )
857- time . sleep ( timeout / 50.0 )
878+ else :
879+ # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
880+ try :
881+ log_content = self . get_log ()
882+ except OMCSessionException :
883+ log_content = 'log not available'
884+
885+ logger . error (f"OMC did not start. Log-file says: \n { log_content } " )
886+ raise OMCSessionException ( f"No connection with OMC (timeout= { self . _timeout } ). " )
887+
858888 if command == "quit()" :
859889 self ._omc_zmq .close ()
860890 self ._omc_zmq = None
@@ -950,7 +980,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
950980 raise OMCSessionException (f"OMC error occurred for 'sendExpression({ command } , { parsed } ):\n "
951981 f"{ msg_long_str } " )
952982
953- if parsed is False :
983+ if not parsed :
954984 return result
955985
956986 try :
@@ -1099,25 +1129,20 @@ def _omc_port_get(self) -> str:
10991129 port = None
11001130
11011131 # See if the omc server is running
1102- attempts = 0
1103- while True :
1132+ loop = self . _timeout_loop ( timestep = 0.1 )
1133+ while next ( loop ) :
11041134 omc_portfile_path = self ._get_portfile_path ()
1105-
11061135 if omc_portfile_path is not None and omc_portfile_path .is_file ():
11071136 # Read the port file
11081137 with open (file = omc_portfile_path , mode = 'r' , encoding = "utf-8" ) as f_p :
11091138 port = f_p .readline ()
11101139 break
1111-
11121140 if port is not None :
11131141 break
1114-
1115- attempts += 1
1116- if attempts == 80.0 :
1117- raise OMCSessionException (f"OMC Server did not start (timeout={ self ._timeout } ). "
1118- f"Could not open file { omc_portfile_path } . "
1119- f"Log-file says:\n { self .get_log ()} " )
1120- time .sleep (self ._timeout / 80.0 )
1142+ else :
1143+ logger .error (f"OMC server did not start. Log-file says:\n { self .get_log ()} " )
1144+ raise OMCSessionException (f"OMC Server did not start (timeout={ self ._timeout } , "
1145+ f"logfile={ repr (self ._omc_logfile )} )." )
11211146
11221147 logger .info (f"Local OMC Server is up and running at ZMQ port { port } "
11231148 f"pid={ self ._omc_process .pid if isinstance (self ._omc_process , subprocess .Popen ) else '?' } " )
@@ -1198,8 +1223,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
11981223 if sys .platform == 'win32' :
11991224 raise NotImplementedError ("Docker not supported on win32!" )
12001225
1201- docker_process = None
1202- for _ in range ( 0 , 40 ):
1226+ loop = self . _timeout_loop ( timestep = 0.2 )
1227+ while next ( loop ):
12031228 docker_top = subprocess .check_output (["docker" , "top" , docker_cid ]).decode ().strip ()
12041229 docker_process = None
12051230 for line in docker_top .split ("\n " ):
@@ -1210,10 +1235,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12101235 except psutil .NoSuchProcess as ex :
12111236 raise OMCSessionException (f"Could not find PID { docker_top } - "
12121237 "is this a docker instance spawned without --pid=host?" ) from ex
1213-
12141238 if docker_process is not None :
12151239 break
1216- time .sleep (self ._timeout / 40.0 )
1240+ else :
1241+ logger .error (f"Docker did not start. Log-file says:\n { self .get_log ()} " )
1242+ raise OMCSessionException (f"Docker based OMC Server did not start (timeout={ self ._timeout } )." )
12171243
12181244 return docker_process
12191245
@@ -1235,8 +1261,8 @@ def _omc_port_get(self) -> str:
12351261 raise OMCSessionException (f"Invalid docker container ID: { self ._docker_container_id } " )
12361262
12371263 # See if the omc server is running
1238- attempts = 0
1239- while True :
1264+ loop = self . _timeout_loop ( timestep = 0.1 )
1265+ while next ( loop ) :
12401266 omc_portfile_path = self ._get_portfile_path ()
12411267 if omc_portfile_path is not None :
12421268 try :
@@ -1247,16 +1273,12 @@ def _omc_port_get(self) -> str:
12471273 port = output .decode ().strip ()
12481274 except subprocess .CalledProcessError :
12491275 pass
1250-
12511276 if port is not None :
12521277 break
1253-
1254- attempts += 1
1255- if attempts == 80.0 :
1256- raise OMCSessionException (f"Docker based OMC Server did not start (timeout={ self ._timeout } ). "
1257- f"Could not open port file { omc_portfile_path } . "
1258- f"Log-file says:\n { self .get_log ()} " )
1259- time .sleep (self ._timeout / 80.0 )
1278+ else :
1279+ logger .error (f"Docker did not start. Log-file says:\n { self .get_log ()} " )
1280+ raise OMCSessionException (f"Docker based OMC Server did not start (timeout={ self ._timeout } , "
1281+ f"logfile={ repr (self ._omc_logfile )} )." )
12601282
12611283 logger .info (f"Docker based OMC Server is up and running at port { port } " )
12621284
@@ -1424,25 +1446,24 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]:
14241446 raise OMCSessionException (f"Invalid content for docker container ID file path: { docker_cid_file } " )
14251447
14261448 docker_cid = None
1427- for _ in range (0 , 40 ):
1449+ loop = self ._timeout_loop (timestep = 0.1 )
1450+ while next (loop ):
14281451 try :
14291452 with open (file = docker_cid_file , mode = "r" , encoding = "utf-8" ) as fh :
14301453 docker_cid = fh .read ().strip ()
14311454 except IOError :
14321455 pass
1433- if docker_cid :
1456+ if docker_cid is not None :
14341457 break
1435- time .sleep (self ._timeout / 40.0 )
1436-
1437- if docker_cid is None :
1458+ else :
14381459 logger .error (f"Docker did not start. Log-file says:\n { self .get_log ()} " )
14391460 raise OMCSessionException (f"Docker did not start (timeout={ self ._timeout } might be too short "
14401461 "especially if you did not docker pull the image before this command)." )
14411462
14421463 docker_process = self ._docker_process_get (docker_cid = docker_cid )
14431464 if docker_process is None :
1444- raise OMCSessionException (f"Docker top did not contain omc process { self ._random_string } . "
1445- f"Log-file says: \n { self .get_log () } " )
1465+ logger . error (f"Docker did not start. Log-file says: \n { self .get_log () } " )
1466+ raise OMCSessionException ( f"Docker top did not contain omc process { self ._random_string } . " )
14461467
14471468 return omc_process , docker_process , docker_cid
14481469
@@ -1594,12 +1615,11 @@ def _omc_process_get(self) -> subprocess.Popen:
15941615 return omc_process
15951616
15961617 def _omc_port_get (self ) -> str :
1597- omc_portfile_path : Optional [pathlib .Path ] = None
15981618 port = None
15991619
16001620 # See if the omc server is running
1601- attempts = 0
1602- while True :
1621+ loop = self . _timeout_loop ( timestep = 0.1 )
1622+ while next ( loop ) :
16031623 try :
16041624 omc_portfile_path = self ._get_portfile_path ()
16051625 if omc_portfile_path is not None :
@@ -1610,16 +1630,12 @@ def _omc_port_get(self) -> str:
16101630 port = output .decode ().strip ()
16111631 except subprocess .CalledProcessError :
16121632 pass
1613-
16141633 if port is not None :
16151634 break
1616-
1617- attempts += 1
1618- if attempts == 80.0 :
1619- raise OMCSessionException (f"WSL based OMC Server did not start (timeout={ self ._timeout } ). "
1620- f"Could not open port file { omc_portfile_path } . "
1621- f"Log-file says:\n { self .get_log ()} " )
1622- time .sleep (self ._timeout / 80.0 )
1635+ else :
1636+ logger .error (f"WSL based OMC server did not start. Log-file says:\n { self .get_log ()} " )
1637+ raise OMCSessionException (f"WSL based OMC Server did not start (timeout={ self ._timeout } , "
1638+ f"logfile={ repr (self ._omc_logfile )} )." )
16231639
16241640 logger .info (f"WSL based OMC Server is up and running at ZMQ port { port } "
16251641 f"pid={ self ._omc_process .pid if isinstance (self ._omc_process , subprocess .Popen ) else '?' } " )
0 commit comments