Skip to content

The app's USB-unplug watcher duplicates Core 1.8.0's serial liveness detector; both race to the same teardown #441

Open

Description

@tylerkron

Summary

The app runs two USB-unplug detectors for every serial device, and both end in the same teardown. The second is the app's own IDeviceWatcher stack: Services/DeviceWatcher/* (5 files, 391 lines) plus ConnectionManager.CheckIfSerialDeviceWasRemoved. It re-implements a detector Core 1.8.0 already runs on the same port.

  • Core: SerialStreamTransport polls port presence every 1 s (DefaultLivenessCheckInterval, in PublicAPI.Shipped.txt at v1.8.0). It needs two consecutive misses, then raises ConnectionStatus.Lost. That runs AbstractStreamingDevice.OnCoreStatusChanged → ConnectionLost → ConnectionManager.OnDeviceConnectionLost → TearDownDroppedDevice. Core measured 1.6 s on a real cable pull (daqifi-core#382/refactor(abstraction-police): the connection dialog asks Core for serial port names instead of importing System.IO.Ports (+2/−2 prod) #403). The app's SerialStreamingDevice builds new SerialStreamTransport(Port.PortName, enableDtr: true), which uses the default interval, so the detector is on.
  • App: SerialPortPollingDeviceWatcher polls SerialPort.GetPortNames() every 1 s on macOS/Linux. WmiDeviceWatcher does the same job on Windows. Either one raises DeviceRemoved → CheckIfSerialDeviceWasRemoved → the same TearDownDroppedDevice.

The code already knows. ConnectionManager.cs, inside CheckIfSerialDeviceWasRemoved, says Core's own port-presence poll "reports the same unplug as ConnectionLost ... so this and OnDeviceConnectionLost race for one removal." Both paths skip a device under firmware update (IsDeviceBeingUpdated), and whichever arrives second finds the device already gone.

Why it was kept, and why that reason does not hold

PR #172 kept the watcher: "This port's macOS/Linux USB hotplug detection lives there and is strictly better than upstream's pre-deletion state." That compares it with upstream desktop, not with Core. The watcher was the only macOS detector when #150 added it. It stopped being the only one when Core added transport liveness (#382). PR #404's title repeats the stale premise ("the only USB-unplug detector on macOS/Linux").

What would actually change for a user

  • Latency: Core reports in about 1.6-4 s. The app's poller takes about 1-2 s, and WMI on Windows is event-driven. This is the one real difference; name it in the PR.
  • Notification wording: "X was unplugged." becomes "X disconnected (reason)."
  • A false notification disappears: DaqifiViewModel.ReportHotplugDetectionUnavailable tells the user that "unplugging a connected device will not be noticed" whenever the watcher fails to start. That is untrue while Core's detector runs.

Retirement plan

Delete:

  • Services/DeviceWatcher/ (all 5 files)
  • ConnectionManager.CheckIfSerialDeviceWasRemoved, the watcher field and its construction block
  • HotplugDetectionUnavailableMessage / HotplugUnavailableUserMessage
  • DaqifiViewModel.ReportHotplugDetectionUnavailable
  • Daqifi.Avalonia.Tests/Services/SerialPortPollingDeviceWatcherTests.cs
  • the System.Management PackageReference in Daqifi.Avalonia.csproj (WmiDeviceWatcher is its only code user; the other hit is a comment)

Update the internal test constructor used by ConnectionManagerTeardownTests and ConnectionManagerConnectResultTests (it takes a NoOpDeviceWatcher).

Guarding tests: ConnectionManagerTeardownTests already drives OnDeviceConnectionLost → TearDownDroppedDevice, including the firmware-update exemption, and that is the path every unplug will take. Add one test that a Lost status on a serial device removes it with no watcher present.

Verification this needs, and why it is not unattended-safe: nobody has watched a physical unplug reach ConnectionStatus.Lost through this app (#172's bench run covered the false-positive direction only). Before merging, do one attended cable pull on macOS, and ideally one on Windows, with the watcher deleted. Read DAQifiAppLog.log for the teardown line.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions