Skip to content

Commit 0970115

Browse files
diningPhilosopher64Prabhakar Kumar
authored andcommitted
Introduces the environment variable MWI_PROCESS_START_TIMEOUT, to configure the default timeout of 120 seconds, used by matlab-proxy while waiting for the processes it launches (viz: Xvfb & MATLAB).
fixes #24 Eg: ``` # Set the timeout to 5 minutes (300 seconds) env MWI_PROCESS_START_TIMEOUT=300 matlab-proxy-app ```
1 parent b197a2c commit 0970115

File tree

6 files changed

+86
-16
lines changed

6 files changed

+86
-16
lines changed

matlab_proxy/app_state.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from datetime import datetime, timedelta, timezone
1111

1212
from matlab_proxy import util
13+
from matlab_proxy.settings import get_process_startup_timeout
1314
from matlab_proxy.util import mw, mwi, system, windows
1415
from matlab_proxy.util.mwi import environment_variables as mwi_env
1516
from matlab_proxy.util.mwi import token_auth
@@ -35,9 +36,6 @@ class AppState:
3536

3637
# Constants that are applicable to AppState class
3738
MATLAB_PORT_CHECK_DELAY_IN_SECONDS = 1
38-
# The maximum amount of time in seconds the Embedded Connector can take
39-
# for launching, before the matlab-proxy server concludes that something is wrong.
40-
EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS = 120
4139

4240
def __init__(self, settings):
4341
"""Parameterized constructor for the AppState class.
@@ -49,6 +47,9 @@ def __init__(self, settings):
4947
self.settings = settings
5048
self.processes = {"matlab": None, "xvfb": None}
5149

50+
# Timeout for processes launched by matlab-proxy
51+
self.PROCESS_TIMEOUT = get_process_startup_timeout()
52+
5253
# The port on which MATLAB(launched by this matlab-proxy process) starts on.
5354
self.matlab_port = None
5455

@@ -825,10 +826,7 @@ async def __track_embedded_connector_state():
825826

826827
else:
827828
time_diff = time.time() - self.embedded_connector_start_time
828-
if (
829-
time_diff
830-
> self.EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS
831-
):
829+
if time_diff > self.PROCESS_TIMEOUT:
832830
# Since max allowed startup time has elapsed, it means that MATLAB is in a stuck state and cannot be launched.
833831
# Set the error and stop matlab.
834832
user_visible_error = "Unable to start MATLAB.\nTry again by clicking Start MATLAB."
@@ -845,12 +843,12 @@ async def __force_stop_matlab(error):
845843
if system.is_windows():
846844
# In WINDOWS systems, errors are raised as UI windows and cannot be captured programmatically.
847845
# So, raise a generic error wherever appropriate
848-
generic_error = f"MATLAB did not start in {int(self.EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS)} seconds. Use Windows Remote Desktop to check for any errors."
846+
generic_error = f"MATLAB did not start in {int(self.PROCESS_TIMEOUT)} seconds. Use Windows Remote Desktop to check for any errors."
849847
logger.error(f":{this_task}: {generic_error}")
850848
if len(self.logs["matlab"]) == 0:
851849
await __force_stop_matlab(user_visible_error)
852850
# Breaking out of the loop to end this task as matlab-proxy was unable to launch MATLAB successfully
853-
# even after waiting for EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS
851+
# even after waiting for self.PROCESS_TIMEOUT
854852
break
855853
else:
856854
# Do not stop the MATLAB process or break from the loop (the error type is unknown)
@@ -862,12 +860,12 @@ async def __force_stop_matlab(error):
862860
# If there are no logs after the max startup time has elapsed, it means that MATLAB is in a stuck state and cannot be launched.
863861
# Set the error and stop matlab.
864862
logger.error(
865-
f":{this_task}: MATLAB did not start in {int(self.EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS)} seconds!"
863+
f":{this_task}: MATLAB did not start in {int(self.PROCESS_TIMEOUT)} seconds!"
866864
)
867865
if len(self.logs["matlab"]) == 0:
868866
await __force_stop_matlab(user_visible_error)
869867
# Breaking out of the loop to end this task as matlab-proxy was unable to launch MATLAB successfully
870-
# even after waiting for EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS
868+
# even after waiting for self.PROCESS_TIMEOUT
871869
break
872870

873871
else:
@@ -909,7 +907,7 @@ async def __update_matlab_port(delay: int):
909907
try:
910908
await asyncio.wait_for(
911909
__read_matlab_ready_file(delay),
912-
self.EMBEDDED_CONNECTOR_MAX_STARTUP_DURATION_IN_SECONDS,
910+
self.PROCESS_TIMEOUT,
913911
)
914912
except asyncio.TimeoutError:
915913
logger.debug(

matlab_proxy/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Copyright (c) 2023 The MathWorks, Inc.
22

33
"""This module defines project-level constants"""
4+
45
CONNECTOR_SECUREPORT_FILENAME = "connector.securePort"
56
VERSION_INFO_FILE_NAME = "VersionInfo.xml"
67
MAX_HTTP_REQUEST_SIZE = 500_000_000 # 500MB
8+
9+
# Max startup duration in seconds for processes launched by matlab-proxy
10+
# This constant is meant for internal use within matlab-proxy
11+
# Clients of this package should use settings.py::get_process_startup_timeout() function
12+
DEFAULT_PROCESS_START_TIMEOUT = 120

matlab_proxy/settings.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import xml.etree.ElementTree as ET
1111

1212
import matlab_proxy
13-
from matlab_proxy.constants import VERSION_INFO_FILE_NAME
13+
from matlab_proxy import constants
1414
from matlab_proxy.util import mwi, system
1515
from matlab_proxy.util.mwi import environment_variables as mwi_env
1616
from matlab_proxy.util.mwi import token_auth
@@ -23,6 +23,35 @@
2323
logger = mwi.logger.get()
2424

2525

26+
def get_process_startup_timeout():
27+
"""Returns the timeout for a process launched by matlab-proxy as specified by MWI_PROCESS_START_TIMEOUT environment variable
28+
if valid, else returns the default value.
29+
30+
Returns:
31+
int: timeout for a process launched by matlab-proxy
32+
"""
33+
custom_startup_timeout = os.getenv(mwi_env.get_env_name_process_startup_timeout())
34+
35+
if custom_startup_timeout:
36+
if custom_startup_timeout.isdigit():
37+
logger.info(
38+
f"Using custom process startup timeout {custom_startup_timeout} seconds"
39+
)
40+
return int(custom_startup_timeout)
41+
42+
else:
43+
logger.warn(
44+
f"The value set for {mwi_env.get_env_name_process_startup_timeout()}:{custom_startup_timeout} is not a number. Using {constants.DEFAULT_PROCESS_START_TIMEOUT} as the default value"
45+
)
46+
return constants.DEFAULT_PROCESS_START_TIMEOUT
47+
48+
logger.info(
49+
f"Using {constants.DEFAULT_PROCESS_START_TIMEOUT} seconds as the default timeout value"
50+
)
51+
52+
return constants.DEFAULT_PROCESS_START_TIMEOUT
53+
54+
2655
def get_matlab_executable_and_root_path():
2756
"""Returns the path from the MWI_CUSTOM_MATLAB_ROOT environment variable if valid, else returns
2857
MATLAB root based on the matlab executable if found on the system path.
@@ -86,7 +115,7 @@ def get_matlab_version(matlab_root_path):
86115
if matlab_root_path is None:
87116
return None
88117

89-
version_info_file_path = Path(matlab_root_path) / VERSION_INFO_FILE_NAME
118+
version_info_file_path = Path(matlab_root_path) / constants.VERSION_INFO_FILE_NAME
90119
tree = ET.parse(version_info_file_path)
91120
root = tree.getroot()
92121

matlab_proxy/util/mw.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import aiohttp
99
from matlab_proxy.default_configuration import config
1010
from matlab_proxy.util import mwi
11+
from matlab_proxy.settings import get_process_startup_timeout
1112
from matlab_proxy.util.mwi.exceptions import (
1213
EntitlementError,
1314
MatlabError,
@@ -290,8 +291,11 @@ async def create_xvfb_process(xvfb_cmd, pipe, env=None):
290291
number_of_bytes = 200
291292

292293
logger.debug("Waiting for XVFB process to initialize and provide Display Number")
293-
# Waits upto 10 seconds for the read_descriptor to be ready.
294-
ready_descriptors, _, _ = select.select([read_descriptor], [], [], 10)
294+
295+
# Wait for timeout specified by matlab-proxy for launching processes.
296+
ready_descriptors, _, _ = select.select(
297+
[read_descriptor], [], [], get_process_startup_timeout()
298+
)
295299

296300
# If read_descriptor is in ready_descriptors, read from it.
297301
if read_descriptor in ready_descriptors:

matlab_proxy/util/mwi/environment_variables.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,8 @@ def get_env_name_mwi_use_existing_license():
150150
def get_env_name_custom_matlab_root():
151151
"""User specified path to MATLAB root"""
152152
return "MWI_CUSTOM_MATLAB_ROOT"
153+
154+
155+
def get_env_name_process_startup_timeout():
156+
"""User specified timeout in seconds for processes launched by matlab-proxy"""
157+
return "MWI_PROCESS_START_TIMEOUT"

tests/test_settings.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,31 @@ def test_get_mw_context_tags(monkeypatch):
291291
actual_result = settings.get_mw_context_tags(extension_name)
292292

293293
assert expected_result == actual_result
294+
295+
296+
@pytest.mark.parametrize(
297+
"timeout_value",
298+
[(130, 130), ("asdf", 120), (120.5, 120), (None, 120)],
299+
ids=["Valid number", "Invalid number", "Valid decimal number", "No value supplied"],
300+
)
301+
def test_get_process_timeout(timeout_value, monkeypatch):
302+
"""Parameterized test to check settings.test_get_process_timeout returns the correct timeout value when MWI_PROCESS_STARTUP_TIMEOUT is set.
303+
304+
Args:
305+
timeout (str): Timeout for processes launched by matlab-proxy
306+
monkeypatch (Builtin pytest fixture): Pytest fixture to monkeypatch environment variables.
307+
"""
308+
# Arrange
309+
supplied_timeout, expected_timeout = timeout_value[0], timeout_value[1]
310+
311+
# pytest would throw warnings if None is supplied to monkeypatch
312+
if supplied_timeout:
313+
monkeypatch.setenv(
314+
mwi_env.get_env_name_process_startup_timeout(), str(supplied_timeout)
315+
)
316+
317+
# Act
318+
actual_timeout = settings.get_process_startup_timeout()
319+
320+
# Assert
321+
assert expected_timeout == actual_timeout

0 commit comments

Comments
 (0)