From 8a4457119a198be1bd2d694638b52aff3a46a13c Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Sun, 14 Jun 2026 13:03:39 +0000 Subject: [PATCH] Add comments documenting gousb lifecycle management Explain why we use gousb directly instead of antgo/driver/usb (libusb context leak on error paths), document the defer cleanup ordering, and note that errors are returned for graceful retry. --- cmd/bridge/ant.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmd/bridge/ant.go b/cmd/bridge/ant.go index eaf8aff..91189ac 100644 --- a/cmd/bridge/ant.go +++ b/cmd/bridge/ant.go @@ -44,6 +44,11 @@ func resetAndWait(drv io.ReadWriter) error { func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32, events chan<- Event) error { + // Use gousb directly instead of antgo/driver/usb to manage context + // lifecycle explicitly. The old code created a new gousb.Context on + // every call via libusb_init and leaked it on error paths (device not + // found, interface claim failure, etc.), which triggered a libusb + // assertion after repeated ListenForTrainer calls. gctx := gousb.NewContext() defer gctx.Close() @@ -60,6 +65,9 @@ func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32, if err != nil { return fmt.Errorf("ant: claim interface: %w", err) } + // Cleanup is ordered by defer stack: done() releases interface, + // dev.Close() closes device, gctx.Close() closes context, cancel() + // cancels the ANT message goroutine. defer done() out, err := intf.OutEndpoint(1) @@ -75,6 +83,8 @@ func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32, logMsg("ant: device opened") + // Errors are returned (not panicked) so the caller can retry + // gracefully instead of requiring a full process restart. if err := resetAndWait(drv); err != nil { return err } @@ -85,6 +95,8 @@ func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32, msgs := make(chan ant.BroadcastMessage, 100) ctx, cancel := context.WithCancel(context.Background()) + // cancel is the final defer in the stack, ensuring the ANT message + // goroutine is torn down after all USB resources are released. defer cancel() go func() { device.DumpBroadcastMessages(ctx, drv, msgs) @@ -124,6 +136,8 @@ func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32, } case <-time.After(10 * time.Second): logMsg("ant: no more messages arrived, restarting") + // Return without error so the caller can retry gracefully + // rather than panicking and requiring a full restart. return nil } }