mirror of
https://github.com/progval/irctest.git
synced 2025-04-07 15:59:49 +00:00
Prevent random port collisions between controllers (#191)
This happens from time to time on the CI and is pretty annoying
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import multiprocessing
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
@ -9,7 +10,18 @@ import subprocess
|
||||
import tempfile
|
||||
import textwrap
|
||||
import time
|
||||
from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, Type
|
||||
from typing import (
|
||||
IO,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
)
|
||||
|
||||
import irctest
|
||||
|
||||
@ -58,9 +70,43 @@ class _BaseController:
|
||||
supported_sasl_mechanisms: Set[str]
|
||||
proc: Optional[subprocess.Popen]
|
||||
|
||||
_used_ports: Set[Tuple[str, int]]
|
||||
"""``(hostname, port))`` used by this controller."""
|
||||
# the following need to be shared between processes in case we are running in
|
||||
# parallel (with pytest-xdist)
|
||||
# The dicts are used as a set of (hostname, port), because _manager.set() doesn't
|
||||
# exist.
|
||||
_manager = multiprocessing.Manager()
|
||||
_port_lock = _manager.Lock()
|
||||
"""Lock for access to ``_all_used_ports`` and ``_available_ports``."""
|
||||
_all_used_ports: MutableMapping[Tuple[str, int], None] = _manager.dict()
|
||||
"""``(hostname, port)`` used by all controllers."""
|
||||
_available_ports: MutableMapping[Tuple[str, int], None] = _manager.dict()
|
||||
"""``(hostname, port)`` available to any controller."""
|
||||
|
||||
def __init__(self, test_config: TestCaseControllerConfig):
|
||||
self.test_config = test_config
|
||||
self.proc = None
|
||||
self._used_ports = set()
|
||||
|
||||
def get_hostname_and_port(self) -> Tuple[str, int]:
|
||||
with self._port_lock:
|
||||
try:
|
||||
# try to get a known available port
|
||||
((hostname, port), _) = self._available_ports.popitem()
|
||||
except KeyError:
|
||||
# if there aren't any, iterate while we get a fresh one.
|
||||
while True:
|
||||
(hostname, port) = find_hostname_and_port()
|
||||
if (hostname, port) not in self._all_used_ports:
|
||||
# double-checking in self._used_ports to prevent collisions
|
||||
# between controllers starting at the same time.
|
||||
break
|
||||
|
||||
# Make this port unavailable to other processes
|
||||
self._all_used_ports[(hostname, port)] = None
|
||||
|
||||
return (hostname, port)
|
||||
|
||||
def check_is_alive(self) -> None:
|
||||
assert self.proc
|
||||
@ -84,6 +130,11 @@ class _BaseController:
|
||||
if self.proc:
|
||||
self.kill_proc()
|
||||
|
||||
# move this controller's ports from _all_used_ports to _available_ports
|
||||
for hostname, port in self._used_ports:
|
||||
del self._all_used_ports[(hostname, port)]
|
||||
self._available_ports[(hostname, port)] = None
|
||||
|
||||
|
||||
class DirectoryBasedController(_BaseController):
|
||||
"""Helper for controllers whose software configuration is based on an
|
||||
@ -202,9 +253,6 @@ class BaseServerController(_BaseController):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.faketime_enabled = False
|
||||
|
||||
def get_hostname_and_port(self) -> Tuple[str, int]:
|
||||
return find_hostname_and_port()
|
||||
|
||||
def run(
|
||||
self,
|
||||
hostname: str,
|
||||
|
Reference in New Issue
Block a user