⚡ Bolt: Run datasource health checks concurrently in Registry.HealthReport - #131
⚡ Bolt: Run datasource health checks concurrently in Registry.HealthReport#131blue4209211 wants to merge 4 commits into
Conversation
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors the HealthReport function in pkg/proxy/registry.go to perform health checks concurrently using goroutines, preventing slow or timing-out datasources from blocking the entire process. It also adds a unit test to verify this concurrent behavior. The review feedback suggests using defer cancel() inside the goroutine to prevent context leaks and checking the parent context status before initiating health checks.
| checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | ||
| err := t.proxy.HealthCheck(checkCtx) | ||
| cancel() |
There was a problem hiding this comment.
Using defer cancel() is the idiomatic Go pattern to ensure that context resources are cleaned up as soon as the goroutine exits, preventing potential context leaks if the function is later refactored to include early returns.
Additionally, consider checking ctx.Err() at the beginning of the goroutine to avoid initiating health checks if the parent context has already been cancelled.
| checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | |
| err := t.proxy.HealthCheck(checkCtx) | |
| cancel() | |
| checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | |
| defer cancel() | |
| err := t.proxy.HealthCheck(checkCtx) |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request updates the HealthReport method in pkg/proxy/registry.go to run health checks concurrently using goroutines, preventing slow or timing-out datasources from blocking the overall reporting process. It also adds a unit test to verify this behavior and updates the documentation. The feedback suggests using defer cancel() instead of manually calling cancel() to ensure context resources are reliably released on all execution paths.
| Name: t.cfg.Name, | ||
| LastCheck: now, | ||
| } |
There was a problem hiding this comment.
Using defer cancel() is more idiomatic and robust than calling cancel() manually at the end of the block. It ensures that the context resources are released on all execution paths, including any future refactoring that might introduce early returns or panics, and maintains consistency with CollectAllMetadata.
| Name: t.cfg.Name, | |
| LastCheck: now, | |
| } | |
| checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | |
| defer cancel() | |
| err := t.proxy.HealthCheck(checkCtx) |
There was a problem hiding this comment.
Code Review
This pull request refactors the HealthReport method in pkg/proxy/registry.go to run health checks concurrently using goroutines, preventing a single slow or timing-out datasource from blocking others. It also adds a unit test to verify this concurrent behavior. The feedback points out that returning early when ctx.Err() != nil inside the goroutine can cause datasources to be silently omitted from the final report map if the context is cancelled before the goroutine executes. Removing this early return ensures all datasources are consistently represented in the report with an explicit error status.
| if ctx.Err() != nil { | ||
| return | ||
| } |
There was a problem hiding this comment.
By returning early when ctx.Err() != nil, some datasources will be silently omitted from the returned report map if the context is cancelled or times out before the goroutine executes. Since t.proxy.HealthCheck(checkCtx) already respects context cancellation and will return immediately with the context error, removing this early return ensures that every datasource is consistently represented in the report with an explicit error status (e.g., context canceled) instead of being missing from the map.
…erve full reporting
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors the HealthReport method in pkg/proxy/registry.go to run health checks concurrently using goroutines, preventing a single slow or timing-out datasource from blocking others. It also adds a corresponding unit test to verify this behavior. The reviewer suggested limiting the maximum concurrency of these health checks using a semaphore (buffered channel) to prevent potential resource exhaustion when dealing with a large number of datasources.
| var mu sync.Mutex | ||
| var wg sync.WaitGroup | ||
|
|
||
| if !pOk || !cOk { | ||
| continue | ||
| } | ||
| // Run health checks concurrently to prevent a single slow or timing-out | ||
| // datasource from blocking the health status of other datasources. | ||
| for _, t := range targets { | ||
| wg.Add(1) | ||
| go func(t target) { | ||
| defer wg.Done() |
There was a problem hiding this comment.
Running health checks concurrently across all registered datasources without any limit can lead to resource exhaustion (such as exceeding the open file descriptor limit due to concurrent network connections, or overwhelming downstream services) if there are many datasources. Introducing a simple semaphore using a buffered channel limits the maximum concurrency while still allowing parallel execution.
var mu sync.Mutex
var wg sync.WaitGroup
sem := make(chan struct{}, 10) // Limit concurrent health checks
// Run health checks concurrently to prevent a single slow or timing-out
// datasource from blocking the health status of other datasources.
for _, t := range targets {
wg.Add(1)
go func(t target) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
Description
Optimized
Registry.HealthReportinpkg/proxy/registry.goto run health checks across all registered datasources concurrently using goroutines, async.WaitGroup, and async.Mutexfor map protection.Previously,
HealthReportexecuted health checks sequentially for every registered datasource with a 10-second timeout per check. When multiple datasources were configured and any failed or experienced high latency, health reporting blocked sequentially for O(N * latency).Type of change
How Has This Been Tested?
Checklist
make validatepasses (fmt + lint + test)