matlab-proxy 0.9.1__py3-none-any.whl → 0.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of matlab-proxy might be problematic. Click here for more details.
- matlab_proxy/app.py +3 -0
- matlab_proxy/app_state.py +251 -177
- matlab_proxy/constants.py +7 -5
- matlab_proxy/gui/asset-manifest.json +3 -3
- matlab_proxy/gui/index.html +1 -1
- matlab_proxy/gui/static/js/{main.daea642c.js → main.fbc5c728.js} +3 -3
- matlab_proxy/gui/static/js/main.fbc5c728.js.map +1 -0
- matlab_proxy/settings.py +31 -1
- matlab_proxy/util/mwi/embedded_connector/request.py +1 -1
- matlab_proxy/util/mwi/environment_variables.py +70 -29
- matlab_proxy/util/mwi/logger.py +30 -24
- matlab_proxy/util/mwi/token_auth.py +3 -1
- {matlab_proxy-0.9.1.dist-info → matlab_proxy-0.10.1.dist-info}/METADATA +4 -1
- {matlab_proxy-0.9.1.dist-info → matlab_proxy-0.10.1.dist-info}/RECORD +19 -19
- {matlab_proxy-0.9.1.dist-info → matlab_proxy-0.10.1.dist-info}/WHEEL +1 -1
- matlab_proxy/gui/static/js/main.daea642c.js.map +0 -1
- /matlab_proxy/gui/static/js/{main.daea642c.js.LICENSE.txt → main.fbc5c728.js.LICENSE.txt} +0 -0
- {matlab_proxy-0.9.1.dist-info → matlab_proxy-0.10.1.dist-info}/LICENSE.md +0 -0
- {matlab_proxy-0.9.1.dist-info → matlab_proxy-0.10.1.dist-info}/entry_points.txt +0 -0
- {matlab_proxy-0.9.1.dist-info → matlab_proxy-0.10.1.dist-info}/top_level.txt +0 -0
matlab_proxy/app.py
CHANGED
|
@@ -152,6 +152,9 @@ async def get_env_config(req):
|
|
|
152
152
|
config = state.settings["env_config"]
|
|
153
153
|
config["authEnabled"] = state.settings["mwi_is_token_auth_enabled"]
|
|
154
154
|
|
|
155
|
+
config["useMOS"] = mwi_env.Experimental.should_use_mos_html()
|
|
156
|
+
config["useMRE"] = mwi_env.Experimental.should_use_mre_html()
|
|
157
|
+
|
|
155
158
|
# In a previously authenticated session, if the url is accessed without the token(using session cookie), send the token as well.
|
|
156
159
|
config["authStatus"] = True if await token_auth.authenticate_request(req) else False
|
|
157
160
|
return web.json_response(config)
|
matlab_proxy/app_state.py
CHANGED
|
@@ -8,9 +8,17 @@ import os
|
|
|
8
8
|
import time
|
|
9
9
|
from collections import deque
|
|
10
10
|
from datetime import datetime, timedelta, timezone
|
|
11
|
+
from typing import Final, Optional
|
|
11
12
|
|
|
12
13
|
from matlab_proxy import util
|
|
13
|
-
from matlab_proxy.settings import
|
|
14
|
+
from matlab_proxy.settings import (
|
|
15
|
+
get_process_startup_timeout,
|
|
16
|
+
)
|
|
17
|
+
from matlab_proxy.constants import (
|
|
18
|
+
CONNECTOR_SECUREPORT_FILENAME,
|
|
19
|
+
MATLAB_LOGS_FILE_NAME,
|
|
20
|
+
VERSION_INFO_FILE_NAME,
|
|
21
|
+
)
|
|
14
22
|
from matlab_proxy.util import mw, mwi, system, windows
|
|
15
23
|
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
16
24
|
from matlab_proxy.util.mwi import token_auth
|
|
@@ -25,7 +33,7 @@ from matlab_proxy.util.mwi.exceptions import (
|
|
|
25
33
|
UIVisibleFatalError,
|
|
26
34
|
log_error,
|
|
27
35
|
)
|
|
28
|
-
|
|
36
|
+
|
|
29
37
|
|
|
30
38
|
logger = mwi.logger.get()
|
|
31
39
|
|
|
@@ -36,7 +44,7 @@ class AppState:
|
|
|
36
44
|
"""
|
|
37
45
|
|
|
38
46
|
# Constants that are applicable to AppState class
|
|
39
|
-
MATLAB_PORT_CHECK_DELAY_IN_SECONDS = 1
|
|
47
|
+
MATLAB_PORT_CHECK_DELAY_IN_SECONDS: Final[int] = 1
|
|
40
48
|
|
|
41
49
|
def __init__(self, settings):
|
|
42
50
|
"""Parameterized constructor for the AppState class.
|
|
@@ -227,64 +235,91 @@ class AppState:
|
|
|
227
235
|
Returns:
|
|
228
236
|
String: Status of MATLAB. Returns either up, down or starting.
|
|
229
237
|
"""
|
|
230
|
-
|
|
231
238
|
# MATLAB can either be "up", "starting" or "down" state depending upon Xvfb, MATLAB and the Embedded Connector
|
|
232
|
-
matlab
|
|
233
|
-
|
|
239
|
+
# Return matlab status as "down" if the processes validation fails
|
|
240
|
+
if not self._are_required_processes_ready():
|
|
241
|
+
return "down"
|
|
242
|
+
|
|
243
|
+
# If execution reaches here, it implies that:
|
|
244
|
+
# 1) MATLAB process has started.
|
|
245
|
+
# 2) Embedded connector has not started yet.
|
|
246
|
+
return await self._get_matlab_connector_status()
|
|
247
|
+
|
|
248
|
+
def _are_required_processes_ready(
|
|
249
|
+
self, matlab_process=None, xvfb_process=None
|
|
250
|
+
) -> bool:
|
|
251
|
+
# Update the processes to what is tracked in the instance's processes if a None is received
|
|
252
|
+
if matlab_process is None:
|
|
253
|
+
matlab_process = self.processes["matlab"]
|
|
254
|
+
if xvfb_process is None:
|
|
255
|
+
xvfb_process = self.processes["xvfb"]
|
|
234
256
|
|
|
235
257
|
if system.is_linux():
|
|
236
|
-
if
|
|
258
|
+
if xvfb_process is None or xvfb_process.returncode is not None:
|
|
237
259
|
logger.debug(
|
|
238
260
|
"Xvfb has not started"
|
|
239
|
-
if
|
|
240
|
-
else f"Xvfb exited with returncode:{
|
|
261
|
+
if xvfb_process is None
|
|
262
|
+
else f"Xvfb exited with returncode:{xvfb_process.returncode}"
|
|
241
263
|
)
|
|
242
|
-
return
|
|
264
|
+
return False
|
|
243
265
|
|
|
244
|
-
if
|
|
266
|
+
if matlab_process is None or matlab_process.returncode is not None:
|
|
245
267
|
logger.debug(
|
|
246
268
|
"MATLAB has not started"
|
|
247
|
-
if
|
|
248
|
-
else f"MATLAB exited with returncode:{
|
|
269
|
+
if matlab_process is None
|
|
270
|
+
else f"MATLAB exited with returncode:{matlab_process.returncode}"
|
|
249
271
|
)
|
|
250
|
-
return
|
|
272
|
+
return False
|
|
251
273
|
|
|
252
274
|
elif system.is_mac():
|
|
253
|
-
if
|
|
275
|
+
if matlab_process is None or matlab_process.returncode is not None:
|
|
254
276
|
logger.debug(
|
|
255
277
|
"MATLAB has not started"
|
|
256
|
-
if
|
|
257
|
-
else f"MATLAB exited with returncode:{
|
|
278
|
+
if matlab_process is None
|
|
279
|
+
else f"MATLAB exited with returncode:{matlab_process.returncode}"
|
|
258
280
|
)
|
|
259
|
-
return
|
|
281
|
+
return False
|
|
260
282
|
|
|
261
283
|
# For windows platform
|
|
262
284
|
else:
|
|
263
|
-
if
|
|
285
|
+
if matlab_process is None or not matlab_process.is_running():
|
|
264
286
|
logger.debug(
|
|
265
287
|
"MATLAB has not started"
|
|
266
|
-
if
|
|
267
|
-
else f"MATLAB exited with returncode:{
|
|
288
|
+
if matlab_process is None
|
|
289
|
+
else f"MATLAB exited with returncode:{matlab_process.wait()}"
|
|
268
290
|
)
|
|
269
|
-
return
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
def _get_token_auth_headers(self) -> Optional[dict]:
|
|
296
|
+
"""Returns token info as headers if authentication is enabled.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
[Dict | None]: Returns token authentication headers if any.
|
|
300
|
+
"""
|
|
301
|
+
return (
|
|
302
|
+
{self.settings["mwi_auth_token_name"]: self.settings["mwi_auth_token_hash"]}
|
|
303
|
+
if self.settings["mwi_is_token_auth_enabled"]
|
|
304
|
+
else None
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
async def _get_matlab_connector_status(self) -> str:
|
|
308
|
+
"""Returns the status of MATLABs Embedded Connector.
|
|
270
309
|
|
|
310
|
+
Returns:
|
|
311
|
+
str: Returns any of "up", "down" or "starting" indicating the status of Embedded Connector.
|
|
312
|
+
"""
|
|
271
313
|
if not self.matlab_session_files["matlab_ready_file"].exists():
|
|
272
314
|
return "starting"
|
|
273
315
|
|
|
274
|
-
# If execution reaches here, it implies that:
|
|
275
|
-
# 1) MATLAB process has started.
|
|
276
|
-
# 2) Embedded connector has not started yet.
|
|
277
316
|
# Proceed to query the Embedded Connector about its state.
|
|
278
317
|
# matlab-proxy sends a request to itself to the endpoint: /messageservice/json/state
|
|
279
318
|
# which the server redirects to the matlab_view() function to handle (which then sends the request to EC)
|
|
280
319
|
# As the matlab_view is now a protected endpoint, we need to pass token information through headers.
|
|
281
320
|
|
|
282
321
|
# Include token information into the headers if authentication is enabled.
|
|
283
|
-
headers = (
|
|
284
|
-
{self.settings["mwi_auth_token_name"]: self.settings["mwi_auth_token_hash"]}
|
|
285
|
-
if self.settings["mwi_is_token_auth_enabled"]
|
|
286
|
-
else None
|
|
287
|
-
)
|
|
322
|
+
headers = self._get_token_auth_headers()
|
|
288
323
|
|
|
289
324
|
embedded_connector_status = await mwi.embedded_connector.request.get_state(
|
|
290
325
|
mwi_server_url=self.settings["mwi_server_url"],
|
|
@@ -300,16 +335,16 @@ class AppState:
|
|
|
300
335
|
self.embedded_connector_state = embedded_connector_status
|
|
301
336
|
|
|
302
337
|
if self.embedded_connector_state == "down":
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
338
|
+
# Even if the embedded connector's status is 'down', we return matlab status as
|
|
339
|
+
# 'starting' because the MATLAB process itself has been created and matlab-proxy
|
|
340
|
+
# is waiting for the embedded connector to start serving content.
|
|
306
341
|
matlab_status = "starting"
|
|
307
342
|
|
|
308
343
|
# Update time stamp when MATLAB state is "starting".
|
|
309
344
|
if not self.embedded_connector_start_time:
|
|
310
345
|
self.embedded_connector_start_time = time.time()
|
|
311
346
|
|
|
312
|
-
#
|
|
347
|
+
# Set matlab_status to "up" since embedded connector is up.
|
|
313
348
|
else:
|
|
314
349
|
matlab_status = "up"
|
|
315
350
|
|
|
@@ -395,12 +430,12 @@ class AppState:
|
|
|
395
430
|
Returns:
|
|
396
431
|
Boolean: True if MATLAB is Licensed. False otherwise.
|
|
397
432
|
"""
|
|
398
|
-
|
|
399
433
|
if self.licensing is not None:
|
|
400
|
-
|
|
401
|
-
|
|
434
|
+
logger.debug(f"Licensing type: {self.licensing.get('type')}")
|
|
435
|
+
if self.licensing.get("type") == "nlm":
|
|
436
|
+
if self.licensing.get("conn_str") is not None:
|
|
402
437
|
return True
|
|
403
|
-
elif self.licensing
|
|
438
|
+
elif self.licensing.get("type") == "mhlm":
|
|
404
439
|
if (
|
|
405
440
|
self.licensing.get("identity_token") is not None
|
|
406
441
|
and self.licensing.get("source_id") is not None
|
|
@@ -408,7 +443,7 @@ class AppState:
|
|
|
408
443
|
and self.licensing.get("entitlement_id") is not None
|
|
409
444
|
):
|
|
410
445
|
return True
|
|
411
|
-
elif self.licensing
|
|
446
|
+
elif self.licensing.get("type") == "existing_license":
|
|
412
447
|
return True
|
|
413
448
|
return False
|
|
414
449
|
|
|
@@ -514,7 +549,7 @@ class AppState:
|
|
|
514
549
|
def create_logs_dir_for_MATLAB(self):
|
|
515
550
|
"""Creates the root folder where MATLAB writes the ready file and updates attibutes on self."""
|
|
516
551
|
|
|
517
|
-
# NOTE It is not
|
|
552
|
+
# NOTE It is not guaranteed that the port will remain free!
|
|
518
553
|
# FIXME Because of https://github.com/http-party/node-http-proxy/issues/1342 the
|
|
519
554
|
# node application in development mode always uses port 31515 to bypass the
|
|
520
555
|
# reverse proxy. Once this is addressed, remove this special case.
|
|
@@ -644,10 +679,41 @@ class AppState:
|
|
|
644
679
|
# The mwi_logs_dir is where MATLAB will write any subsequent logs
|
|
645
680
|
matlab_env["MATLAB_LOG_DIR"] = str(self.mwi_logs_dir)
|
|
646
681
|
|
|
682
|
+
# Set MW_CONNECTOR_CONTEXT_ROOT for MPA
|
|
683
|
+
if mwi_env.Experimental.is_mpa_enabled():
|
|
684
|
+
matlab_env["MW_CONNECTOR_CONTEXT_ROOT"] = self.settings.get("base_url", "/")
|
|
685
|
+
logger.info(
|
|
686
|
+
f"MW_CONNECTOR_CONTEXT_ROOT is set to: {matlab_env['MW_CONNECTOR_CONTEXT_ROOT']}"
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
# Setup Simulink Online which requires a pre-warm stage
|
|
690
|
+
if mwi_env.Experimental.is_simulink_enabled():
|
|
691
|
+
logger.info("Enabling usage of Simulink Online...")
|
|
692
|
+
matlab_env["PREWARM_SIMULINK"] = "true"
|
|
693
|
+
|
|
647
694
|
# Env setup related to logging
|
|
648
695
|
# Very verbose logging in debug mode
|
|
649
696
|
if logger.isEnabledFor(logging.getLevelName("DEBUG")):
|
|
650
|
-
|
|
697
|
+
mwi_log_file = self.settings.get("mwi_log_file", None)
|
|
698
|
+
# If a log file is supplied to write matlab-proxy server logs,
|
|
699
|
+
# use it to write MATLAB logs too.
|
|
700
|
+
if mwi_log_file:
|
|
701
|
+
# Append MATLAB logs to matlab-proxy logs
|
|
702
|
+
matlab_env["MW_DIAGNOSTIC_DEST"] = f"file,append={mwi_log_file}"
|
|
703
|
+
|
|
704
|
+
elif system.is_posix():
|
|
705
|
+
matlab_env["MW_DIAGNOSTIC_DEST"] = "stdout"
|
|
706
|
+
|
|
707
|
+
else:
|
|
708
|
+
# On windows stdout is not supported yet.
|
|
709
|
+
# So, use the default log file for MATLAB logs
|
|
710
|
+
matlab_logs_file = self.mwi_logs_dir / MATLAB_LOGS_FILE_NAME
|
|
711
|
+
# Write MATLAB logs
|
|
712
|
+
matlab_env["MW_DIAGNOSTIC_DEST"] = f"file={matlab_logs_file}"
|
|
713
|
+
|
|
714
|
+
logger.info(
|
|
715
|
+
f"Writing MATLAB process logs to: {matlab_env['MW_DIAGNOSTIC_DEST']}"
|
|
716
|
+
)
|
|
651
717
|
matlab_env[
|
|
652
718
|
"MW_DIAGNOSTIC_SPEC"
|
|
653
719
|
] = "connector::http::server=all;connector::lifecycle=all"
|
|
@@ -755,6 +821,139 @@ class AppState:
|
|
|
755
821
|
# If something went wrong in starting matlab, return None
|
|
756
822
|
return None
|
|
757
823
|
|
|
824
|
+
async def __force_stop_matlab(self, error, task):
|
|
825
|
+
"""A private method to update self.error and force stop matlab"""
|
|
826
|
+
self.error = MatlabError(error)
|
|
827
|
+
logger.error(f"{task}: {error}")
|
|
828
|
+
|
|
829
|
+
# If force_quit is not set to True, stop_matlab() would try to
|
|
830
|
+
# send a HTTP request to the Embedded Connector (which is already "down")
|
|
831
|
+
await self.stop_matlab(force_quit=True)
|
|
832
|
+
|
|
833
|
+
async def __track_embedded_connector_state(self):
|
|
834
|
+
"""track_embedded_connector_state is an asyncio task to track the status of MATLAB Embedded Connector.
|
|
835
|
+
This task will start and stop with the MATLAB process.
|
|
836
|
+
"""
|
|
837
|
+
this_task = "track_embedded_connector_state:"
|
|
838
|
+
logger.debug(f"{this_task}: Starting task...")
|
|
839
|
+
|
|
840
|
+
while True:
|
|
841
|
+
if self.embedded_connector_state == "up":
|
|
842
|
+
logger.debug(
|
|
843
|
+
f"{this_task}: MATLAB Embedded Connector is up, not checking for any errors in MATLABs stderr pipe. Sleeping for 10 seconds..."
|
|
844
|
+
)
|
|
845
|
+
# Embedded connector is up, sleep for 10 seconds and recheck again
|
|
846
|
+
await asyncio.sleep(10)
|
|
847
|
+
continue
|
|
848
|
+
|
|
849
|
+
# Embedded connector is down, so check for how long it has been down and error out if necessary
|
|
850
|
+
# embedded_connector_start_time variable is updated by get_matlab_state().
|
|
851
|
+
else:
|
|
852
|
+
# If its not yet set, sleep for 1 second and recheck again
|
|
853
|
+
if not self.embedded_connector_start_time:
|
|
854
|
+
await asyncio.sleep(1)
|
|
855
|
+
continue
|
|
856
|
+
|
|
857
|
+
else:
|
|
858
|
+
time_diff = time.time() - self.embedded_connector_start_time
|
|
859
|
+
if time_diff > self.PROCESS_TIMEOUT:
|
|
860
|
+
# Since max allowed startup time has elapsed, it means that MATLAB is in a stuck state and cannot be launched.
|
|
861
|
+
# Set the error and stop matlab.
|
|
862
|
+
user_visible_error = "Unable to start MATLAB.\nTry again by clicking Start MATLAB."
|
|
863
|
+
|
|
864
|
+
if system.is_windows():
|
|
865
|
+
# In WINDOWS systems, errors are raised as UI windows and cannot be captured programmatically.
|
|
866
|
+
# So, raise a generic error wherever appropriate
|
|
867
|
+
generic_error = f"MATLAB did not start in {int(self.PROCESS_TIMEOUT)} seconds. Use Windows Remote Desktop to check for any errors."
|
|
868
|
+
logger.error(f":{this_task}: {generic_error}")
|
|
869
|
+
if len(self.logs["matlab"]) == 0:
|
|
870
|
+
await self.__force_stop_matlab(
|
|
871
|
+
user_visible_error, this_task
|
|
872
|
+
)
|
|
873
|
+
# Breaking out of the loop to end this task as matlab-proxy was unable to launch MATLAB successfully
|
|
874
|
+
# even after waiting for self.PROCESS_TIMEOUT
|
|
875
|
+
break
|
|
876
|
+
else:
|
|
877
|
+
# Do not stop the MATLAB process or break from the loop (the error type is unknown)
|
|
878
|
+
self.error = MatlabError(generic_error)
|
|
879
|
+
await asyncio.sleep(5)
|
|
880
|
+
continue
|
|
881
|
+
|
|
882
|
+
else:
|
|
883
|
+
# 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.
|
|
884
|
+
# Set the error and stop matlab.
|
|
885
|
+
logger.error(
|
|
886
|
+
f":{this_task}: MATLAB did not start in {int(self.PROCESS_TIMEOUT)} seconds!"
|
|
887
|
+
)
|
|
888
|
+
if len(self.logs["matlab"]) == 0:
|
|
889
|
+
await self.__force_stop_matlab(
|
|
890
|
+
user_visible_error, this_task
|
|
891
|
+
)
|
|
892
|
+
# Breaking out of the loop to end this task as matlab-proxy was unable to launch MATLAB successfully
|
|
893
|
+
# even after waiting for self.PROCESS_TIMEOUT
|
|
894
|
+
break
|
|
895
|
+
|
|
896
|
+
else:
|
|
897
|
+
logger.debug(
|
|
898
|
+
f"{this_task}: MATLAB has been in a 'starting' state for {int(time_diff)} seconds. Sleeping for 1 second..."
|
|
899
|
+
)
|
|
900
|
+
await asyncio.sleep(1)
|
|
901
|
+
|
|
902
|
+
async def __matlab_stderr_reader_posix(self):
|
|
903
|
+
"""matlab_stderr_reader_posix is an asyncio task which reads the stderr pipe of the MATLAB process, parses it
|
|
904
|
+
and updates state variables accordingly.
|
|
905
|
+
"""
|
|
906
|
+
if system.is_posix():
|
|
907
|
+
matlab = self.processes["matlab"]
|
|
908
|
+
logger.debug("matlab_stderr_reader_posix() task: Starting task...")
|
|
909
|
+
|
|
910
|
+
while not matlab.stderr.at_eof():
|
|
911
|
+
logger.debug(
|
|
912
|
+
"matlab_stderr_reader_posix() task: Waiting to read data from stderr pipe..."
|
|
913
|
+
)
|
|
914
|
+
line = await matlab.stderr.readline()
|
|
915
|
+
if line is None:
|
|
916
|
+
logger.debug(
|
|
917
|
+
"matlab_stderr_reader_posix() task: Received data from stderr pipe appending to logs..."
|
|
918
|
+
)
|
|
919
|
+
break
|
|
920
|
+
self.logs["matlab"].append(line)
|
|
921
|
+
await self.handle_matlab_output()
|
|
922
|
+
|
|
923
|
+
async def __update_matlab_port(self, delay: int):
|
|
924
|
+
"""Task to populate matlab_port from the matlab ready file. Times out if max_duration is breached
|
|
925
|
+
|
|
926
|
+
Args:
|
|
927
|
+
delay (int): time delay in seconds before retrying the file read operation
|
|
928
|
+
"""
|
|
929
|
+
logger.debug(
|
|
930
|
+
f'updating matlab_port information from {self.matlab_session_files["matlab_ready_file"]}'
|
|
931
|
+
)
|
|
932
|
+
try:
|
|
933
|
+
await asyncio.wait_for(
|
|
934
|
+
self.__read_matlab_ready_file(delay),
|
|
935
|
+
self.PROCESS_TIMEOUT,
|
|
936
|
+
)
|
|
937
|
+
except asyncio.TimeoutError:
|
|
938
|
+
logger.debug(
|
|
939
|
+
"Timeout error received while updating matlab port, stopping matlab!"
|
|
940
|
+
)
|
|
941
|
+
await self.stop_matlab(force_quit=True)
|
|
942
|
+
self.error = MatlabError(
|
|
943
|
+
"Unable to start MATLAB because of a timeout. Try again by clicking Start MATLAB."
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
async def __read_matlab_ready_file(self, delay):
|
|
947
|
+
# reads with delays from the file where connector has written its port information
|
|
948
|
+
while not self.matlab_session_files["matlab_ready_file"].exists():
|
|
949
|
+
await asyncio.sleep(delay)
|
|
950
|
+
|
|
951
|
+
with open(self.matlab_session_files["matlab_ready_file"]) as f:
|
|
952
|
+
self.matlab_port = int(f.read())
|
|
953
|
+
logger.debug(
|
|
954
|
+
f"MATLAB Ready file successfully read, matlab_port set to: {self.matlab_port}"
|
|
955
|
+
)
|
|
956
|
+
|
|
758
957
|
async def start_matlab(self, restart_matlab=False):
|
|
759
958
|
"""Start MATLAB.
|
|
760
959
|
|
|
@@ -816,145 +1015,16 @@ class AppState:
|
|
|
816
1015
|
logger.debug(f"Started MATLAB (PID={matlab.pid})")
|
|
817
1016
|
self.processes["matlab"] = matlab
|
|
818
1017
|
|
|
819
|
-
async def __track_embedded_connector_state():
|
|
820
|
-
"""track_embedded_connector_state is an asyncio task to track the status of MATLAB Embedded Connector.
|
|
821
|
-
This task will start and stop with the MATLAB process.
|
|
822
|
-
"""
|
|
823
|
-
this_task = "track_embedded_connector_state:"
|
|
824
|
-
logger.debug(f"{this_task}: Starting task...")
|
|
825
|
-
|
|
826
|
-
while True:
|
|
827
|
-
if self.embedded_connector_state == "up":
|
|
828
|
-
logger.debug(
|
|
829
|
-
f"{this_task}: MATLAB Embedded Connector is up, not checking for any errors in MATLABs stderr pipe. Sleeping for 10 seconds..."
|
|
830
|
-
)
|
|
831
|
-
# Embedded connector is up, sleep for 10 seconds and recheck again
|
|
832
|
-
await asyncio.sleep(10)
|
|
833
|
-
continue
|
|
834
|
-
|
|
835
|
-
# Embedded connector is down, so check for how long it has been down and error out if necessary
|
|
836
|
-
# embedded_connector_start_time variable is updated by get_matlab_state().
|
|
837
|
-
else:
|
|
838
|
-
# If its not yet set, sleep for 1 second and recheck again
|
|
839
|
-
if not self.embedded_connector_start_time:
|
|
840
|
-
await asyncio.sleep(1)
|
|
841
|
-
continue
|
|
842
|
-
|
|
843
|
-
else:
|
|
844
|
-
time_diff = time.time() - self.embedded_connector_start_time
|
|
845
|
-
if time_diff > self.PROCESS_TIMEOUT:
|
|
846
|
-
# Since max allowed startup time has elapsed, it means that MATLAB is in a stuck state and cannot be launched.
|
|
847
|
-
# Set the error and stop matlab.
|
|
848
|
-
user_visible_error = "Unable to start MATLAB.\nTry again by clicking Start MATLAB."
|
|
849
|
-
|
|
850
|
-
async def __force_stop_matlab(error):
|
|
851
|
-
"""A private method to update self.error and force stop matlab"""
|
|
852
|
-
self.error = MatlabError(error)
|
|
853
|
-
logger.error(f"{this_task}: {error}")
|
|
854
|
-
|
|
855
|
-
# If force_quit is not set to True, stop_matlab() would try to
|
|
856
|
-
# send a HTTP request to the Embedded Connector (which is already "down")
|
|
857
|
-
await self.stop_matlab(force_quit=True)
|
|
858
|
-
|
|
859
|
-
if system.is_windows():
|
|
860
|
-
# In WINDOWS systems, errors are raised as UI windows and cannot be captured programmatically.
|
|
861
|
-
# So, raise a generic error wherever appropriate
|
|
862
|
-
generic_error = f"MATLAB did not start in {int(self.PROCESS_TIMEOUT)} seconds. Use Windows Remote Desktop to check for any errors."
|
|
863
|
-
logger.error(f":{this_task}: {generic_error}")
|
|
864
|
-
if len(self.logs["matlab"]) == 0:
|
|
865
|
-
await __force_stop_matlab(user_visible_error)
|
|
866
|
-
# Breaking out of the loop to end this task as matlab-proxy was unable to launch MATLAB successfully
|
|
867
|
-
# even after waiting for self.PROCESS_TIMEOUT
|
|
868
|
-
break
|
|
869
|
-
else:
|
|
870
|
-
# Do not stop the MATLAB process or break from the loop (the error type is unknown)
|
|
871
|
-
self.error = MatlabError(generic_error)
|
|
872
|
-
await asyncio.sleep(5)
|
|
873
|
-
continue
|
|
874
|
-
|
|
875
|
-
else:
|
|
876
|
-
# 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.
|
|
877
|
-
# Set the error and stop matlab.
|
|
878
|
-
logger.error(
|
|
879
|
-
f":{this_task}: MATLAB did not start in {int(self.PROCESS_TIMEOUT)} seconds!"
|
|
880
|
-
)
|
|
881
|
-
if len(self.logs["matlab"]) == 0:
|
|
882
|
-
await __force_stop_matlab(user_visible_error)
|
|
883
|
-
# Breaking out of the loop to end this task as matlab-proxy was unable to launch MATLAB successfully
|
|
884
|
-
# even after waiting for self.PROCESS_TIMEOUT
|
|
885
|
-
break
|
|
886
|
-
|
|
887
|
-
else:
|
|
888
|
-
logger.debug(
|
|
889
|
-
f"{this_task}: MATLAB has been in a 'starting' state for {int(time_diff)} seconds. Sleeping for 1 second..."
|
|
890
|
-
)
|
|
891
|
-
await asyncio.sleep(1)
|
|
892
|
-
|
|
893
|
-
async def __matlab_stderr_reader_posix():
|
|
894
|
-
"""matlab_stderr_reader_posix is an asyncio task which reads the stderr pipe of the MATLAB process, parses it
|
|
895
|
-
and updates state variables accordingly.
|
|
896
|
-
"""
|
|
897
|
-
if system.is_posix():
|
|
898
|
-
matlab = self.processes["matlab"]
|
|
899
|
-
logger.debug("matlab_stderr_reader_posix() task: Starting task...")
|
|
900
|
-
|
|
901
|
-
while not matlab.stderr.at_eof():
|
|
902
|
-
logger.debug(
|
|
903
|
-
"matlab_stderr_reader_posix() task: Waiting to read data from stderr pipe..."
|
|
904
|
-
)
|
|
905
|
-
line = await matlab.stderr.readline()
|
|
906
|
-
if line is None:
|
|
907
|
-
logger.debug(
|
|
908
|
-
"matlab_stderr_reader_posix() task: Received data from stderr pipe appending to logs..."
|
|
909
|
-
)
|
|
910
|
-
break
|
|
911
|
-
self.logs["matlab"].append(line)
|
|
912
|
-
await self.handle_matlab_output()
|
|
913
|
-
|
|
914
|
-
async def __update_matlab_port(delay: int):
|
|
915
|
-
"""Task to populate matlab_port from the matlab ready file. Times out if max_duration is breached
|
|
916
|
-
|
|
917
|
-
Args:
|
|
918
|
-
delay (int): time delay in seconds before retrying the file read operation
|
|
919
|
-
"""
|
|
920
|
-
logger.debug(
|
|
921
|
-
f'updating matlab_port information from {self.matlab_session_files["matlab_ready_file"]}'
|
|
922
|
-
)
|
|
923
|
-
try:
|
|
924
|
-
await asyncio.wait_for(
|
|
925
|
-
__read_matlab_ready_file(delay),
|
|
926
|
-
self.PROCESS_TIMEOUT,
|
|
927
|
-
)
|
|
928
|
-
except asyncio.TimeoutError:
|
|
929
|
-
logger.debug(
|
|
930
|
-
"Timeout error received while updating matlab port, stopping matlab!"
|
|
931
|
-
)
|
|
932
|
-
await self.stop_matlab(force_quit=True)
|
|
933
|
-
self.error = MatlabError(
|
|
934
|
-
"Unable to start MATLAB because of a timeout. Try again by clicking Start MATLAB."
|
|
935
|
-
)
|
|
936
|
-
|
|
937
|
-
async def __read_matlab_ready_file(delay):
|
|
938
|
-
# reads with delays from the file where connector has written its port information
|
|
939
|
-
while not self.matlab_session_files["matlab_ready_file"].exists():
|
|
940
|
-
await asyncio.sleep(delay)
|
|
941
|
-
|
|
942
|
-
with open(self.matlab_session_files["matlab_ready_file"]) as f:
|
|
943
|
-
self.matlab_port = int(f.read())
|
|
944
|
-
logger.debug(
|
|
945
|
-
f"MATLAB Ready file successfully read, matlab_port set to: {self.matlab_port}"
|
|
946
|
-
)
|
|
947
|
-
|
|
948
1018
|
loop = util.get_event_loop()
|
|
949
1019
|
# Start all tasks relevant to MATLAB process
|
|
950
1020
|
self.tasks["matlab_stderr_reader_posix"] = loop.create_task(
|
|
951
|
-
__matlab_stderr_reader_posix()
|
|
1021
|
+
self.__matlab_stderr_reader_posix()
|
|
952
1022
|
)
|
|
953
1023
|
self.tasks["track_embedded_connector_state"] = loop.create_task(
|
|
954
|
-
__track_embedded_connector_state()
|
|
1024
|
+
self.__track_embedded_connector_state()
|
|
955
1025
|
)
|
|
956
1026
|
self.tasks["update_matlab_port"] = loop.create_task(
|
|
957
|
-
__update_matlab_port(self.MATLAB_PORT_CHECK_DELAY_IN_SECONDS)
|
|
1027
|
+
self.__update_matlab_port(self.MATLAB_PORT_CHECK_DELAY_IN_SECONDS)
|
|
958
1028
|
)
|
|
959
1029
|
|
|
960
1030
|
"""
|
|
@@ -980,7 +1050,7 @@ class AppState:
|
|
|
980
1050
|
except asyncio.CancelledError:
|
|
981
1051
|
pass
|
|
982
1052
|
If the request fails, the server process exits with error code 1.
|
|
983
|
-
|
|
1053
|
+
|
|
984
1054
|
url = self.settings["mwi_server_url"] + "/terminate_integration"
|
|
985
1055
|
|
|
986
1056
|
try:
|
|
@@ -998,7 +1068,7 @@ class AppState:
|
|
|
998
1068
|
await matlab.wait()
|
|
999
1069
|
except:
|
|
1000
1070
|
logger.info(
|
|
1001
|
-
f"Exception
|
|
1071
|
+
f"Exception occurred during termination of MATLAB process with PID: {matlab.pid}!"
|
|
1002
1072
|
)
|
|
1003
1073
|
pass
|
|
1004
1074
|
|
|
@@ -1026,12 +1096,16 @@ class AppState:
|
|
|
1026
1096
|
|
|
1027
1097
|
try:
|
|
1028
1098
|
data = mwi.embedded_connector.helpers.get_data_to_eval_mcode("exit")
|
|
1099
|
+
headers = self._get_token_auth_headers()
|
|
1029
1100
|
url = mwi.embedded_connector.helpers.get_mvm_endpoint(
|
|
1030
1101
|
self.settings["mwi_server_url"]
|
|
1031
1102
|
)
|
|
1032
1103
|
|
|
1033
1104
|
resp_json = await mwi.embedded_connector.send_request(
|
|
1034
|
-
url=url,
|
|
1105
|
+
url=url,
|
|
1106
|
+
method="POST",
|
|
1107
|
+
data=data,
|
|
1108
|
+
headers=headers,
|
|
1035
1109
|
)
|
|
1036
1110
|
|
|
1037
1111
|
if resp_json["messages"]["EvalResponse"][0]["isError"]:
|
|
@@ -1061,7 +1135,7 @@ class AppState:
|
|
|
1061
1135
|
session_file.unlink()
|
|
1062
1136
|
|
|
1063
1137
|
# In posix systems, variable matlab is an instance of asyncio.subprocess.Process()
|
|
1064
|
-
# In windows systems, variable matlab is an
|
|
1138
|
+
# In windows systems, variable matlab is an instance of psutil.Process()
|
|
1065
1139
|
matlab = self.processes["matlab"]
|
|
1066
1140
|
|
|
1067
1141
|
waiters = []
|
matlab_proxy/constants.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2023 The MathWorks, Inc.
|
|
2
|
+
from typing import Final
|
|
2
3
|
|
|
3
4
|
"""This module defines project-level constants"""
|
|
4
5
|
|
|
5
|
-
CONNECTOR_SECUREPORT_FILENAME = "connector.securePort"
|
|
6
|
-
VERSION_INFO_FILE_NAME = "VersionInfo.xml"
|
|
7
|
-
MAX_HTTP_REQUEST_SIZE = 500_000_000 # 500MB
|
|
6
|
+
CONNECTOR_SECUREPORT_FILENAME: Final[str] = "connector.securePort"
|
|
7
|
+
VERSION_INFO_FILE_NAME: Final[str] = "VersionInfo.xml"
|
|
8
|
+
MAX_HTTP_REQUEST_SIZE: Final[int] = 500_000_000 # 500MB
|
|
9
|
+
MATLAB_LOGS_FILE_NAME: Final[str] = "matlab_logs.txt"
|
|
8
10
|
|
|
9
11
|
# Max startup duration in seconds for processes launched by matlab-proxy
|
|
10
12
|
# This constant is meant for internal use within matlab-proxy
|
|
11
13
|
# Clients of this package should use settings.py::get_process_startup_timeout() function
|
|
12
|
-
DEFAULT_PROCESS_START_TIMEOUT =
|
|
14
|
+
DEFAULT_PROCESS_START_TIMEOUT: Final[int] = 600
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
3
|
"main.css": "./static/css/main.aa888962.css",
|
|
4
|
-
"main.js": "./static/js/main.
|
|
4
|
+
"main.js": "./static/js/main.fbc5c728.js",
|
|
5
5
|
"static/media/mathworks-pictograms.svg?20181009": "./static/media/mathworks-pictograms.f6f087b008b5a9435f26.svg",
|
|
6
6
|
"static/media/MATLAB-env-blur.png": "./static/media/MATLAB-env-blur.4fc94edbc82d3184e5cb.png",
|
|
7
7
|
"static/media/mathworks.svg?20181004": "./static/media/mathworks.80a3218e1ba29f0573fb.svg",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"static/media/gripper.svg": "./static/media/gripper.9defbc5e76d0de8bb6e0.svg",
|
|
36
36
|
"static/media/arrow.svg": "./static/media/arrow.0c2968b90bd9a64c8c3f.svg",
|
|
37
37
|
"main.aa888962.css.map": "./static/css/main.aa888962.css.map",
|
|
38
|
-
"main.
|
|
38
|
+
"main.fbc5c728.js.map": "./static/js/main.fbc5c728.js.map"
|
|
39
39
|
},
|
|
40
40
|
"entrypoints": [
|
|
41
41
|
"static/css/main.aa888962.css",
|
|
42
|
-
"static/js/main.
|
|
42
|
+
"static/js/main.fbc5c728.js"
|
|
43
43
|
]
|
|
44
44
|
}
|
matlab_proxy/gui/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MATLAB"/><meta name="internal_mw_identifier" content="MWI_MATLAB_PROXY_IDENTIFIER"/><link rel="manifest" href="./manifest.json"/><title>MATLAB</title><script defer="defer" src="./static/js/main.
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MATLAB"/><meta name="internal_mw_identifier" content="MWI_MATLAB_PROXY_IDENTIFIER"/><link rel="manifest" href="./manifest.json"/><title>MATLAB</title><script defer="defer" src="./static/js/main.fbc5c728.js"></script><link href="./static/css/main.aa888962.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|