net_consomme: decouple control requests from packet processing path - #4378
net_consomme: decouple control requests from packet processing path#4378Daman Mulye (damanm24) wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces cross-cutting networking/control-path and concurrency changes (new endpoint action flow + shared listener registries) that warrant final human validation beyond automated review.
Pull request overview
This PR changes net_consomme so control requests (e.g., port bind/unbind, state updates) make progress independently of the packet processing path, avoiding stalls when the guest is idle. It introduces a listener-only control capability backed by shared listener tables, while deferring stateful requests until a queue restart can safely apply them.
Changes:
- Move control-request draining to the endpoint control path (
wait_for_endpoint_action) and buffer restart-requiring requests until the next queue start. - Add a queue-independent
ListenerControlbacked by locked TCP/UDP listener registries so frequent bind/unbind operations don’t require restarting queues. - Add an end-to-end ttrpc test that validates Consommé host port forwarding bind/unbind behavior.
File summaries
| File | Description |
|---|---|
| vmm_tests/vmm_tests/tests/tests/ttrpc.rs | Adds an end-to-end ttrpc-driven port-forwarding test (bind, connect/verify banner, unbind/verify refusal). |
| vm/devices/net/net_consomme/src/resolver.rs | Wires the endpoint to accept a generalized request receiver (renamed from port-only channel). |
| vm/devices/net/net_consomme/src/lib.rs | Implements endpoint-side request draining + buffering, introduces listener-control handling, and adds endpoint actions for restart triggering. |
| vm/devices/net/net_consomme/consomme/src/udp.rs | Makes UDP listeners a shared, lock-protected registry and adds a bind/unbind control surface usable outside the packet loop. |
| vm/devices/net/net_consomme/consomme/src/tcp/tests.rs | Updates TCP tests for the new lock-wrapped listener table. |
| vm/devices/net/net_consomme/consomme/src/tcp.rs | Makes TCP listeners a shared, lock-protected registry and adds a bind/unbind control surface usable outside the packet loop. |
| vm/devices/net/net_consomme/consomme/src/lib.rs | Introduces a public ListenerControl abstraction to manage TCP/UDP listeners without queue-owned state. |
| vm/devices/net/net_backend_resources/src/lib.rs | Expands ConsommeRequest to include DNS-record and virtual-address operations and adds a serializable DNS record config. |
Review details
Suppressed comments (1)
vm/devices/net/net_consomme/src/lib.rs:276
ConsommeMessageErrorstill definesBindandDnsRecordvariants, but the updatedConsommeControlmethods now surface bind/unbind/DNS failures viaRemote(mesh::error::RemoteError)instead (no in-file construction of those variants, and no uses across the repo). Keeping unused public variants here is misleading for callers and makes it harder to reason about which errors can actually be returned.
/// Error type returned from some dynamic update functions like bind_port.
#[derive(Debug, Error)]
pub enum ConsommeMessageError {
/// Communication error with running instance.
#[error("communication error")]
- Files reviewed: 8/8 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved TCP refresh, unnecessary queue restart, and process-cleanup issues remain.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
vmm_tests/vmm_tests/tests/tests/ttrpc.rs:1229
- This test owns a raw
PolledChild, butstd::process::Childdoes not kill the process when dropped. Any?/panic before the explicitQuitandwaitbelow can therefore leave an OpenVMM VM and ttrpc socket running, interfering with later tests. Use theOpenvmmChildguard defined in this file, aslaunch_openvmmdoes.
vm/devices/net/net_consomme/src/lib.rs:543
drain_channelsreturnsfalsewhen a bind or unbind is handled throughlistener_control, but this result is discarded and every completed request is reported asRestartRequired. That still stops and restarts the queues for each listener operation, defeating the decoupling and throughput goal; only queue-owned requests should complete this future withRestartRequired.
EndpointAction::RestartRequired
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Critical listener-lock starvation and additional API, cleanup, and regression-test issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (4)
Previously missed (1) — in code that hasn't changed since the last review.
vmm_tests/vmm_tests/tests/tests/ttrpc.rs:1229
- This test constructs a raw
PolledChild, butstd::process::Childis not killed when dropped. Any failure or panic after this line, including theunwraps before the explicit teardown, can leave OpenVMM running and holding the ttrpc socket for later tests. Use the existingOpenvmmChildguard used bylaunch_openvmmso all error paths kill and reap the process.
vm/devices/net/net_consomme/src/lib.rs:310
- Adding
Synchere unnecessarily tightens this public callback contract.StateUpdateRequestis transported through ameshchannel and the channel is explicitlySynceven for non-Syncpayloads such asCell<T>(seesupport/mesh/mesh_channel_core/src/mpsc.rs:73-75,927-932), so existing callers with aSend-but-not-Synccaptured value now fail to compile even though the callback is moved to and invoked by one consumer. Keep the previousSendbound unless shared concurrent invocation is required.
pub type ConsommeParamsUpdateFn = Box<dyn Fn(&mut ConsommeParams) + Send + Sync>;
vm/devices/net/net_consomme/src/lib.rs:306
- This changes the public
ConsommeControlerror contract:bind_port,unbind_port, andadd_dns_recordnow route failures throughRemoteError, so the existing typedBindandDnsRecordvariants are no longer returned (the only constructors are now in the enum definition).ConsommeControlis the in-process API created bynew_dynamic, so callers matching those variants will silently stop handling bind/DNS failures; preserve typed errors for that path or make this breaking change explicit and remove/update the obsolete variants.
/// Error from a remote operation on the endpoint.
#[error(transparent)]
Remote(mesh::error::RemoteError),
vmm_tests/vmm_tests/tests/tests/ttrpc.rs:1354
- The bind request is sent while the guest is actively running
udhcpcand thenclistener, so packet-driven queue polls are occurring. The old implementation could therefore still drain this request when packet processing happened, allowing this test to pass without exercising the new endpoint control path during an idle NIC. Add an idle-guest phase (or explicitly wait for packet activity to stop) and issue the control request with a bounded timeout to cover the regression.
vmservice::Vm::ModifyResource,
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
| let mut listeners = self.inner.udp.listeners.lock(); | ||
| for listener in listeners.values_mut() { | ||
| listener.poll_listener( | ||
| cx, | ||
| &mut self.inner.state, | ||
| self.client, | ||
| &self.inner.udp.connections, | ||
| ); | ||
| } |
Currently, consommé control messages are only dequeued/handled when the backend is being polled for network packets. If a guest stops driving the NIC for packets, control requests are never dequeued/handled by consommé.
This PR moves control requests polling to the endpoint control path, so requests continue to make progress independent of packet flows.
Control requests fall into two categories based on whether they require access to queue-owned consommé state.
Requests such as adding static DNS records, creating virtual addresses, and updating network parameters modify broader protocol state. These requests are buffered by the endpoint and applied when it regains ownership of the consommé state, which requires restarting the network queues.
Port bind and unbind requests only modify the TCP or UDP listener tables. They also occur much more frequently than the other control operations. Restarting the queues for every bind or unbind would repeatedly interrupt packet processing and impose a significant throughput penalty, particularly for workloads that frequently create and remove port forwards.
To avoid that cost, the listener tables use a locking mechanism and expose a separate listener-control capability. Bind and unbind requests can therefore be applied directly while the queues continue running. Requests that require broader mutable state still use the deferred queue-restart path.