Skip to content

Commit d34e223

Browse files
syntronadeas31
andauthored
[ModelicaSystem*] add timeout argument (#382)
* [OMCSession*] define set_timeout() * [OMCSession*] align all usages of timeout to the same structure * [OMCSession*] simplify code for timeout loops * [OMCSession] fix definiton of _timeout variable - use set_timeout() checks * [OMCSession*] some additional cleanup (mypy / flake8) * remove not needed variable definitions * fix if condition for bool * [OMCSession] move call to set_timeout() to __post_init__ * [OMCSession] fix log message * [OMCSession] store the filename of the log file and print it in exception messages * [OMCSessionWSL] fix another exception message & add log filename --------- Co-authored-by: Adeel Asghar <adeel.asghar@liu.se>
1 parent 2c016b2 commit d34e223

File tree

1 file changed

+91
-75
lines changed

1 file changed

+91
-75
lines changed

OMPython/OMCSession.py

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)