Skip to content

EventFilterWindow silently drops foreign spec changes coalesced with own-write echo #3455

Description

@SamBarker

Supersedes #3445, which investigated the wrong path (shared InformerEventSource). This issue identifies the actual root cause in EventFilterWindow.

Summary

When a controller uses SSA (Server-Side Apply) and a foreign actor concurrently changes the same resource's spec, the EventFilterWindow can silently drop the foreign change. The controller never re-reconciles and is stuck until maxReconciliationInterval (default 10 hours).

Mechanism

  1. Controller reconciles resource at gen=1
  2. Foreign actor changes spec → gen=2 → new RV on API server
  3. Controller's SSA patchResource merges on top → API returns RV N (gen=2, because SSA merged with the already-modified resource)
  4. The fabric8 informer coalesces both watch events (same resource key) into a single event at RV N
  5. EventFilterWindow records own write RV=N, sees relatedEvents={N} == ownResourceVersions={N}drops the event (EventFilterWindow.java line 182–186)
  6. Controller finishes gen=1 reconciliation successfully, no pending events → rescheduled for maxReconciliationInterval
  7. Resource has gen=2 but observedGeneration=1permanently stale until the 10-hour timer fires

Why SSA is specifically affected

With non-SSA updates (full PUT), a concurrent foreign change causes a 409 Conflict, forcing the controller to retry with the current resource state. With SSA, patches succeed even when the resource was concurrently modified — different field managers don't conflict. The API response RV carries both changes, but EventFilterWindow treats it as a pure own-write echo.

Why this didn't happen before 5.3.x

The EventFilterWindow buffering mechanism was introduced in 5.3.x. It captures events that arrive during the API call window (startEventFilteringModifydoneEventFilterModify). When the informer coalesces the foreign change event with the own-write echo into a single event, the window sees only one event at the own-write RV and drops it.

In 5.2.x, the simpler TemporaryResourceCache / canSkipEvent approach checked events as they arrived. With different timing characteristics (no synchronized onAddOrUpdate, no buffering window), the foreign event was more likely to arrive and be processed before the own-write RV was recorded in the cache.

Observed in production

This was observed in a Kroxylicious operator GHA failure when upgrading from JOSDK 5.2.5 to 5.3.5. A dependent controller sees the stale resource (gen=2, observedGeneration=1), exhausts retries, and also goes to sleep for maxReconciliationInterval.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions