feat(sdk): track active_tunnels_per_relay gauge in accept loop

cognitive committed Apr 30, 2026 at 05:20 UTC ef4b980a81e8999dc1716a8a962068262685e1df
1 file changed +25 -2
sdk/expose.go
+25 -2
@@ -357,6 +357,21 @@ func (c *exposureConn) Close() error {
357 return closeErr
358 }
359
360 +// tunnelCounterConn wraps a net.Conn and calls decr exactly once on the first
361 +// Close invocation to decrement the active_tunnels_per_relay gauge. Subsequent
362 +// Close calls are forwarded to the underlying conn but do not double-decrement.
363 +// Concurrency is guaranteed by sync.Once.
364 +type tunnelCounterConn struct {
365 + net.Conn
366 + once sync.Once
367 + decr func()
368 +}
369 +
370 +func (c *tunnelCounterConn) Close() error {
371 + c.once.Do(c.decr)
372 + return c.Conn.Close()
373 +}
374 +
375 func (e *Exposure) Accept() (net.Conn, error) {
376 select {
377 case <-e.done:
@@ -609,11 +624,19 @@ func (e *Exposure) runListenerAcceptLoop(listener *listener) {
624 return
625 }
626
627 + discovery.ActiveTunnelsPerRelay.WithLabelValues(relayURL).Inc()
628 + wrappedConn := &tunnelCounterConn{
629 + Conn: conn,
630 + decr: func() {
631 + discovery.ActiveTunnelsPerRelay.WithLabelValues(relayURL).Dec()
632 + },
633 + }
634 +
635 select {
636 case <-e.done:
614 - _ = conn.Close()
637 + _ = wrappedConn.Close()
638 return
616 - case e.accepted <- conn:
639 + case e.accepted <- wrappedConn:
640 }
641 }
642 }