You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 portstring, 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
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).
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.
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.
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
Actionable conflict diagnostics — only treat true bus-identity fields (port, baudrate) as hard conflicts; warn on the rest. One clear error naming both resources and the differing field.
(Optional, lower priority) shrink blast radius: reconsider the process-global singleton.
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).
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 theportstring, ref-counted, handing both resources a sharedSafeSoArmController. 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
armdependency — 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
config.Portverbatim. Arm/dev/ttyUSB0+ gripper/dev/serial/by-id/usb-...(same device) never match → twofeetech.NewBusconnections contending for one UART. Fails silently at the hardware level (garbled frames, intermittent timeouts).baudrate/timeoutdiffer (one set, one defaulted) →configsEqualfails → second component refuses to build.timeoutgates identity despite being irrelevant to bus identity.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.ForceCloseSharedController()closes every entry; registry is process-wide.Note: the guided path is safe — discovery emits both configs with the same
portPathand 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
runtime.Callermachinery. Removes Update kinematics to match urdf #3's fragility.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).