Skip to content

Commit 360d483

Browse files
TerryHowescottrigby
authored andcommitted
fix(kube): prevent spurious early exit in WaitForDelete during informer sync
During informer initialization there is a brief window where watched resources appear as Unknown before their real statuses are delivered. The statusObserver skips Unknown resources when waiting for deletion (they may have been deleted before the watch started), but if *all* resources are in that transient Unknown state the skipped-resource list is empty. AggregateStatus on an empty slice returns the desired status, causing cancel() to be called immediately — before any real status event has arrived. Guard against this by tracking the count of Unknown-skipped resources. When every resource was Unknown-skipped and none have a definitive status yet, defer the early-cancel decision until at least one resource reports a real status. This preserves the correct behaviour for resources that were genuinely deleted before the watch started (they eventually receive a NotFound or stay Unknown, and the aggregate succeeds), while fixing the race for resources that are transiently Unknown at startup. Also tighten the ctx.Err() check in waitForDelete: only append a deadline error when there are resource-specific errors to accompany it. A timeout while all resources are Unknown or NotFound is not itself an error — the resources are in an acceptable state for a delete wait. Fixes: TestStatusWaitForDelete/error_when_not_all_objects_are_deleted Signed-off-by: Terry Howe <[email protected]> (cherry picked from commit 4e24ee4)
1 parent 7651edf commit 360d483

1 file changed

Lines changed: 14 additions & 1 deletion

File tree

pkg/kube/statuswait.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL
160160
errs = append(errs, fmt.Errorf("resource %s/%s/%s still exists. status: %s, message: %s",
161161
rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message))
162162
}
163-
if err := ctx.Err(); err != nil {
163+
// Only include a deadline error when there are also resource-specific errors.
164+
// If all resources are Unknown or NotFound (e.g. deleted before the watch started),
165+
// a timeout is not itself an error for WaitForDelete.
166+
if err := ctx.Err(); err != nil && len(errs) > 0 {
164167
errs = append(errs, err)
165168
}
166169
if len(errs) > 0 {
@@ -234,13 +237,15 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl
234237
return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) {
235238
var rss []*event.ResourceStatus
236239
var nonDesiredResources []*event.ResourceStatus
240+
var unknownSkipped int
237241
for _, rs := range statusCollector.ResourceStatuses {
238242
if rs == nil {
239243
continue
240244
}
241245
// If a resource is already deleted before waiting has started, it will show as unknown.
242246
// This check ensures we don't wait forever for a resource that is already deleted.
243247
if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus {
248+
unknownSkipped++
244249
continue
245250
}
246251
// Failed is a terminal state. This check ensures we don't wait forever for a resource
@@ -254,6 +259,14 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl
254259
}
255260
}
256261

262+
// During informer initialization there is a brief window where existing resources
263+
// appear as Unknown before their real status is delivered. If every resource was
264+
// skipped as Unknown, we cannot yet distinguish "all deleted" from "not yet synced",
265+
// so hold off on the early-cancel to avoid a spurious success or premature exit.
266+
if unknownSkipped > 0 && len(rss) == 0 {
267+
return
268+
}
269+
257270
if aggregator.AggregateStatus(rss, desired) == desired {
258271
logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss))
259272
cancel()

0 commit comments

Comments
 (0)