plesty.server.model.ports ========================= .. py:module:: plesty.server.model.ports .. autoapi-nested-parse:: TCP port rules for device servers on one bench. Precedence for the probed port: an explicit ``port`` key, then ``--tcp-port``/``-tp`` in ``args``, then a ``*_TCP_PORT`` value in ``env``, then plesty-lib's default 5555. Attributes ---------- .. autoapisummary:: plesty.server.model.ports.DEFAULT_TCP_PORT plesty.server.model.ports.AGENT_PORT plesty.server.model.ports.DEVICE_PORT_BASE plesty.server.model.ports._PORT_FLAGS Functions --------- .. autoapisummary:: plesty.server.model.ports.port_from plesty.server.model.ports.declares_port plesty.server.model.ports.port_is_free plesty.server.model.ports.port_listening plesty.server.model.ports.allocate_port plesty.server.model.ports._int_port Module Contents --------------- .. py:data:: DEFAULT_TCP_PORT :value: 5555 .. py:data:: AGENT_PORT :value: 5550 .. py:data:: DEVICE_PORT_BASE :value: 5551 .. py:data:: _PORT_FLAGS :value: ('--tcp-port', '-tp') .. py:function:: port_from(args: tuple[str, Ellipsis], env: collections.abc.Mapping[str, str]) -> int Resolve the TCP port a server binds from its args and environment. :param args: Command-line arguments passed to ``python -m ``. :param env: Environment values the fleet sets for the process. :returns: The port to probe, falling back to :data:`DEFAULT_TCP_PORT`. .. py:function:: declares_port(args: tuple[str, Ellipsis], env: collections.abc.Mapping[str, str]) -> bool Whether *args* or *env* name the port themselves. A declaration that names its own port is left alone — the bench allocates only for devices that would otherwise take the default and collide. .. py:function:: port_is_free(port: int, host: str = '127.0.0.1') -> bool Whether *port* can actually be bound right now. The fleet file and the process records say what this bench *believes* it is using. A server that outlived its record, or any other program on the machine, believes nothing — and the socket is the only thing that knows. The socket option differs because the platforms disagree about what ``SO_REUSEADDR`` means. On POSIX it says "a port left in ``TIME_WAIT`` is still free", which is the question. On **Windows** it says "bind even if another socket is listening here" — so the probe succeeded against a live server and every busy port reported free, which silently disabled everything built on this: the port a start moves away from, and the port :func:`allocate_port` hands out. ``SO_EXCLUSIVEADDRUSE`` is the Windows way to ask the question POSIX answers with ``SO_REUSEADDR``. .. py:function:: port_listening(port: int, host: str = '127.0.0.1', timeout: float = 0.3) -> bool Whether something accepts TCP connections on *port* right now. The complement of :func:`port_is_free` for a port a *server* holds: a device server binds ``tcp://*``, and on some hosts a specific-address bind still succeeds beside a wildcard listener — connecting is the one test that answers the question everywhere. .. py:function:: allocate_port(taken: collections.abc.Iterable[int], base: int = DEVICE_PORT_BASE, is_free: collections.abc.Callable[[int], bool] = port_is_free) -> int The lowest free port from *base* up, skipping *taken* and the agent's. :param taken: Ports already spoken for on this bench. :param base: Where counting starts. :param is_free: Asks the host whether a port can be bound; injected by tests. :returns: A port no other device on the bench holds *and* nothing on the host is listening on. :raises FleetError: When counting runs past the end of the port range. .. py:function:: _int_port(value: str, where: str) -> int