Skip to content

fix(smart-router): do not panic when 1 static provider fails, trigger a background gorou…#2262

Open
Tomelia1999 wants to merge 5 commits intomainfrom
fix-unified-setup-panic
Open

fix(smart-router): do not panic when 1 static provider fails, trigger a background gorou…#2262
Tomelia1999 wants to merge 5 commits intomainfrom
fix-unified-setup-panic

Conversation

@Tomelia1999
Copy link
Copy Markdown
Contributor

…tine that re verifies the fails providers every 3 minutes

Description

Closes: #XXXX


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • read the contribution guide
  • included the correct type prefix in the PR title, you can find examples of the prefixes below:
  • confirmed ! in the type prefix if API or client breaking change
  • targeted the main branch
  • provided a link to the relevant issue or specification
  • reviewed "Files changed" and left comments if necessary
  • included the necessary unit and integration tests
  • updated the relevant documentation or specification, including comments for documenting Go code
  • confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

I have...

  • confirmed the correct type prefix in the PR title
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage

…tine that re verifies the fails providers every 3 minutes
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Graceful handling of failed static providers with background retry

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Prevent panic when static providers fail verification at startup
• Exclude failed providers from session registration, allowing partial startup
• Launch background retry goroutine to re-validate failed providers every 3 minutes
• Add thread-safe copy-on-write merging when recovering providers during retries
• Implement graceful degradation: endpoint serves with healthy providers while retrying failed ones
Diagram
flowchart LR
  A["Provider Validation<br/>Phase 1"] --> B{"All Failed?"}
  B -->|Yes| C["Return Error<br/>Block Startup"]
  B -->|No| D["Filter Failed<br/>Providers"]
  D --> E["Register Healthy<br/>Providers"]
  E --> F["Launch Retry<br/>Goroutine"]
  F --> G["Background Loop<br/>Every 3min"]
  G --> H{"Provider<br/>Recovered?"}
  H -->|Yes| I["Merge into Active<br/>Sessions"]
  H -->|No| J["Keep Retrying"]
  I --> K["Update Session<br/>Manager"]
Loading

Grey Divider

File Changes

1. protocol/rpcsmartrouter/rpcsmartrouter.go Bug fix, enhancement, error handling +423/-193

Graceful provider failure handling with background retry loop

• Added failedStaticProviders map to track providers that failed verification at startup
• Changed error handling in Phase 1 validation from panic/return to graceful exclusion with logging
• Implemented filtering logic to exclude failed providers before session registration
• Added retryFailedStaticProviders() background goroutine that re-validates failed providers every
 3 minutes
• Implemented copy-on-write merging to safely re-register recovered providers without mutating old
 session maps
• Updated updateEpoch() to hold lock during read-modify-write cycle to prevent races with retry
 goroutine
• Modified ChainTracker creation to use only healthy providers instead of unfiltered list

protocol/rpcsmartrouter/rpcsmartrouter.go


2. protocol/rpcsmartrouter/rpcsmartrouter_test.go 🧪 Tests +441/-0

Comprehensive test coverage for graceful provider failures

• Added helper functions for test setup: createTestRPCSmartRouter(), createTestSessionManager(),
 createTestProviderSession(), createTestStaticProviderEndpoint()
• Added 11 new test scenarios covering graceful failure cases: all healthy, epoch doesn't resurrect
 failed, filtering logic, all failed, nil maps, single provider failure, mixed static/backup,
 duplicate names, retry self-termination, concurrent epoch/retry, copy-on-write correctness, epoch
 before retry
• Tests verify no panics, correct filtering, thread-safety with race detector, and proper state
 transitions

protocol/rpcsmartrouter/rpcsmartrouter_test.go


3. smartrouter_lava.yml ⚙️ Configuration changes +41/-29

Update smart router config for local development

• Updated configuration to use local endpoints instead of remote gateway URLs
• Changed REST endpoint from remote gateway to local http://127.0.0.1:1317
• Added tendermintrpc endpoint configuration with local http://127.0.0.1:26657 and WebSocket
 support
• Added timeout configurations (5s and 10s) for local endpoints
• Simplified provider setup to match local development environment

smartrouter_lava.yml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 9, 2026

Code Review by Qodo

🐞 Bugs (2)   📘 Rule violations (0)   📎 Requirement gaps (0)   🖥 UI issues (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (2)

Grey Divider


Action required

1. Retry goroutine connection leak🐞
Description
retryFailedStaticProviders builds new ChainRouters every 3 minutes using the long-lived endpoint ctx
and never cancels per-attempt contexts, so connector goroutines/clients created during verification
remain alive until the entire smart-router ctx is cancelled. This can cause unbounded growth in idle
goroutines/connections for permanently failing providers.
Code

protocol/rpcsmartrouter/rpcsmartrouter.go[R1686-1705]

+			parallelConnections := uint(lavasession.DefaultMaximumStreamsOverASingleConnection)
+			verificationRouter, err := chainlib.GetChainRouter(ctx, parallelConnections, verificationEndpoint, chainParser)
+			if err != nil {
+				stillFailed = append(stillFailed, provider)
+				utils.LavaFormatWarning("retry: static provider chain router creation still failing", err,
+					utils.LogAttr("chain", rpcEndpoint.ChainID),
+					utils.LogAttr("provider", provider.Name),
+				)
+				continue
+			}
+
+			verificationFetcher := chainlib.NewChainFetcher(ctx, &chainlib.ChainFetcherOptions{
+				ChainRouter: verificationRouter,
+				ChainParser: chainParser,
+				Endpoint:    verificationEndpoint,
+				Cache:       nil,
+			})
+
+			if err := verificationFetcher.Validate(ctx); err != nil {
+				stillFailed = append(stillFailed, provider)
Evidence
The retry loop creates a ChainRouter with the endpoint-scoped ctx and uses the same ctx for
Validate, without any per-iteration cancel. ChainRouter construction passes ctx down into proxy
constructors, which create connectors that spawn goroutines waiting on ctx.Done() before closing;
those goroutines keep the connector (and underlying client) reachable, so resources persist and
accumulate across retry ticks until the endpoint shuts down.

protocol/rpcsmartrouter/rpcsmartrouter.go[1627-1712]
protocol/chainlib/chainlib.go[211-226]
protocol/chainlib/chain_router.go[241-295]
protocol/chainlib/chainproxy/connector.go[62-88]
protocol/chainlib/chainproxy/connector.go[131-147]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`retryFailedStaticProviders` repeatedly creates verification ChainRouters using the long-lived endpoint context. Downstream connectors start goroutines that only terminate on `ctx.Done()`, so each retry tick can leave behind additional goroutines/clients until process shutdown.
### Issue Context
Connectors (e.g. HTTP Connector) start `go connector.connectorLoop(ctx)` which blocks on `<-ctx.Done()` before closing the client.
### Fix Focus Areas
- protocol/rpcsmartrouter/rpcsmartrouter.go[1627-1712]
- protocol/chainlib/chainproxy/connector.go[62-88]
### Suggested fix
- In `retryFailedStaticProviders`, create a short-lived context per provider attempt (or per tick), e.g. `verifyCtx, cancel := context.WithTimeout(ctx, <reasonable timeout>)`.
- Pass `verifyCtx` to `GetChainRouter` and `Validate`.
- Call `cancel()` after each provider attempt (or at end of tick) so connector goroutines terminate and clients close.
- Consider doing the same in the Phase-1 startup validation loop (to avoid keeping validation-only connectors alive for the full endpoint lifetime).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Subscriptions use failed providers 🐞
Description
CreateSmartRouterEndpoint filters static providers into healthyStaticProviders for session
registration, but WebSocket and gRPC subscription endpoints are still collected from
relevantStaticProviderList (unfiltered). This allows providers that failed validation (and were
excluded from routing) to still be used for subscriptions, causing avoidable subscription failures
against dead nodes.
Code

protocol/rpcsmartrouter/rpcsmartrouter.go[R921-988]

+	// Collect ALL WebSocket-capable endpoints from static providers for direct subscriptions
+	// WebSocket URLs are identified by ws:// or wss:// prefix
+	var wsEndpoints []*common.NodeUrl
+	for _, provider := range relevantStaticProviderList {
+		for i := range provider.NodeUrls {
+			url := strings.ToLower(provider.NodeUrls[i].Url)
+			if strings.HasPrefix(url, "ws://") || strings.HasPrefix(url, "wss://") {
+				wsEndpoints = append(wsEndpoints, &provider.NodeUrls[i])
+				utils.LavaFormatInfo("Found WebSocket endpoint for direct subscriptions",
+					utils.LogAttr("url", provider.NodeUrls[i].Url),
+					utils.LogAttr("provider", provider.Name),
+					utils.LogAttr("chainID", provider.ChainID),
+				)
+			}
+		}
+	}
+
+	// Create DirectWSSubscriptionManager if WebSocket endpoints are available
+	// Otherwise fall back to provider-based subscription manager
+	if len(wsEndpoints) > 0 {
+		directWSManager := NewDirectWSSubscriptionManager(
+			smartRouterMetricsManager,
+			spectypes.APIInterfaceJsonRPC, // WebSocket subscriptions use JSON-RPC
+			rpcEndpoint.ChainID,
+			rpcEndpoint.ApiInterface,
+			wsEndpoints,
+			optimizer, // Pass optimizer for endpoint selection
+			nil,       // Use default WebSocket config (configurable via CLI flags later)
+		)
+		// Start background cleanup goroutine
+		directWSManager.Start(ctx)
+		wsSubscriptionManager = directWSManager
+		utils.LavaFormatInfo("Using DirectWSSubscriptionManager for direct WebSocket subscriptions",
+			utils.LogAttr("chainID", rpcEndpoint.ChainID),
+			utils.LogAttr("apiInterface", rpcEndpoint.ApiInterface),
+			utils.LogAttr("wsEndpointCount", len(wsEndpoints)),
+			utils.LogAttr("optimizerEnabled", optimizer != nil),
+		)
+	} else {
+		// No WebSocket endpoints configured - use NoOp manager that returns clear errors
+		// Smart router does NOT fall back to provider-based subscriptions (per implementation plan)
+		// Provider-based subscriptions are only for rpcconsumer, not rpcsmartrouter
+		wsSubscriptionManager = NewNoOpWSSubscriptionManager(rpcEndpoint.ChainID, rpcEndpoint.ApiInterface)
+		utils.LavaFormatInfo("No WebSocket endpoints configured for direct subscriptions",
+			utils.LogAttr("chainID", rpcEndpoint.ChainID),
+			utils.LogAttr("apiInterface", rpcEndpoint.ApiInterface),
+			utils.LogAttr("hint", "Add ws:// or wss:// URLs to static-providers-list to enable subscriptions"),
+		)
+	}
+
+	// Create gRPC streaming subscription manager for gRPC server-streaming methods
+	// This supports Cosmos Event Streaming, Solana Geyser, and other gRPC streaming protocols
+	var grpcEndpoints []*common.NodeUrl
+	if rpcEndpoint.ApiInterface == spectypes.APIInterfaceGrpc {
+		// Collect gRPC endpoints from static providers
+		for _, provider := range relevantStaticProviderList {
+			if provider.ApiInterface == spectypes.APIInterfaceGrpc {
+				for i := range provider.NodeUrls {
+					grpcEndpoints = append(grpcEndpoints, &provider.NodeUrls[i])
+					utils.LavaFormatInfo("Found gRPC endpoint for streaming subscriptions",
+						utils.LogAttr("url", provider.NodeUrls[i].Url),
+						utils.LogAttr("provider", provider.Name),
+						utils.LogAttr("chainID", provider.ChainID),
+					)
+				}
+			}
+		}
+	}
Evidence
Static providers are filtered before convertProvidersToSessions and UpdateAllProviders, but the
WS and gRPC endpoint collection loops iterate relevantStaticProviderList rather than
healthyStaticProviders. That contradicts the intent of excluding failed providers from active use
and can route subscription traffic to endpoints known to have failed verification.

protocol/rpcsmartrouter/rpcsmartrouter.go[821-849]
protocol/rpcsmartrouter/rpcsmartrouter.go[921-937]
protocol/rpcsmartrouter/rpcsmartrouter.go[971-988]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
WS/gRPC subscription endpoint collection uses the unfiltered static provider list, so endpoints that failed validation can still be used for subscriptions.
### Issue Context
Providers are filtered into `healthyStaticProviders` for session registration, but subscription endpoint collection still iterates `relevantStaticProviderList`.
### Fix Focus Areas
- protocol/rpcsmartrouter/rpcsmartrouter.go[821-849]
- protocol/rpcsmartrouter/rpcsmartrouter.go[921-988]
### Suggested fix
- Change the WS endpoint collection loop to iterate `healthyStaticProviders`.
- Change the gRPC endpoint collection loop to iterate `healthyStaticProviders` (and keep the `ApiInterface == grpc` check).
- (Optional) If you want subscriptions to automatically include recovered providers, consider rebuilding/updating the subscription managers when `retryFailedStaticProviders` re-registers recovered providers, or keep subscription endpoints tied strictly to currently-registered providers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Name-keyed failure collisions 🐞
Description
Failed-provider tracking and filtering use provider.Name as the unique key
(failedStaticNames/failedBackupNames), so duplicate names can exclude healthy providers sharing the
same name and can undercount failures in the “all providers failed” check. This can let an endpoint
start with an empty healthy provider set and then fail requests later due to no valid providers.
Code

protocol/rpcsmartrouter/rpcsmartrouter.go[R646-648]

  		if err != nil {
-				err = utils.LavaFormatError("[PANIC] failed creating chain router for verification", err,
+				failedStaticNames[staticProvider.Name] = struct{}{}
+				failedStaticEndpoints = append(failedStaticEndpoints, staticProvider)
Evidence
Failures are stored in a map keyed only by staticProvider.Name and later filtering checks
failedStaticNames[p.Name]. With duplicate names, the map collapses multiple failures into one
entry, so the all-fail check can be bypassed, and filtering can remove multiple providers
unintentionally. If the registered provider set becomes empty, the consumer session manager later
returns PairingListEmptyError (“No pairings available”).

protocol/rpcsmartrouter/rpcsmartrouter.go[596-608]
protocol/rpcsmartrouter/rpcsmartrouter.go[646-696]
protocol/rpcsmartrouter/rpcsmartrouter.go[827-835]
protocol/lavasession/consumer_session_manager.go[1083-1094]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Failure tracking uses `provider.Name` as a unique key. Duplicate names can:
1) cause a healthy provider to be filtered out because another provider with the same name failed, and
2) cause `len(failedStaticNames)` to undercount failures and bypass the “all providers failed” gate.
### Issue Context
The all-fail logic and filtering depend on the failed-name set size.
### Fix Focus Areas
- protocol/rpcsmartrouter/rpcsmartrouter.go[596-696]
- protocol/rpcsmartrouter/rpcsmartrouter.go[827-835]
### Suggested fix
- Track failures using a stable unique identifier (e.g., list index, or composite key like `name + networkAddress + firstNodeUrl`, or `NetworkAddress` + `ApiInterface`).
- Alternatively, keep `failedCount` as an integer counter independent of the set, and use the set only for filtering.
- Consider validating configuration to enforce unique provider names up-front and error clearly if duplicates are found.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread protocol/rpcsmartrouter/rpcsmartrouter.go
Comment on lines +921 to +988
// Collect ALL WebSocket-capable endpoints from static providers for direct subscriptions
// WebSocket URLs are identified by ws:// or wss:// prefix
var wsEndpoints []*common.NodeUrl
for _, provider := range relevantStaticProviderList {
for i := range provider.NodeUrls {
url := strings.ToLower(provider.NodeUrls[i].Url)
if strings.HasPrefix(url, "ws://") || strings.HasPrefix(url, "wss://") {
wsEndpoints = append(wsEndpoints, &provider.NodeUrls[i])
utils.LavaFormatInfo("Found WebSocket endpoint for direct subscriptions",
utils.LogAttr("url", provider.NodeUrls[i].Url),
utils.LogAttr("provider", provider.Name),
utils.LogAttr("chainID", provider.ChainID),
)
}
}
}

// Create DirectWSSubscriptionManager if WebSocket endpoints are available
// Otherwise fall back to provider-based subscription manager
if len(wsEndpoints) > 0 {
directWSManager := NewDirectWSSubscriptionManager(
smartRouterMetricsManager,
spectypes.APIInterfaceJsonRPC, // WebSocket subscriptions use JSON-RPC
rpcEndpoint.ChainID,
rpcEndpoint.ApiInterface,
wsEndpoints,
optimizer, // Pass optimizer for endpoint selection
nil, // Use default WebSocket config (configurable via CLI flags later)
)
// Start background cleanup goroutine
directWSManager.Start(ctx)
wsSubscriptionManager = directWSManager
utils.LavaFormatInfo("Using DirectWSSubscriptionManager for direct WebSocket subscriptions",
utils.LogAttr("chainID", rpcEndpoint.ChainID),
utils.LogAttr("apiInterface", rpcEndpoint.ApiInterface),
utils.LogAttr("wsEndpointCount", len(wsEndpoints)),
utils.LogAttr("optimizerEnabled", optimizer != nil),
)
} else {
// No WebSocket endpoints configured - use NoOp manager that returns clear errors
// Smart router does NOT fall back to provider-based subscriptions (per implementation plan)
// Provider-based subscriptions are only for rpcconsumer, not rpcsmartrouter
wsSubscriptionManager = NewNoOpWSSubscriptionManager(rpcEndpoint.ChainID, rpcEndpoint.ApiInterface)
utils.LavaFormatInfo("No WebSocket endpoints configured for direct subscriptions",
utils.LogAttr("chainID", rpcEndpoint.ChainID),
utils.LogAttr("apiInterface", rpcEndpoint.ApiInterface),
utils.LogAttr("hint", "Add ws:// or wss:// URLs to static-providers-list to enable subscriptions"),
)
}

// Create gRPC streaming subscription manager for gRPC server-streaming methods
// This supports Cosmos Event Streaming, Solana Geyser, and other gRPC streaming protocols
var grpcEndpoints []*common.NodeUrl
if rpcEndpoint.ApiInterface == spectypes.APIInterfaceGrpc {
// Collect gRPC endpoints from static providers
for _, provider := range relevantStaticProviderList {
if provider.ApiInterface == spectypes.APIInterfaceGrpc {
for i := range provider.NodeUrls {
grpcEndpoints = append(grpcEndpoints, &provider.NodeUrls[i])
utils.LavaFormatInfo("Found gRPC endpoint for streaming subscriptions",
utils.LogAttr("url", provider.NodeUrls[i].Url),
utils.LogAttr("provider", provider.Name),
utils.LogAttr("chainID", provider.ChainID),
)
}
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Subscriptions use failed providers 🐞 Bug ≡ Correctness

CreateSmartRouterEndpoint filters static providers into healthyStaticProviders for session
registration, but WebSocket and gRPC subscription endpoints are still collected from
relevantStaticProviderList (unfiltered). This allows providers that failed validation (and were
excluded from routing) to still be used for subscriptions, causing avoidable subscription failures
against dead nodes.
Agent Prompt
### Issue description
WS/gRPC subscription endpoint collection uses the unfiltered static provider list, so endpoints that failed validation can still be used for subscriptions.

### Issue Context
Providers are filtered into `healthyStaticProviders` for session registration, but subscription endpoint collection still iterates `relevantStaticProviderList`.

### Fix Focus Areas
- protocol/rpcsmartrouter/rpcsmartrouter.go[821-849]
- protocol/rpcsmartrouter/rpcsmartrouter.go[921-988]

### Suggested fix
- Change the WS endpoint collection loop to iterate `healthyStaticProviders`.
- Change the gRPC endpoint collection loop to iterate `healthyStaticProviders` (and keep the `ApiInterface == grpc` check).
- (Optional) If you want subscriptions to automatically include recovered providers, consider rebuilding/updating the subscription managers when `retryFailedStaticProviders` re-registers recovered providers, or keep subscription endpoints tied strictly to currently-registered providers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

known but won't get handled on this PR, dedicated ticket will be opened

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 15d987c addressed this comment by collecting WebSocket and gRPC subscription endpoints from healthyStaticProviders instead of the unfiltered list, ensuring failed providers are excluded from subscriptions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The condition len(failedStaticNames) >= validatedCount is semantically ambiguous based on whether validatedCount tracks all attempted providers or only successful ones. Based on the companion log healthy = validatedCount -
▎ len(failedStaticNames), the intent appears to be "total attempted", but the variable name says "validated" (successful). This can cause the endpoint to be killed when there are still healthy providers.

Suggestion:
▎ // Rename the loop counter and fix the check
▎ totalAttemptedCount := 0 // was: validatedCount
▎ // (increment on each iteration, not just on success)

▎ healthyCount := totalAttemptedCount - len(failedStaticNames)
▎ if healthyCount == 0 {
▎ err := utils.LavaFormatError("all static providers failed — cannot serve endpoint", nil, ...)
▎ errCh <- err
▎ return err
▎ }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic is good, no changes needed.
renamed the param validatedCount -> totalAttemptedCount

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 15d987c addressed this comment by renaming validatedCount to totalAttemptedCount, incrementing it for every provider attempt, and deriving healthyCount for failure detection so the endpoint only errors when all attempted providers fail while still logging healthy counts appropriately.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WS endpoint collection loop (~line 960 in new file)

Issue: relevantStaticProviderList is used to collect WebSocket and gRPC endpoints, but this is the unfiltered list including failed providers. This is a regression from the old panic-before-reaching-this-line behavior.

Suggestion: Change both loops to use healthyStaticProviders. Even if MAG-1569 tracks the full fix, using the unfiltered list here is strictly worse than before:
▎ for _, provider := range healthyStaticProviders { // not relevantStaticProviderList

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 15d987c addressed this comment by switching the WebSocket and gRPC endpoint collection loops to iterate over healthyStaticProviders, ensuring only validated providers contribute endpoints instead of the unfiltered list.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: A hung provider will block the retry loop indefinitely for all other failed providers sharing the same goroutine.

Suggestion:
▎ attemptCtx, attemptCancel := context.WithTimeout(ctx, 30*time.Second)
▎ defer attemptCancel()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 15d987c addressed this comment by wrapping each verification and retry attempt in a 30-second context.WithTimeout so that a hung provider can no longer block the shared goroutine loop, ensuring stalled connections time out instead of hanging indefinitely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants