Skip to content

Harden the shared-controller architecture (port-key identity, lifecycle, diagnostics) #25

Description

Tracking issue from an architecture review of the shared serial-bus controller that the arm and gripper components share (registry.go / manager.go).

Context

The arm (servos 1-5) and gripper (servo 6) sit on one serial bus and must share a single connection. Today that's done via a process-global registry (globalRegistry) keyed by the port string, ref-counted, handing both resources a shared SafeSoArmController. The two resources look independent in config but are joined at runtime by a string match the framework can't see or manage.

The concept is sound (you can't have two owners of one UART, and forcing the gripper to declare an arm dependency — as the xarm module does — costs the standalone-gripper UX we intentionally kept). The implementation makes the coupling invisible and identity-fragile. This issue tracks hardening it rather than replacing it.

Footguns, ranked

  1. Port-string identity → silent double-open (worst). The registry keys on config.Port verbatim. Arm /dev/ttyUSB0 + gripper /dev/serial/by-id/usb-... (same device) never match → two feetech.NewBus connections contending for one UART. Fails silently at the hardware level (garbled frames, intermittent timeouts).
  2. Config drift → hard "conflict" error. Same port string but baudrate/timeout differ (one set, one defaulted) → configsEqual fails → second component refuses to build. timeout gates identity despite being irrelevant to bus identity.
  3. Fragile, order-dependent lifecycle. Release goes through releaseFromCaller()runtime.Caller(2) PC matching — sensitive to reconfigure order and RDK call-stack shape. The fix(controller): propagate close-state to all shared-controller holders #24 data race is a symptom of the same shared-mutable-singleton root.
  4. Global blast radius. ForceCloseSharedController() closes every entry; registry is process-wide.

Note: the guided path is safe — discovery emits both configs with the same portPath and no baudrate/timeout, so footguns #1/#2 are manual-config-only. That lowers real-world severity but doesn't remove the trap.

Plan: harden in place (Path A), keep the standalone-gripper UX

North star (not scheduled)

If hardening stops paying off — i.e. users keep misconfiguring despite the above, or the standalone-gripper case proves unimportant — migrate to the xarm dependency model (gripper depends on arm; arm solely owns the bus). That makes the coupling explicit and framework-managed and eliminates the cross-component data race by construction (single owner). It reintroduces the standalone-gripper limitation and is a user-visible migration, so it should be a deliberate v-next, not a bug reaction.

Related: #24 (close-state race fix), #11 (closed — lifecycle refactor this draws from).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions