fix(bridge): add error variants and diagnostic logging for IPC

Implements ticket #341 for Sprint 3:
- Add DeserializationWithDump and MutexPoisoned variants to BridgeError
- Replace .expect("mutex poisoned") with graceful error propagation
  in LocalBridge and TcpBridge (4 locations)
- Log first 256 bytes as hex dump on deserialization failure for
  debugging malformed payloads
- Classify errors in receive_bridge_inputs: BrokenPipe/ConnectionReset
  → clean shutdown, MutexPoisoned → shutdown, DeserializationWithDump
  → skip frame (recoverable)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 17:51:38 +01:00
co-authored by Claude Opus 4.6
parent f899624103
commit f186cec264
3 changed files with 90 additions and 16 deletions
+32 -2
View File
@@ -19,12 +19,16 @@ pub enum BridgeError {
Serialization(#[from] rmp_serde::encode::Error),
#[error("deserialization error: {0}")]
Deserialization(#[from] rmp_serde::decode::Error),
#[error("deserialization error (raw bytes logged): {0}")]
DeserializationWithDump(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("transport error: {0}")]
Transport(String),
#[error("client disconnected")]
Disconnected,
#[error("internal mutex poisoned: {0}")]
MutexPoisoned(String),
}
/// Abstracts transport layer (D-020)
@@ -133,13 +137,29 @@ pub fn receive_bridge_inputs(
tracing::info!("Client disconnected, shutting down");
running.0 = false;
}
Err(BridgeError::Io(ref e))
if e.kind() == std::io::ErrorKind::BrokenPipe
|| e.kind() == std::io::ErrorKind::ConnectionReset =>
{
tracing::info!("Pipe broken, shutting down cleanly");
running.0 = false;
}
Err(BridgeError::MutexPoisoned(ref msg)) => {
tracing::error!("Bridge mutex poisoned: {}. Shutting down.", msg);
running.0 = false;
}
Err(BridgeError::DeserializationWithDump(ref msg)) => {
// Recoverable: skip this frame's input, don't shut down
tracing::error!("Skipping malformed input frame: {}", msg);
}
Err(e) => {
tracing::error!("Bridge receive error: {}", e);
}
}
}
/// Send snapshot from buffer to bridge
/// Send snapshot from buffer to bridge.
/// Any send error is fatal — the client cannot proceed without snapshots.
pub fn send_bridge_snapshot(
bridge: Option<Res<BridgeResource>>,
mut buffer: ResMut<SnapshotBuffer>,
@@ -148,7 +168,17 @@ pub fn send_bridge_snapshot(
let Some(bridge) = bridge else { return };
if let Some(snapshot) = buffer.snapshot.take() {
if let Err(e) = bridge.send_snapshot(&snapshot) {
tracing::error!("Bridge send error: {}", e);
match &e {
BridgeError::Disconnected => {
tracing::info!("Client disconnected during send, shutting down");
}
BridgeError::MutexPoisoned(msg) => {
tracing::error!("Bridge mutex poisoned during send: {}", msg);
}
_ => {
tracing::error!("Bridge send error: {}", e);
}
}
running.0 = false;
}
}