diff --git a/testgres/consts.py b/testgres/consts.py index 89c49ab..d358920 100644 --- a/testgres/consts.py +++ b/testgres/consts.py @@ -10,6 +10,10 @@ TMP_CACHE = 'tgsc_' TMP_BACKUP = 'tgsb_' +TMP_TESTGRES = "testgres" + +TMP_TESTGRES_PORTS = TMP_TESTGRES + "/ports" + # path to control file XLOG_CONTROL_FILE = "global/pg_control" diff --git a/testgres/impl/port_manager__generic.py b/testgres/impl/port_manager__generic.py index a51af2b..3f312c1 100755 --- a/testgres/impl/port_manager__generic.py +++ b/testgres/impl/port_manager__generic.py @@ -2,48 +2,95 @@ from ..port_manager import PortManager from ..exceptions import PortForException +from .. import consts +import os import threading import random import typing class PortManager__Generic(PortManager): + C_MIN_PORT_NUMBER = 1024 + C_MAX_PORT_NUMBER = 65535 + _os_ops: OsOperations _guard: object # TODO: is there better to use bitmap fot _available_ports? _available_ports: typing.Set[int] _reserved_ports: typing.Set[int] + _lock_dir: str + def __init__(self, os_ops: OsOperations): + assert __class__.C_MIN_PORT_NUMBER <= __class__.C_MAX_PORT_NUMBER + assert os_ops is not None assert isinstance(os_ops, OsOperations) self._os_ops = os_ops self._guard = threading.Lock() - self._available_ports: typing.Set[int] = set(range(1024, 65535)) - self._reserved_ports: typing.Set[int] = set() + + self._available_ports = set( + range(__class__.C_MIN_PORT_NUMBER, __class__.C_MAX_PORT_NUMBER + 1) + ) + assert len(self._available_ports) == ( + (__class__.C_MAX_PORT_NUMBER - __class__.C_MIN_PORT_NUMBER) + 1 + ) + + self._reserved_ports = set() + self._lock_dir = None def reserve_port(self) -> int: assert self._guard is not None assert type(self._available_ports) == set # noqa: E721t assert type(self._reserved_ports) == set # noqa: E721 + assert isinstance(self._os_ops, OsOperations) with self._guard: + if self._lock_dir is None: + temp_dir = self._os_ops.get_tempdir() + assert type(temp_dir) == str # noqa: E721 + lock_dir = os.path.join(temp_dir, consts.TMP_TESTGRES_PORTS) + assert type(lock_dir) == str # noqa: E721 + self._os_ops.makedirs(lock_dir) + self._lock_dir = lock_dir + + assert self._lock_dir is not None + assert type(self._lock_dir) == str # noqa: E721 + t = tuple(self._available_ports) assert len(t) == len(self._available_ports) sampled_ports = random.sample(t, min(len(t), 100)) t = None for port in sampled_ports: + assert type(port) == int # noqa: E721 assert not (port in self._reserved_ports) assert port in self._available_ports + assert port >= __class__.C_MIN_PORT_NUMBER + assert port <= __class__.C_MAX_PORT_NUMBER + if not self._os_ops.is_port_free(port): continue - self._reserved_ports.add(port) - self._available_ports.discard(port) + try: + lock_path = self.helper__make_lock_path(port) + self._os_ops.makedir(lock_path) # raise + except: # noqa: 722 + continue + + assert self._os_ops.path_exists(lock_path) + + try: + self._reserved_ports.add(port) + except: # noqa: 722 + assert not (port in self._reserved_ports) + self._os_ops.rmdir(lock_path) + raise + assert port in self._reserved_ports + self._available_ports.discard(port) assert not (port in self._available_ports) return port @@ -51,10 +98,14 @@ def reserve_port(self) -> int: def release_port(self, number: int) -> None: assert type(number) == int # noqa: E721 + assert number >= __class__.C_MIN_PORT_NUMBER + assert number <= __class__.C_MAX_PORT_NUMBER assert self._guard is not None assert type(self._reserved_ports) == set # noqa: E721 + lock_path = self.helper__make_lock_path(number) + with self._guard: assert number in self._reserved_ports assert not (number in self._available_ports) @@ -62,3 +113,18 @@ def release_port(self, number: int) -> None: self._reserved_ports.discard(number) assert not (number in self._reserved_ports) assert number in self._available_ports + + assert isinstance(self._os_ops, OsOperations) + assert self._os_ops.path_exists(lock_path) + self._os_ops.rmdir(lock_path) + + return + + def helper__make_lock_path(self, port_number: int) -> str: + assert type(port_number) == int # noqa: E721 + # You have to call the reserve_port at first! + assert type(self._lock_dir) == str # noqa: E721 + + result = os.path.join(self._lock_dir, str(port_number) + ".lock") + assert type(result) == str # noqa: E721 + return result