Ports
Every device server on a bench binds one TCP port, and the bench is the only thing that sees all of them at once. Left alone, every device would take plesty-lib’s default 5555 and the second server to start would fail — so allocating ports, remembering them, and moving them when they are taken is the bench’s job.
The rules live in plesty/server/model/ports.py.
The reserved numbers
|
|
|
|
|
|
Where a port comes from
port_from(args, env) resolves what a server would bind, in this order:
--tcp-portor-tpin the declaration’sargs(as--tcp-port 5560or--tcp-port=5560)any
*_TCP_PORTkey in the declaration’senv, taking the keys in sorted orderDEFAULT_TCP_PORT— 5555
A declaration that reaches step 3 is one the bench must allocate for, because 5555 is what every such declaration would take.
Named versus allocated
This is the distinction the rest of the behaviour turns on.
A named port is one the declaration itself carries, in args or env.
declares_port(args, env) is true for it. The bench does not choose it and
cannot change it: that number is passed to the device process, so a server
started anywhere else would contradict its own arguments.
An allocated port is one the bench chose, because the declaration named
none. declare calls allocate_port(taken) — the lowest free number from
5551 up, skipping the agent’s port, anything another device on this bench
holds, and anything the host is already listening on — then writes it into
fleet.yaml. From that moment DeviceSpec.port_is_explicit is true: the
port is written down, so the port a client connected to yesterday is the port
it finds today.
When the port is taken
Two benches on one machine allocate from the same range, and a server left running from an earlier start holds whatever it holds. A busy port serves no client, whoever is at fault, so the two cases diverge:
Named port |
Allocated port |
|
|---|---|---|
On |
Refused with |
Re-allocated to a free port, recorded, started there |
Why |
The number belongs to the device’s own arguments |
The number was only ever the bench’s choice |
The move is written back to fleet.yaml and reported, so nothing has to be
guessed afterwards. See Command line for what declare and start
print, and Process management for where PortBusy
is raised.
Asking the host, not the records
fleet.yaml and the process records say what this bench believes it is
using. A server that outlived its record believes nothing, and neither does
any other program on the machine — so allocation asks the socket, not the
bookkeeping.
Two functions ask, and they are not interchangeable:
port_is_free(port)binds it. Used when choosing a port to hand out.port_listening(port)connects to it. Used when deciding whether something is already serving there.
The distinction is a platform one. 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. The bind
probe also sets a different socket option per platform, 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 being asked; on Windows it
says “bind even if another socket is listening here”, which is not. Windows
uses SO_EXCLUSIVEADDRUSE instead.