fix(producer): surface write failures via a SendFailed event - #413
Conversation
…408) MessageProducer.Send() is fire-and-forget: a failed background write was only logged at Warning, so a command that never reached the device was indistinguishable from one that was delivered. Add a SendFailed event on IMessageProducer<T>/MessageProducer<T> (and a forwarding SendFailed event on DaqifiDevice) carrying the exception and an IsTimeout flag, and give write timeouts their own log message text so they're greppable separately from other write failures. Purely observational — the producer still drains the rest of the queue exactly as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoSurface producer write failures via SendFailed events
AI Description
Diagram
High-Level Assessment
Files changed (7)
|
Code Review by Qodo
1.
|
…rence Disconnect() nulled _messageProducer without detaching OnMessageSendFailed first, so a producer thread still winding down (blocked in a synchronous Stream.Write/Flush) kept the device alive via the delegate and could keep invoking it post-teardown. Mirrors the existing MessageReceived unsubscribe pattern for the consumer. Found by Qodo's agentic review on PR #413. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 4c6e56c |
Summary
Closes #408.
MessageProducer<T>.Send()is fire-and-forget: the message goes onto a queue drained by a background thread, and ifstream.Writefails, the failure was only logged at Warning. Nothing propagated to the caller, so a command that never reached the device looked identical, from the caller's point of view, to one that was delivered.MessageSendFailedEventArgs<T>and aSendFailedevent onIMessageProducer<T>/MessageProducer<T>, raised (from the background thread, wrapped so a throwing subscriber can't kill the loop) for every failed write. Carries the failing message, the exception, and anIsTimeoutflag.SendFailedevent onDaqifiDevice(mirroring the existingChannelsPopulated-style concrete-class event), plus a log-warning subscriber wired at every placeDaqifiDeviceconstructs a producer — so real device usage gets visibility today without needing feat: device-level ErrorOccurred event — background read errors and per-frame decode failures are currently invisible #378's broader device-level error surface.ITransportHealthSinkfault.Send()/IMessageProducer<T>.Sendthat delivery is not guaranteed, and added a "Delivery Failures" section todocs/DEVICE_INTERFACES.md.This is intentionally scoped to option 2/3 from the issue (something to observe, plus greppable logs) rather than #378's full device-level
ErrorOccurredsurface, since #378 is still open —DaqifiDevice.SendFailedgives real callers a signal today and can be superseded/wired into that broader surface later.Test plan
SendFailedraised on write failure (non-timeout and timeout), timeout gets distinct log text, a throwingSendFailedsubscriber doesn't kill the background loop, no event on a successful write, andDaqifiDevice.SendFailed/log-warning wiring.Daqifi.Core.Testssuite: 2191 passed, 2 skipped (pre-existing), 0 failed./dev/cu.usbmodem1101): connect → stream 3s @ 10 Hz on channels 0+1 → stop → disconnect, exit code 0, no regression.🤖 Generated with Claude Code