|
32 | 32 | import traceback
|
33 | 33 |
|
34 | 34 | import pandas as pd
|
| 35 | +from loguru import logger |
35 | 36 |
|
36 | 37 | from .._Utils import _try_import
|
37 | 38 |
|
|
53 | 54 | from ..Protocol._protocol import Protocol as _Protocol
|
54 | 55 | from .._SireWrappers import System as _System
|
55 | 56 | from ..Types._type import Type as _Type
|
| 57 | +from ..Types import Time as _Time |
56 | 58 | from .. import Units as _Units
|
57 | 59 | from .. import _Utils
|
58 | 60 | from ..FreeEnergy._restraint import Restraint as _Restraint
|
@@ -898,7 +900,7 @@ def setSeed(self, seed):
|
898 | 900 | else:
|
899 | 901 | self._seed = seed
|
900 | 902 |
|
901 |
| - def wait(self, max_time=None): |
| 903 | + def wait(self, max_time=None, inactivity_timeout: None | _Time = None): |
902 | 904 | """
|
903 | 905 | Wait for the process to finish.
|
904 | 906 |
|
@@ -939,11 +941,52 @@ def wait(self, max_time=None):
|
939 | 941 | self._process.wait(max_time)
|
940 | 942 |
|
941 | 943 | else:
|
942 |
| - # Wait for the process to finish. |
943 |
| - self._process.wait() |
| 944 | + if inactivity_timeout is None: |
| 945 | + # Wait for the process to finish. |
| 946 | + self._process.wait() |
944 | 947 |
|
945 |
| - # Store the final run time. |
946 |
| - self.runTime() |
| 948 | + # Store the final run time. |
| 949 | + self.runTime() |
| 950 | + else: |
| 951 | + inactivity_timeout = int(inactivity_timeout.milliseconds().value()) |
| 952 | + last_time = self._getLastTime() |
| 953 | + if last_time is None: |
| 954 | + # Wait for the process to finish. |
| 955 | + self._process.wait() |
| 956 | + |
| 957 | + # Store the final run time. |
| 958 | + self.runTime() |
| 959 | + else: |
| 960 | + while self.isRunning(): |
| 961 | + self._process.wait(inactivity_timeout) |
| 962 | + if self.isRunning(): |
| 963 | + current_time = self._getLastTime() |
| 964 | + if current_time > last_time: |
| 965 | + logger.info( |
| 966 | + f"Current simulation time ({current_time})." |
| 967 | + ) |
| 968 | + last_time = current_time |
| 969 | + else: |
| 970 | + logger.warning( |
| 971 | + f"Current simulation time ({current_time}) has not advanced compared " |
| 972 | + f"to the last time ({last_time}). The process " |
| 973 | + f"might have hung and will be killed." |
| 974 | + ) |
| 975 | + with open( |
| 976 | + f"{self.workDir()}/{self._name}.out", "a+" |
| 977 | + ) as f: |
| 978 | + f.write("Process Hung. Killed.") |
| 979 | + self.kill() |
| 980 | + |
| 981 | + def _getLastTime(self) -> float | None: |
| 982 | + """This is the base method in the Process base class. |
| 983 | + Each subclass, such as AMBER or GROMACS, is expected to override this method |
| 984 | + to provide their own implementation for returning the current time. |
| 985 | +
|
| 986 | + If this method is not overridden, it will return None, |
| 987 | + and the `inactivity_timeout` feature will be skipped. |
| 988 | + """ |
| 989 | + return None |
947 | 990 |
|
948 | 991 | def isQueued(self):
|
949 | 992 | """
|
|
0 commit comments