3.10.1 The shared socket transport¶
The three protocol servers — DZRP, GDB RSP and ZRCP — share one transport, in
src/remote/transport.* (target jnext_remote). It has no toolkit
dependency and is built in every configuration. It carries no protocol: it never
parses a byte. Each server is its own listener with its own backend client, so
all three may run at once. The design, and the reason behind each rule below, is
doc/design/debug-subsystem/transport.md.
The three types¶
| Type | Role |
|---|---|
remote::Server |
the listener and its one client connection. It is a jnext::dbg::Service, so the loop owner's pump() drives it |
remote::Protocol |
what a server implements: on_connect, on_service, on_notify, on_disconnect |
remote::Connection |
the adapter's view of its client: available(), read(), write(), close(), peer_gone() |
A server is built from a ServerConfig and its Protocol:
ServerConfig |
Meaning | Default |
|---|---|---|
name |
the log prefix | — |
busy_reply |
what a second client is sent before it is closed | — |
max_input |
unconsumed input at which reading stops, backpressuring the peer through the kernel | 1 MiB |
max_output |
queued output at which a client that stopped reading its replies is disconnected | 32 MiB |
linger_ms |
how long a closing connection keeps delivering what was queued | 2000 |
One pass¶
Server::service_once(wait_ms) accepts, reads, asks the Protocol to execute
at most one complete command (on_service), and writes — repeated until a
command ran or wait_ms passed. wait_ms 0 is exactly one pass. That is the
contract pump()'s drain relies on (3.9.3).
flush_notifications() calls the adapter's on_notify() after the drain, which
is where a server turns a recorded Paused push into a packet.
The rules a Connection keeps:
write()never blocks and never drops. It queues; the server sends once the adapter's callback returns, and what the kernel does not take goes out on later passes.close()still delivers what is queued, for at mostlinger_ms.on_disconnect()is called exactly once peron_connect(), whoever ended the session, and every byte the client sent before hanging up is offered to the adapter first.
One client per listener. A second client is sent the adapter's busy_reply
and closed. A client that hangs up and dials again is not a second one: a pass
serves the current client first, so a hang-up is seen before a redial is
judged; it takes one more look at the client after the listener has parked an
arrival, because the client may have read its reply, hung up and redialled in
between; and it leaves the redial parked until the old session is retired
(remote_transport_test rows XPT-SRV-30..33).
Sockets¶
Sockets come only from esp01's public seam — esp::make_socket_listener,
EspListener, EspTransport (src/esp01/include/esp01/esp_socket.h) — which
is already non-blocking and has a Windows twin. src/remote/ makes no socket
call of its own. The seam has no readiness wait, so the one wait, the headless
loop's paused pump(), is a bounded 1 ms sleep between non-blocking passes.
The server logs to the debugger channel. Among its lines is
<name>: listening on <addr>:<port>, with the port actually bound, which is
what a --<proto>-port 0 regression row reads to find the server.
--debug-listen-address ADDR (numeric only, default 127.0.0.1) is validated
in main.cpp and held in EmulatorConfig::debug_listen_address. It is refused
unless a server port (--dzrp-port, --gdb-port or --zrcp-port) is given
too (debug-listen-address-func).
How a loop owner opens the servers¶
DebugServers::start(Debugger&, EmulatorConfig) (src/platform/debug_servers.*)
builds each server the configuration asks for, opens it on the listen address,
and registers it with Debugger::add_service(). A server that cannot bind its
port is a startup error in every frontend: the loop owner logs it and exits.
DebugServers also holds the pump budgets of
3.9.3:
frame_loop_budget(), headless_should_wait() and headless_wait_budget().
The fake¶
FakeListener, FakeTransport and FakePeer (src/remote/fake_transport.*)
implement the same esp interfaces in memory. An adapter's unit suite runs the
production Server with only the kernel replaced. A peer's window and receive
chunk model partial writes and reads, so a command split across two reads, or a
reply the peer takes a byte at a time, is a test case rather than a hope.
remote_transport_test pins the transport three ways: over the fake, over a
real socket on 127.0.0.1 port 0, and through Debugger::pump() (XPT-PUMP).