feat: enhance relay failure handling and selection logic

rabbitprincess committed Apr 12, 2026 at 22:03 UTC 0add2c1bf48f88167e9a8576064b205ce20bf923
3 files changed +27 -4
portal/discovery/policy.go
+3
@@ -71,6 +71,9 @@ func (p DefaultRelayPolicy) SelectPriority(states []RelayState, clientState Clie
71
72 out := selected[:0]
73 for _, state := range selected {
74 + if state.consecutiveFailures > 0 && !state.Reachable {
75 + continue
76 + }
77 if clientState.RequireUDP && state.hasDescriptor() && !state.Descriptor.SupportsUDP {
78 continue
79 }
portal/discovery/relayset.go
+18 -4
@@ -316,6 +316,23 @@ func (s *RelaySet) RecordDiscoveryRTT(relayURL string, rtt time.Duration, measur
316 s.relays[relayURL] = state
317 }
318
319 +func (s *RelaySet) recordRelayFailureLocked(relayURL string, state RelayState, err error, recoveryFailures int) (expired bool, expireReason string, consecutiveFailures int) {
320 + state, expired, expireReason = s.policy.OnFailure(state, err, recoveryFailures)
321 + s.relays[relayURL] = state
322 + return expired, expireReason, state.consecutiveFailures
323 +}
324 +
325 +func (s *RelaySet) RecordRelayFailure(relayURL string, err error, recoveryFailures int) (expired bool, expireReason string, consecutiveFailures int) {
326 + s.mu.Lock()
327 + defer s.mu.Unlock()
328 +
329 + state, ok := s.relays[relayURL]
330 + if !ok {
331 + return false, "", 0
332 + }
333 + return s.recordRelayFailureLocked(relayURL, state, err, recoveryFailures)
334 +}
335 +
336 func (s *RelaySet) RecordDiscoveryFailure(identity types.Identity, relayURL string, err error, recoveryFailures int) (expired bool, expireReason string, consecutiveFailures int) {
337 relayKey := identity.Key()
338 if relayKey == "" {
@@ -343,8 +360,5 @@ func (s *RelaySet) RecordDiscoveryFailure(identity types.Identity, relayURL stri
360 if !ok {
361 return false, "", 0
362 }
346 -
347 - state, expired, expireReason = s.policy.OnFailure(state, err, recoveryFailures)
348 - s.relays[relayURL] = state
349 - return expired, expireReason, state.consecutiveFailures
363 + return s.recordRelayFailureLocked(relayURL, state, err, recoveryFailures)
364 }
sdk/listener.go
+6
@@ -147,6 +147,9 @@ func (l *Listener) runStartup(ctx context.Context) {
147 errors.Is(err, &types.APIRequestError{Code: types.APIErrorCodeTransportMismatch}) ||
148 errors.Is(err, &types.APIRequestError{Code: types.APIErrorCodeHostnameConflict}) ||
149 errors.Is(err, &types.APIRequestError{Code: types.APIErrorCodeIPBanned}) {
150 + if l.relaySet != nil && l.api != nil && l.api.baseURL != nil {
151 + l.relaySet.RecordRelayFailure(l.api.baseURL.String(), err, 1)
152 + }
153 log.Error().
154 Err(err).
155 Str("relay_url", l.api.baseURL.String()).
@@ -669,6 +672,9 @@ func (l *Listener) retryOrClose(ctx context.Context, operation string, err error
672 Logger()
673
674 if l.retryCount > 0 && retries > l.retryCount {
675 + if l.relaySet != nil && l.api != nil && l.api.baseURL != nil {
676 + l.relaySet.RecordRelayFailure(l.api.baseURL.String(), err, 1)
677 + }
678 if operation != "lease renewal" {
679 logger.Error().
680 Err(err).