Skip to content

fix(serial): SerialClient greets every connection with hello (#443) - #447

Merged
lxsaah merged 3 commits into
mainfrom
claude/clever-edison-bcr4y0
Nov 9, 2026
Merged

lxsaah merged 3 commits into
mainfrom
claude/clever-edison-bcr4y0

Conversation

@lxsaah

@lxsaah lxsaah commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Description

A UART never closes, so a SerialServer learns that the client behind it changed only from a hello. SerialClient never sent one. When a host process restarted while the board kept running, the new process numbered its subscriptions from 1 again. The old session still held those ids, so the board answered Denied. Mirrored records went silent until the board was reset, while writes kept working.

aimdb-core

  • EnvelopeCodec::hello is a new provided method that defaults to None. It names the request a client sends to open each connection. AimxCodec returns ("hello", {"client","version": PROTOCOL_VERSION}).
  • ClientConfig::sends_hello now sends the codec's hello when the codec has one:
    • It is sent once per connection, before any caller command, and the client does not wait for the reply. The connection keeps frame order, so the server handles the hello before the subscriptions and writes that follow it.
    • The hello uses correlation id 0, which ClientHandle never issues. The demux loop drops its reply when it arrives. A version_mismatch reply is logged as the new HelloRefused event and the client keeps running.
    • Codecs without a hello keep the existing Ping/Pong exchange, which still waits for the Pong.

aimdb-serial-connector

  • SerialClient::new and SerialClient::over_port now set sends_hello: true.
  • When SerialPortDialer opens the port, it writes a single 0x00 delimiter. Without it, a frame left half-written by the previous process could merge with the new hello, and the board would drop both. The receiver skips an extra delimiter as an empty chunk.
  • Updated the crate docs ("Sessions on a line that never closes") and both CHANGELOGs.

Why the client doesn't wait for the Welcome

The first version of this PR waited for the reply and resent the hello every second until one came. That opened failure modes that order alone avoids:

  • The handshake loop didn't watch the command channel. On a silent line, it kept resending after every handle was dropped.
  • A Welcome larger than the receive limit was dropped by the COBS reader. The client then resent forever without ever moving on.

Accepted limits

  • A hello lost on the line is not resent. The client stays in the old session until it reconnects or restarts. The leading delimiter covers the likely cause, a half-written frame left by the previous process.
  • A protocol-version mismatch is only logged and doesn't stop the client. The board doesn't stop serving after a refused hello either.
  • A board still running a session opened by an older client, one that never sent hello, only gets greeted by the first upgraded client's hello. Resetting the board once clears this.
  • Out of scope, as the issue says: inbound mirrors are still not re-issued after a reconnect, so resetting the board under a running client still stops them.

Related Issue

Tests

aimdb-serial-connector/tests/client_restart.rs:

  • a_restarted_client_keeps_mirroring: the board produces every 5 ms on a line that stays open. Three host processes (SerialClient::new + link_from) run one after another and are killed without a goodbye. Each must mirror values newer than when it started. With sends_hello turned off, this test fails at host process 2, which reproduces the issue.
  • the_hello_goes_first_and_its_reply_is_not_awaited: a fake board receives a hello (id 0) first, then a subscribe with no Welcome sent. The subscription's events mirror. A late ok reply and a late version_mismatch reply, both with id 0, leave the subscription mirroring.

Checklist

  • I have read the CONTRIBUTING.md document.
  • My code follows the project's coding standards.
  • I have added tests to cover my changes.
  • All new and existing tests passed (make check). I did not run the full make check. What I ran passed: cargo fmt --check; cargo test for aimdb-core (std,connector-session,remote,tracing,log), aimdb-serial-connector (std) and aimdb-client (all features); clippy -D warnings on the core and serial legs, including thumbv7em-none-eabihf with defmt and _test-embassy; and rustdoc -D warnings for both crates.
  • I have updated the documentation accordingly.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGkDJ6wpoRhHZ8DEGbXbf3

claude and others added 3 commits November 9, 2026 16:56
A UART never closes, so a server learns that the client behind it changed
only from a hello. SerialClient never sent one: a host process restarted
while the board kept running reused subscription ids the old session still
held, the board answered Denied, and mirrored records went silent until a
board reset.

aimdb-core:
- EnvelopeCodec::hello (default None) names the request a client opens
  each connection with; AimxCodec returns AimX's hello.
- ClientConfig::sends_hello sends that hello when the codec has one (else
  the old Ping/Pong). It skips frames of the previous session that arrive
  before the reply, resends an unanswered hello every HELLO_RESEND_MS, and
  stops the engine on a version_mismatch instead of redialing.

aimdb-serial-connector:
- SerialClient::new and over_port turn sends_hello on.
- SerialPortDialer writes a frame delimiter on open, so a frame a previous
  process left half-written cannot swallow the hello.
- tests/client_restart.rs: three host processes in turn on one live line
  keep mirroring (fails at the second without the fix), plus a lost hello
  with stray frames, and a refused version.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGkDJ6wpoRhHZ8DEGbXbf3
Frame order already puts the hello ahead of every command, so waiting for
the Welcome bought little and added failure modes: the handshake loop did
not watch the command channel, so a silent line kept it resending after
every handle was dropped, and a Welcome over the receive limit was dropped
by the framer and left the client resending forever.

- sends_hello with a codec hello now sends it once per connection and goes
  straight on. Its reply (id 0, which no ClientHandle issues) is dropped in
  the demux loop; a version_mismatch reply is logged as HelloRefused and
  the client keeps running. The Ping/Pong path is unchanged.
- Drop the resend timer, HELLO_RESEND_MS, Ended::Refused and the
  HelloUnanswered event.
- Tests: the resend and version-refusal tests become one showing the hello
  goes first, the subscribe follows without a Welcome, and late id-0
  replies (ok and refused) leave the subscription mirroring.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WGkDJ6wpoRhHZ8DEGbXbf3
@lxsaah
lxsaah merged commit 0e61234 into main Nov 9, 2026
12 checks passed
@lxsaah
lxsaah deleted the claude/clever-edison-bcr4y0 branch November 9, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SerialClient mirrors never send hello, so a restarted client's subscriptions are refused

2 participants