Skip to content

fix(consumers): connect can fail spuriously with "a previous consumer thread has not yet exited" #383

Description

@tylerkron

Type: Bug
Priority: Low (intermittent; observed once)

Description

A TCP connect+initialize failed with:

InvalidOperationException: Cannot start the consumer: a previous consumer thread has not yet exited.

That guard is StreamMessageConsumer.Start() (src/Daqifi.Core/Communication/Consumers/StreamMessageConsumer.cs:88-94), and the guard itself is correct — it refuses to spawn a second reader against the same stream after a prior Stop()/StopSafely() whose Join timed out, which would reintroduce framing corruption. The problem is the condition arising at all during a normal connect: it surfaces as a confusing internal error on an operation the caller has no way to retry meaningfully.

The likely path is the text-exchange consumer swap during InitializeAsync — DaqifiDevice stops the protobuf consumer, runs a text exchange, and restarts it. If the reader thread is still blocked in Stream.Read when the restart happens, Join times out and Start() throws.

Reproduction

Intermittent — 1 failure in 3 attempts, then 5 consecutive clean runs afterwards, so it is timing-dependent rather than a deterministic state issue.

Observed on a Nyquist 1 (fw 3.7.2, macOS) while connecting over WiFi to a unit that also had an active USB session, via DaqifiDeviceFactory.ConnectFromDeviceInfoAsync. Controls run immediately afterwards did not reproduce it:

Path Result
WiFi connect via factory, nothing else open 3/3 clean
USB then WiFi via factory, both open, no registry 3/3 clean
Same via DaqifiDeviceRegistry (#381) 1 failure, then 2/2 clean

The registry is not implicated — it only calls the factory, and the failure surfaced through it by chance. It handled the failure correctly (the existing USB session was left intact).

Why it is worth fixing

A spurious InvalidOperationException from a connect is indistinguishable, to a consumer, from a permanent failure. Desktop and the MCP server both surface connect errors directly to a user or agent, and "a previous consumer thread has not yet exited" gives them nothing actionable.

Suggested investigation

  • Confirm whether the swap in DaqifiDevice's text-exchange path is the source, and what Join timeout it uses.
  • Consider whether a timed-out Join should escalate (abandon the consumer instance and construct a fresh one against the new stream) rather than leaving the caller with a hard failure — a new instance has no shared reader state, so the double-reader hazard the guard protects against would not apply.
  • If the timeout is simply too tight for TCP teardown, widening it may be enough, but the escalation path is the more robust fix.

Acceptance Criteria

  • Root cause identified (consumer swap during init, or another path)
  • A connect does not fail with this message under normal operation
  • The double-reader guard remains — the fix must not permit two concurrent readers on one stream
  • Regression test with a stream whose reader does not exit promptly

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