Skip to content

Commit eff1041

Browse files
krisctlprabhakk-mw
authored andcommitted
Increases the retry attempts to check for matlab-proxy-app readiness during startup.
fixes mathworks/matlab-proxy#110
1 parent a0abbd8 commit eff1041

File tree

4 files changed

+113
-23
lines changed

4 files changed

+113
-23
lines changed

matlab_proxy_manager/lib/api.py

+21-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 The MathWorks, Inc.
1+
# Copyright 2024-2025 The MathWorks, Inc.
22
import asyncio
33
import os
44
import secrets
@@ -147,27 +147,28 @@ async def _start_subprocess_and_check_for_readiness(
147147
return None
148148

149149
process_id, url, mwi_base_url = result
150-
server_process = None
151150

152-
# Check for the matlab proxy server readiness
153-
if helpers.is_server_ready(url=f"{url}{mwi_base_url}", backoff_factor=0.5):
154-
log.debug("Matlab proxy process info: %s, %s", url, mwi_base_url)
155-
server_process = ServerProcess(
156-
server_url=url,
157-
mwi_base_url=mwi_base_url,
158-
headers=helpers.convert_mwi_env_vars_to_header_format(
159-
matlab_proxy_env, "MWI"
160-
),
161-
pid=str(process_id),
162-
parent_pid=ctx,
163-
id=key,
164-
type="shared" if is_shared_matlab else "named",
165-
mpm_auth_token=mpm_auth_token,
166-
)
167-
else:
168-
log.error("matlab-proxy server never became ready")
151+
log.debug("Matlab proxy process info: %s, %s", url, mwi_base_url)
152+
matlab_proxy_process = ServerProcess(
153+
server_url=url,
154+
mwi_base_url=mwi_base_url,
155+
headers=helpers.convert_mwi_env_vars_to_header_format(matlab_proxy_env, "MWI"),
156+
pid=str(process_id),
157+
parent_pid=ctx,
158+
id=key,
159+
type="shared" if is_shared_matlab else "named",
160+
mpm_auth_token=mpm_auth_token,
161+
)
169162

170-
return server_process
163+
# Check for the matlab proxy server readiness
164+
if not helpers.is_server_ready(
165+
url=matlab_proxy_process.absolute_url, retries=7, backoff_factor=0.5
166+
):
167+
log.error("MATLAB Proxy Server unavailable: matlab-proxy-app failed to start or has timed out.")
168+
matlab_proxy_process.shutdown()
169+
matlab_proxy_process = None
170+
171+
return matlab_proxy_process
171172

172173

173174
def _prepare_cmd_and_env_for_matlab_proxy():

matlab_proxy_manager/storage/server.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# Copyright 2024 The MathWorks, Inc.
1+
# Copyright 2024-2025 The MathWorks, Inc.
22
import json
33
from dataclasses import asdict, dataclass, field
44
from pathlib import Path
55
from typing import Optional
66

7+
import psutil
8+
79
from matlab_proxy_manager.utils import helpers, logger
810

911
log = logger.get()
@@ -96,8 +98,31 @@ def shutdown(self):
9698
return shutdown_resp
9799
except Exception as e:
98100
log.debug("Exception while shutting down matlab proxy: %s", e)
101+
102+
# Force kill matlab-proxy and its process tree if termination
103+
# via shutdown_integration endpoint fails
104+
matlab_proxy_process = psutil.Process(int(self.pid))
105+
self.terminate_process_tree(matlab_proxy_process)
99106
return None
100107

108+
def terminate_process_tree(self, matlab_proxy_process):
109+
"""
110+
Terminates the process tree of the MATLAB proxy server.
111+
112+
This method terminates all child processes of the MATLAB proxy process,
113+
waits for a short period, and then forcefully kills any remaining processes.
114+
Finally, it kills the main MATLAB proxy process itself.
115+
"""
116+
matlab_proxy_child_processes = matlab_proxy_process.children(recursive=True)
117+
for child_process in matlab_proxy_child_processes:
118+
child_process.terminate()
119+
_, alive = psutil.wait_procs(matlab_proxy_child_processes, timeout=5)
120+
for process in alive:
121+
# Kill the processes which are alive even after waiting for 5 seconds
122+
process.kill()
123+
matlab_proxy_process.kill()
124+
log.debug("Killed matlab-proxy process with PID: %s", self.pid)
125+
101126
def is_server_alive(self) -> bool:
102127
"""
103128
Checks if the server process is alive and ready.

tests/unit/proxy-manager/lib/test_api.py

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 The MathWorks, Inc.
1+
# Copyright 2024-2025 The MathWorks, Inc.
22
import pytest
33

44
from matlab_proxy_manager.lib import api as mpm_api
@@ -177,6 +177,66 @@ async def test_start_matlab_proxy_returns_none_if_server_not_created(
177177
assert result is None
178178

179179

180+
async def test_matlab_proxy_is_cleaned_up_if_server_was_not_ready(mocker):
181+
"""
182+
Test case for cleaning up MATLAB proxy when server is not ready.
183+
184+
This test mocks various dependencies and verifies the behavior of the
185+
_start_matlab_proxy function when no existing server is found and
186+
a new server is created but not ready. It checks if the function correctly
187+
returns None, calls the expected methods, and cleans up the server.
188+
"""
189+
mock_find_existing_server = mocker.patch(
190+
"matlab_proxy_manager.storage.server.ServerProcess.find_existing_server",
191+
return_value=None,
192+
)
193+
mock_shutdown = mocker.patch(
194+
"matlab_proxy_manager.storage.server.ServerProcess.shutdown",
195+
return_value=None,
196+
)
197+
mock_delete_dangling_servers = mocker.patch(
198+
"matlab_proxy_manager.utils.helpers._are_orphaned_servers_deleted",
199+
return_value=None,
200+
)
201+
mock_create_state_file = mocker.patch(
202+
"matlab_proxy_manager.utils.helpers.create_state_file", return_value=None
203+
)
204+
mock_create_proxy_manager_dir = mocker.patch(
205+
"matlab_proxy_manager.utils.helpers.create_and_get_proxy_manager_data_dir",
206+
return_value=None,
207+
)
208+
mock_is_server_ready = mocker.patch(
209+
"matlab_proxy_manager.utils.helpers.is_server_ready", return_value=False
210+
)
211+
mock_prep_cmd_and_env = mocker.patch(
212+
"matlab_proxy_manager.lib.api._prepare_cmd_and_env_for_matlab_proxy",
213+
return_value=([], {}),
214+
)
215+
mock_start_subprocess = mocker.patch(
216+
"matlab_proxy_manager.lib.api._start_subprocess",
217+
return_value=(1, "dummy", "dummy"),
218+
)
219+
220+
caller_id = "test_caller"
221+
parent_id = "test_parent"
222+
is_shared_matlab = True
223+
224+
result = await mpm_api._start_matlab_proxy(
225+
caller_id=caller_id, ctx=parent_id, is_shared_matlab=is_shared_matlab
226+
)
227+
228+
mock_delete_dangling_servers.assert_called_once_with(parent_id)
229+
mock_create_proxy_manager_dir.assert_called_once()
230+
mock_find_existing_server.assert_called_once()
231+
mock_create_state_file.assert_not_called()
232+
mock_prep_cmd_and_env.assert_called_once()
233+
mock_start_subprocess.assert_awaited_once()
234+
mock_is_server_ready.assert_called_once()
235+
mock_shutdown.assert_called_once()
236+
237+
assert result is None
238+
239+
180240
# Test for shutdown with missing arguments
181241
async def test_shutdown_missing_args(mocker, mock_server_process):
182242
"""

tests/unit/proxy-manager/storage/test_server.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 The MathWorks, Inc.
1+
# Copyright 2024-2025 The MathWorks, Inc.
22
from pathlib import Path
33

44
import pytest
@@ -52,6 +52,9 @@ def test_shutdown_exception(mocker, server_process):
5252
mock_req_retry_session.return_value.delete.side_effect = Exception(
5353
"Server unreachable"
5454
)
55+
mock_psutil_process = mocker.patch(
56+
"psutil.Process", return_value=mocker.MagicMock()
57+
)
5558

5659
shutdown_response = server_process.shutdown()
5760

@@ -61,6 +64,7 @@ def test_shutdown_exception(mocker, server_process):
6164
headers={"Dummy_header": "Dummy_value"},
6265
)
6366
assert shutdown_response is None
67+
mock_psutil_process.return_value.kill.assert_called()
6468

6569

6670
def test_server_process_instantiation_from_string_correctly():

0 commit comments

Comments
 (0)