33import os
44import secrets
55import subprocess
6- from typing import Optional
6+ from typing import List , Optional , Tuple
77
88import matlab_proxy
99import matlab_proxy .util .system as mwi_sys
2020
2121
2222async def start_matlab_proxy_for_kernel (
23- caller_id : str , parent_id : str , is_isolated_matlab : bool
23+ caller_id : str , parent_id : str , is_shared_matlab : bool
2424):
2525 """
2626 Starts a MATLAB proxy server specifically for MATLAB Kernel.
@@ -29,12 +29,12 @@ async def start_matlab_proxy_for_kernel(
2929 set to None, for starting the MATLAB proxy server via proxy manager.
3030 """
3131 return await _start_matlab_proxy (
32- caller_id = caller_id , ctx = parent_id , is_isolated_matlab = is_isolated_matlab
32+ caller_id = caller_id , ctx = parent_id , is_shared_matlab = is_shared_matlab
3333 )
3434
3535
3636async def start_matlab_proxy_for_jsp (
37- parent_id : str , is_isolated_matlab : bool , mpm_auth_token : str
37+ parent_id : str , is_shared_matlab : bool , mpm_auth_token : str
3838):
3939 """
4040 Starts a MATLAB proxy server specifically for Jupyter Server Proxy (JSP) - Open MATLAB launcher.
@@ -45,7 +45,7 @@ async def start_matlab_proxy_for_jsp(
4545 return await _start_matlab_proxy (
4646 caller_id = "jsp" ,
4747 ctx = parent_id ,
48- is_isolated_matlab = is_isolated_matlab ,
48+ is_shared_matlab = is_shared_matlab ,
4949 mpm_auth_token = mpm_auth_token ,
5050 )
5151
@@ -60,7 +60,7 @@ async def _start_matlab_proxy(**options) -> Optional[dict]:
6060 Args (keyword arguments):
6161 - caller_id (str): The identifier for the caller (kernel id for kernels, "jsp" for JSP).
6262 - ctx (str): The context in which the server is being started (parent pid).
63- - is_isolated_matlab (bool, optional): Whether to start an isolated MATLAB proxy instance.
63+ - is_shared_matlab (bool, optional): Whether to start a shared MATLAB proxy instance.
6464 Defaults to False.
6565 - mpm_auth_token (str, optional): The MATLAB proxy manager token. If not provided,
6666 a new token is generated. Defaults to None.
@@ -69,26 +69,33 @@ async def _start_matlab_proxy(**options) -> Optional[dict]:
6969 ServerProcess: The process representing the MATLAB proxy server.
7070
7171 Raises:
72- ValueError: If `caller_id` is "default" and `is_isolated_matlab ` is True .
72+ ValueError: If `caller_id` is "default" and `is_shared_matlab ` is False .
7373 """
74- caller_id : str = options .get ("caller_id" )
75- ctx : str = options .get ("ctx" )
76- is_isolated_matlab : bool = options .get ("is_isolated_matlab" , False )
74+ # Validate arguments
75+ required_args : List [str ] = ["caller_id" , "ctx" , "is_shared_matlab" ]
76+ missing_args : List [str ] = [arg for arg in required_args if arg not in options ]
77+
78+ if missing_args :
79+ raise ValueError (f"Missing required arguments: { ', ' .join (missing_args )} " )
80+
81+ caller_id : str = options ["caller_id" ]
82+ ctx : str = options ["ctx" ]
83+ is_shared_matlab : bool = options .get ("is_shared_matlab" , True )
7784 mpm_auth_token : Optional [str ] = options .get ("mpm_auth_token" , None )
7885
79- if is_isolated_matlab and caller_id == "default" :
86+ if not is_shared_matlab and caller_id == "default" :
8087 raise ValueError (
81- "Caller id cannot be default when isolated_matlab is set to true "
88+ "Caller id cannot be default when matlab proxy is not shareable "
8289 )
8390
8491 mpm_auth_token = mpm_auth_token or secrets .token_hex (32 )
8592
8693 # Cleanup stale entries before starting new instance of matlab proxy server
8794 helpers ._are_orphaned_servers_deleted (ctx )
8895
89- ident = caller_id if is_isolated_matlab else "default"
96+ ident = caller_id if not is_shared_matlab else "default"
9097 key = f"{ ctx } _{ ident } "
91- log .debug ("Starting matlab proxy using %s, %s, %s" , ctx , ident , is_isolated_matlab )
98+ log .debug ("Starting matlab proxy using %s, %s, %s" , ctx , ident , is_shared_matlab )
9299
93100 data_dir = helpers .create_and_get_proxy_manager_data_dir ()
94101 server_process = ServerProcess .find_existing_server (data_dir , key )
@@ -101,10 +108,8 @@ async def _start_matlab_proxy(**options) -> Optional[dict]:
101108
102109 # Create a new matlab proxy server
103110 else :
104- server_process : ServerProcess | None = (
105- await _start_subprocess_and_check_for_readiness (
106- ident , ctx , key , is_isolated_matlab , mpm_auth_token
107- )
111+ server_process = await _start_subprocess_and_check_for_readiness (
112+ ident , ctx , key , is_shared_matlab , mpm_auth_token
108113 )
109114
110115 # Store the newly created server into filesystem
@@ -115,7 +120,7 @@ async def _start_matlab_proxy(**options) -> Optional[dict]:
115120
116121
117122async def _start_subprocess_and_check_for_readiness (
118- server_id : str , ctx : str , key : str , isolated : bool , mpm_auth_token : str
123+ server_id : str , ctx : str , key : str , is_shared_matlab : bool , mpm_auth_token : str
119124) -> Optional [ServerProcess ]:
120125 """
121126 Starts a MATLAB proxy server.
@@ -136,10 +141,12 @@ async def _start_subprocess_and_check_for_readiness(
136141 matlab_proxy_cmd , matlab_proxy_env = _prepare_cmd_and_env_for_matlab_proxy ()
137142
138143 # Start the matlab proxy process
139- process_id , url , mwi_base_url = await _start_subprocess (
140- matlab_proxy_cmd , matlab_proxy_env , server_id
141- )
144+ result = await _start_subprocess (matlab_proxy_cmd , matlab_proxy_env , server_id )
145+ if not result :
146+ log .error ("Could not start matlab proxy" )
147+ return None
142148
149+ process_id , url , mwi_base_url = result
143150 server_process = None
144151
145152 # Check for the matlab proxy server readiness
@@ -151,14 +158,14 @@ async def _start_subprocess_and_check_for_readiness(
151158 headers = helpers .convert_mwi_env_vars_to_header_format (
152159 matlab_proxy_env , "MWI"
153160 ),
154- pid = process_id ,
161+ pid = str ( process_id ) ,
155162 parent_pid = ctx ,
156163 id = key ,
157- type = "named " if isolated else "shared " ,
164+ type = "shared " if is_shared_matlab else "named " ,
158165 mpm_auth_token = mpm_auth_token ,
159166 )
160167 else :
161- log .error ("Could not start matlab proxy " )
168+ log .error ("matlab-proxy server never became ready " )
162169
163170 return server_process
164171
@@ -189,7 +196,7 @@ def _prepare_cmd_and_env_for_matlab_proxy():
189196 return matlab_proxy_cmd , matlab_proxy_env
190197
191198
192- async def _start_subprocess (cmd , env , server_id ) -> Optional [int ]:
199+ async def _start_subprocess (cmd , env , server_id ) -> Optional [Tuple [ int , str , str ] ]:
193200 """
194201 Initializes and starts a subprocess using the specified command and provided environment.
195202
@@ -199,40 +206,69 @@ async def _start_subprocess(cmd, env, server_id) -> Optional[int]:
199206 process = None
200207 mwi_base_url : str = f"{ constants .MWI_BASE_URL_PREFIX } { server_id } "
201208
202- # Get a free port, closer to starting the matlab proxy appx
203- port : str = helpers .find_free_port ()
204- env .update (
205- {
206- "MWI_APP_PORT" : port ,
207- "MWI_BASE_URL" : mwi_base_url ,
208- }
209- )
209+ # Get a free port and corresponding bound socket
210+ with helpers .find_free_port () as ( port , _ ):
211+ env .update (
212+ {
213+ "MWI_APP_PORT" : port ,
214+ "MWI_BASE_URL" : mwi_base_url ,
215+ }
216+ )
210217
211- # Using loopback address so that DNS resolution doesn't add latency in Windows
212- url : str = f"http://127.0.0.1:{ port } "
218+ # Using loopback address so that DNS resolution doesn't add latency in Windows
219+ url : str = f"http://127.0.0.1:{ port } "
213220
214- if mwi_sys .is_posix ():
215- process = await asyncio .create_subprocess_exec (
216- * cmd ,
217- env = env ,
218- )
219- log .debug ("Started matlab proxy subprocess for posix" )
220- else :
221- process = subprocess .Popen (
222- cmd ,
223- env = env ,
224- )
225- log .debug ("Started matlab proxy subprocess for windows" )
221+ process = await _initialize_process_based_on_os_type (cmd , env )
226222
227223 if not process :
228- log .error ("Matlab proxy process not created %d" , process . returncode )
224+ log .error ("Matlab proxy process not created due to some error" )
229225 return None
230226
231227 process_pid = process .pid
232228 log .debug ("MATLAB proxy info: pid = %s, rc = %s" , process_pid , process .returncode )
233229 return process_pid , url , mwi_base_url
234230
235231
232+ async def _initialize_process_based_on_os_type (cmd , env ):
233+ """
234+ Initializes and starts a subprocess based on the operating system.
235+
236+ This function attempts to create a subprocess using the provided command and
237+ environment variables. It handles both POSIX and Windows systems differently.
238+
239+ Args:
240+ cmd (List[str]): The command to execute in the subprocess.
241+ env (Dict[str, str]): The environment variables for the subprocess.
242+
243+ Returns:
244+ Union[Process, None, Popen[bytes]]: The created subprocess object if successful,
245+ or None if an error occurs during subprocess creation.
246+
247+ Raises:
248+ Exception: If there's an error creating the subprocess (caught and logged).
249+ """
250+ if mwi_sys .is_posix ():
251+ log .debug ("Starting matlab proxy subprocess for posix" )
252+ try :
253+ return await asyncio .create_subprocess_exec (
254+ * cmd ,
255+ env = env ,
256+ )
257+ except Exception as e :
258+ log .error ("Failed to create posix subprocess: %s" , e )
259+ return None
260+ else :
261+ try :
262+ log .debug ("Starting matlab proxy subprocess for windows" )
263+ return subprocess .Popen (
264+ cmd ,
265+ env = env ,
266+ )
267+ except Exception as e :
268+ log .error ("Failed to create windows subprocess: %s" , e )
269+ return None
270+
271+
236272async def shutdown (parent_pid : str , caller_id : str , mpm_auth_token : str ):
237273 """
238274 Shutdown the MATLAB proxy server if the provided authentication token is valid.
0 commit comments