Skip to content

Commit 915e297

Browse files
authored
FEATURE: Auto select server port (#479)
1 parent 0400364 commit 915e297

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

src/ansys/edb/core/session.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Session manager for gRPC."""
2+
23
from contextlib import contextmanager
34
from enum import Enum
5+
import errno
46
from shutil import which
7+
import socket
58
from struct import pack, unpack
69
import subprocess
710
from sys import modules
@@ -142,6 +145,8 @@
142145
from ansys.edb.core.inner.exceptions import EDBSessionException, ErrorCode
143146
from ansys.edb.core.inner.interceptors import ExceptionInterceptor, LoggingInterceptor
144147

148+
DEFAULT_ADDRESS = "localhost"
149+
145150
# The session module singleton
146151
MOD = modules[__name__]
147152
MOD.current_session = None
@@ -172,8 +177,8 @@ def __init__(self, ip_address, port_num, ansys_em_root):
172177
if MOD.current_session is not None:
173178
raise EDBSessionException(ErrorCode.STARTUP_MULTI_SESSIONS)
174179

175-
self.ip_address = ip_address or "localhost"
176-
self.port_num = port_num
180+
self.ip_address = ip_address or DEFAULT_ADDRESS
181+
self.port_num = port_num or _find_available_port()
177182
self.ansys_em_root = ansys_em_root
178183
self.channel = None
179184
self.local_server_proc = None
@@ -457,8 +462,8 @@ def launch_session(ansys_em_root, port_num=None):
457462
ansys_em_root : str
458463
Directory where the ``EDB_RPC_Server.exe`` file is installed.
459464
port_num : int, default: None
460-
Port number to listen on. The default is ``None``, in which
461-
case localhost is used.
465+
Port number to listen on. The default is ``None``, in which case a port in [50051, 60000]
466+
is selected.
462467
463468
Examples
464469
--------
@@ -480,15 +485,16 @@ def launch_session(ansys_em_root, port_num=None):
480485

481486

482487
@contextmanager
483-
def session(ansys_em_root, port_num, ip_address=None):
488+
def session(ansys_em_root: str, port_num: int = None, ip_address: str = None):
484489
r"""Launch a local session to an EDB API server in a context manager.
485490
486491
Parameters
487492
----------
488493
ansys_em_root : str
489494
Directory where the ``EDB_RPC_Server.exe`` file is installed.
490-
port_num : int
491-
Port number to listen on.
495+
port_num : int, default: None
496+
Port number to listen on. The default is ``None``, in which case a port in [50051, 60000]
497+
is selected.
492498
ip_address : str, default: None
493499
IP address where the server executable file is running. The default is ``None``, in which
494500
case localhost is used.
@@ -573,3 +579,22 @@ def _ensure_session(ansys_em_root, port_num, ip_address):
573579
else:
574580
MOD.current_session = _Session(ip_address, port_num, ansys_em_root)
575581
MOD.current_session.connect()
582+
583+
584+
def _find_available_port(interface: str = None, start_port: int = 50051, end_port: int = 60000):
585+
"""Find an available port in the given range.
586+
587+
Parameters
588+
----------
589+
interface : str, default: :data:`DEFAULT_ADDRESS`
590+
Interface to check for available ports.
591+
start_port : int, default: ``50051``
592+
First port number to check.
593+
end_port : int, default: ``60000``
594+
Last port number to check.
595+
"""
596+
for port in range(start_port, end_port):
597+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
598+
if sock.connect_ex((interface or DEFAULT_ADDRESS, port)) == errno.ECONNREFUSED:
599+
return port
600+
raise RuntimeError("No available ports found")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from contextlib import closing
2+
from pathlib import Path
3+
import socket
4+
5+
import settings
6+
7+
from ansys.edb.core.database import Database
8+
import ansys.edb.core.session as session
9+
10+
11+
def test_launch_session_when_default_port_in_use(new_database_path: Path):
12+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
13+
sock.bind(("127.0.0.1", 50051))
14+
sess: session._Session = session.launch_session(settings.server_exe_dir())
15+
try:
16+
assert isinstance(sess.port_num, int) and sess.port_num != 50051
17+
with closing(Database.create(new_database_path)) as db:
18+
assert not db.is_null
19+
finally:
20+
sess.disconnect()
21+
22+
23+
def test_session_when_default_port_in_use(new_database_path: Path):
24+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
25+
sock.bind(("127.0.0.1", 50051))
26+
with session.session(settings.server_exe_dir()):
27+
sess: session._Session = session.MOD.current_session
28+
assert isinstance(sess.port_num, int) and sess.port_num != 50051
29+
with closing(Database.create(new_database_path)) as db:
30+
assert not db.is_null

0 commit comments

Comments
 (0)