plesty.server.services.supervisor

Start, stop, probe and log the device servers of one bench.

A device server is <env python> -m plesty.<module> <args> — the entry point every hub device ships. The supervisor launches it detached through plesty.server.services.launch (so no plesty-server process ever owns it as a child) with its stdout and stderr appended to <home>/logs/<name>.log, remembers pid and port in <home>/state/<name>.json so a later plesty-server invocation, the agent or the GUI finds it, and judges liveness the way experiments do: a describe request over ZMQ answered with status: ok.

Stopping is graceful first — the interrupt lets plesty-lib close the device session ([SERVER] Shutting down...) — and escalates to a kill only after stop_timeout seconds. Every platform difference on the way (how to interrupt, how to kill, how to tell a pid is alive) is a call into plesty.server.host.

Attributes

PROBE_HOST

_START_LOCKS

_LOCKS_GUARD

Exceptions

SupervisorError

A start or stop request could not be carried out.

AlreadyRunning

start was asked for a device whose server is already up.

NotRunning

stop was asked for a device that has no live server.

PortBusy

start found the device's port already answering — another server holds it.

Classes

Supervisor

Launch and control device servers under a Home.

Functions

_start_lock(→ threading.Lock)

probe(→ bool)

Ask the server on port to describe itself.

request(→ dict[str, Any])

Send one request to the server on port and return its reply.

_now(→ str)

Module Contents

plesty.server.services.supervisor.PROBE_HOST = '127.0.0.1'
exception plesty.server.services.supervisor.SupervisorError

Bases: RuntimeError

A start or stop request could not be carried out.

Initialize self. See help(type(self)) for accurate signature.

exception plesty.server.services.supervisor.AlreadyRunning

Bases: SupervisorError

start was asked for a device whose server is already up.

Initialize self. See help(type(self)) for accurate signature.

exception plesty.server.services.supervisor.NotRunning

Bases: SupervisorError

stop was asked for a device that has no live server.

Initialize self. See help(type(self)) for accurate signature.

exception plesty.server.services.supervisor.PortBusy

Bases: SupervisorError

start found the device’s port already answering — another server holds it.

Initialize self. See help(type(self)) for accurate signature.

plesty.server.services.supervisor._START_LOCKS: dict[str, threading.Lock]
plesty.server.services.supervisor._LOCKS_GUARD
plesty.server.services.supervisor._start_lock(name: str) → threading.Lock
Parameters:

name (str)

Return type:

threading.Lock

plesty.server.services.supervisor.probe(port: int, timeout_ms: int = 2000, host_addr: str = PROBE_HOST) → bool

Ask the server on port to describe itself.

Returns:

True when a plesty-lib server answered status: ok in time.

Parameters:
  • port (int)

  • timeout_ms (int)

  • host_addr (str)

Return type:

bool

plesty.server.services.supervisor.request(port: int, message: dict[str, Any], timeout_ms: int = 5000, host_addr: str = PROBE_HOST) → dict[str, Any]

Send one request to the server on port and return its reply.

The same DEALER socket probe() uses, with the message left to the caller: this is how a developer asks a running device server anything its plesty-lib command solver understands, without a client library.

Raises:

SupervisorError – When nobody answers in time, or the answer is not JSON the wire format allows.

Parameters:
  • port (int)

  • message (dict[str, Any])

  • timeout_ms (int)

  • host_addr (str)

Return type:

dict[str, Any]

class plesty.server.services.supervisor.Supervisor(home: plesty.server.model.home.Home, ready_timeout: float | None = None, stop_timeout: float | None = None, probe_timeout_ms: int | None = None)

Launch and control device servers under a Home.

Bind the supervisor to a home.

Parameters:
  • home (plesty.server.model.home.Home) – The bench home.

  • ready_timeout (float | None) – Seconds start() waits for the first probe answer.

  • stop_timeout (float | None) – Seconds stop() waits before escalating.

  • probe_timeout_ms (int | None) – Milliseconds one probe waits for its reply.

  • running.) ((Defaults come from config.yaml)

home
ready_timeout
stop_timeout
probe_timeout_ms
record(name: str) → plesty.server.model.process.ProcessRecord | None

The persisted launch record of name, or None.

Parameters:

name (str)

Return type:

plesty.server.model.process.ProcessRecord | None

exit_file(name: str) → pathlib.Path

Where the launcher records the exit code of the server of name.

Parameters:

name (str)

Return type:

pathlib.Path

known() → list[str]

Names that have a launch record (alive or not).

Return type:

list[str]

_drop_record(name: str) → None
Parameters:

name (str)

Return type:

None

alive(name: str) → bool

Whether the recorded server of name still runs.

Parameters:

name (str)

Return type:

bool

poll(name: str) → int | None

Exit code of the server of name, None while it runs.

LOST_EXIT when the process is gone without a recorded exit (the bench rebooted, or someone killed the launcher) — and when nothing is known at all.

Parameters:

name (str)

Return type:

int | None

status(name: str, probe_port: bool = True) → plesty.server.model.process.ProcessStatus

Observe name: record, process existence and (optionally) a probe.

Parameters:
  • name (str) – Device instance name.

  • probe_port (bool) – Whether to send a describe to tell running from starting; skipping it is cheaper for bulk listings.

Return type:

plesty.server.model.process.ProcessStatus

wait_ready(name: str, timeout: float | None = None) → bool

Poll until the server of name answers a probe.

Returns:

True once it answers; False on timeout or when the process exits first.

Parameters:
  • name (str)

  • timeout (float | None)

Return type:

bool

start(spec: plesty.server.model.device.DeviceSpec, python: pathlib.Path, wait: bool = True, timeout: float | None = None, package: str = '', version: str = '') → plesty.server.model.process.ProcessStatus

Launch the server of spec with the interpreter python.

Parameters:
  • spec (plesty.server.model.device.DeviceSpec) – Module, arguments, environment and port to run with.

  • python (pathlib.Path) – Interpreter of the environment the module is installed in.

  • wait (bool) – Block until the server answers a probe (or fails).

  • timeout (float | None) – Readiness wait in seconds; defaults to ready_timeout.

  • package (str) – Distribution name, stored on the record for listings.

  • version (str) – Installed version, likewise.

Returns:

The status after launch — running when it came up, starting when wait is off or it has not answered yet, exited when it died before answering (detail carries the log tail).

Raises:
Return type:

plesty.server.model.process.ProcessStatus

_start_locked(spec: plesty.server.model.device.DeviceSpec, python: pathlib.Path, wait: bool, timeout: float | None, package: str, version: str) → plesty.server.model.process.ProcessStatus
Parameters:
Return type:

plesty.server.model.process.ProcessStatus

stop(name: str, timeout: float | None = None, note: collections.abc.Callable[[str], None] | None = None) → bool

Interrupt the server of name, escalating to a kill if it lingers.

Interrupt → wait timeout → terminate → wait 3 s → kill tree.

note, when given, receives one line about how the server ended, in the cases where that is worth saying — today, a server the stop killed before its shutdown ran, whose device is therefore still held. It has to be read here: _drop_record() removes the exit file and the record, and status() returns early once the record is gone, so after a stop there is nothing left for anyone to observe. That made the note unreachable on exactly the path an operator takes — press Stop, then ask why the device never disconnected.

Parameters:
  • name (str) – Device instance name.

  • timeout (float | None) – Seconds to wait before escalating.

  • note (collections.abc.Callable[[str], None] | None) – Receives a line about an ending worth reporting.

Returns:

True when a process was stopped, False when only a stale record was cleared.

Raises:

NotRunning – When nothing (not even a stale record) is known.

Return type:

bool

restart(spec: plesty.server.model.device.DeviceSpec, python: pathlib.Path, wait: bool = True, timeout: float | None = None) → plesty.server.model.process.ProcessStatus

Stop the server of spec if it runs, then start() it again.

Parameters:
Return type:

plesty.server.model.process.ProcessStatus

relaunch(record: plesty.server.model.process.ProcessRecord, wait: bool = True) → plesty.server.model.process.ProcessStatus

Start the server again exactly as record says it was started.

Used after a host-tier field test: the interpreter, arguments and environment of the interrupted launch are what the bench expects back.

Parameters:
Return type:

plesty.server.model.process.ProcessStatus

stop_all(names: list[str] | None = None) → list[str]

Stop every known (or the given) server; returns the names stopped.

Parameters:

names (list[str] | None)

Return type:

list[str]

tail(name: str, lines: int = 50) → str

The last lines lines of the log of name ("" when no log).

Parameters:
  • name (str)

  • lines (int)

Return type:

str

clear_log(name: str) → int

Start a new, empty log for name; returns the size of the one set aside.

Nothing is truncated or deleted. Truncating a file a running server writes left it writing at its old offset: the gap filled with NULs, and an 11.7 MB log with no line breaks hung the console, the agent and every reader (#23). The old file stays beside the new one.

A running server’s waiter is asked to write to the new file (request_switch()). One started by an older bench hands the server the file itself and cannot switch, so it is refused with what to do instead.

Parameters:

name (str) – The device whose log to clear.

Returns:

The size of the log set aside; 0 when there was none.

Raises:

SupervisorError – When the server is running and cannot switch, or did not take the new file.

Return type:

int

_wait_exit(name: str, timeout: float) → bool
Parameters:
  • name (str)

  • timeout (float)

Return type:

bool

plesty.server.services.supervisor._now() → str
Return type:

str