Skip to content

fix(backend): batch cleanup loop in listKubernetesObjects (ACM-35158)#6523

Closed
fxiang1 wants to merge 6 commits into
stolostron:mainfrom
fxiang1:fix/ACM-35158-batch-cleanup-loop
Closed

fix(backend): batch cleanup loop in listKubernetesObjects (ACM-35158)#6523
fxiang1 wants to merge 6 commits into
stolostron:mainfrom
fxiang1:fix/ACM-35158-batch-cleanup-loop

Conversation

@fxiang1

@fxiang1 fxiang1 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

📝 Summary

Ticket Summary (Title):
console-mce pod CrashLoopBackOff due to probe failure with large resource sets

Ticket Link:
https://issues.redhat.com/browse/ACM-35158

Type of Change:

  • 🐞 Bug Fix
  • ✨ Feature
  • 🔧 Refactor
  • 💸 Tech Debt
  • 🧪 Test-related
  • 📄 Docs

Root Cause Analysis

PR #6339 introduced batchPromiseAll to yield the event loop during resource caching and deletion, preventing liveness probe timeouts with large resource sets. However, the cleanup loop in listKubernetesObjects (backend/src/routes/events.ts:448-462) was left unbatched. This loop runs after every re-LIST (including watch reconnections) and has two compounding problems:

1. Event loop starvation (unbatched loop)

The for (const uid in cache) loop sequentially inflates every cached resource via inflateResource without ever yielding the event loop. With 22,913 Group resources, this means ~22K sequential decompressions with no setImmediate break, blocking the event loop for the entire duration. During this window, the backend cannot respond to liveness or readiness probe requests, causing Kubernetes to kill the pod.

2. O(n²) UID lookup

Inside the loop, items.find((resource) => resource.metadata.uid === uid) performs a linear scan of the entire items array for each cached entry. With 22K Groups, this results in ~525 million string comparisons — all on the main thread without yielding.

Timeline of the crash loop

  1. Pod starts, lists and watches 22,913 Groups
  2. Watch disconnects (timeout, 410 Gone, network blip) → triggers a re-LIST
  3. batchPromiseAll caches all items with event loop yields (PR ACM-35158 avoid blocking incoming requests [release-2.16] #6339 fix works here ✅)
  4. Cleanup loop begins — inflates all 22K cached entries + O(n²) find, without yielding
  5. Liveness probe times out → Kubelet kills the pod
  6. Pod restarts → re-lists 22K Groups → same crash → CrashLoopBackOff

What Changed

File: backend/src/routes/events.ts

  1. Replaced the for...in cleanup loop with batchPromiseAll — processes cache entries in batches of 100, calling setImmediate between batches to yield the event loop. This is consistent with the pattern established by PR ACM-35158 avoid blocking incoming requests [release-2.16] #6339 for the caching and deletion steps.

  2. Replaced O(n²) items.find() with a Set<string> — builds itemUids = new Set(items.map(item => item.metadata.uid)) upfront, then uses itemUids.has(uid) for O(1) lookups instead of linear scanning.


✅ Checklist

General

  • PR title follows the convention (e.g. ACM-12340 Fix bug with...)
  • Code builds and runs locally without errors
  • No console logs, commented-out code, or unnecessary files
  • All commits are meaningful and well-labeled
  • All new display strings are externalized for localization (English only)

If Bugfix

  • Root cause and fix summary are documented in the ticket (for future reference / errata)
  • Fix tested thoroughly and resolves the issue
  • Test(s) added to prevent regression

🗒️ Notes for Reviewers

This is a follow-up to PR #6339 which batched the cacheResource and deleteResource calls but missed the cleanup loop between them. The cleanup loop is the remaining code path that blocks the event loop with large resource sets.

The fix reuses the existing batchPromiseAll utility (introduced in #6339) — no new dependencies or utilities are added. The Set replacement for items.find() is a straightforward algorithmic improvement from O(n²) to O(n).

All existing backend tests pass (336/336). The batchPromiseAll utility is already covered by backend/test/lib/batch-promise-all.test.ts.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reconciliation between cached resources and the current Kubernetes state to prevent stale entries from persisting.
    • Resource removals are now more accurate by applying active field/label filters during cleanup.
    • Reduced race-related issues during concurrent deletions by adding safeguards to ensure the correct cached item is removed only when it still matches expectations.

The cleanup loop that removes stale resources after a re-LIST was not
using batchPromiseAll, so it blocked the event loop for the entire
duration—preventing liveness probe responses. Additionally, the
items.find() inside the loop was O(n²). With 22K+ Group resources
this caused probe timeouts and CrashLoopBackOff.

- Convert the for...in cleanup loop to use batchPromiseAll, yielding
  the event loop every 100 entries via setImmediate
- Replace O(n²) items.find() with a Set<string> for O(1) UID lookups

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: fxiang1 <fxiang@redhat.com>
@openshift-ci

openshift-ci Bot commented Jul 18, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: fxiang1

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5178b4b5-027c-45ab-83e7-e5497fb6301b

📥 Commits

Reviewing files that changed from the base of the PR and between 915c911 and c24b247.

📒 Files selected for processing (1)
  • backend/src/routes/events.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/routes/events.ts

📝 Walkthrough

Walkthrough

listAndWatch reconciles cached resources using a Kubernetes UID set and batched inflation/filter evaluation. deleteResource verifies expected cache entries before event cleanup and cache removal.

Changes

Cache reconciliation

Layer / File(s) Summary
Set-based stale-resource selection
backend/src/routes/events.ts
Builds a current UID set, batches inflation only for cached entries absent from it, applies field and label selectors, and passes matching stale entries to deletion.
Race-safe resource deletion
backend/src/routes/events.ts
deleteResource validates the expected cache entry before event cleanup and again before removing the cache entry.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and matches the backend cleanup-loop fix.
Description check ✅ Passed The description follows the template with summary, ticket link, RCA, checklist, and reviewer notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

backend/src/routes/events.ts

Parsing error: error TS5012: Cannot read file '/tsconfig.json': ENOENT: no such file or directory, open '/tsconfig.json'.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fxiang1
fxiang1 requested a review from KevinFCormier July 18, 2026 14:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/src/routes/events.ts (1)

447-447: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Use optional chaining for metadata.uid.

Although Kubernetes objects consistently contain metadata, the IResource interface types metadata as optional. Using optional chaining prevents potential runtime crashes and aligns with strict TypeScript checks.

♻️ Proposed refactor
-  const itemUids = new Set(items.map((item) => item.metadata.uid))
+  const itemUids = new Set(items.map((item) => item.metadata?.uid))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/routes/events.ts` at line 447, Update the itemUids construction
in the events route to access the UID through optional chaining on
item.metadata, preserving the existing Set and mapping behavior while safely
handling resources without metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/routes/events.ts`:
- Around line 451-452: Guard the cache lookup in the resource-processing flow
before accessing existing.compressed: when cache[uid] is undefined after the
asynchronous yield, skip that resource safely. Preserve the existing
inflateResource path for entries that remain present.

---

Nitpick comments:
In `@backend/src/routes/events.ts`:
- Line 447: Update the itemUids construction in the events route to access the
UID through optional chaining on item.metadata, preserving the existing Set and
mapping behavior while safely handling resources without metadata.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ef97dbed-b07e-4962-b55c-43dca39ec85a

📥 Commits

Reviewing files that changed from the base of the PR and between 5e849d7 and 6a1505d.

📒 Files selected for processing (1)
  • backend/src/routes/events.ts

Comment thread backend/src/routes/events.ts Outdated
fxiang1 and others added 2 commits July 18, 2026 10:28
Cache entries can be deleted by a concurrent watch event during the
setImmediate yield between batches. Skip UIDs whose cache entry was
removed to prevent a TypeError on undefined.compressed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: fxiang1 <fxiang@redhat.com>
Signed-off-by: fxiang1 <fxiang@redhat.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
backend/src/routes/events.ts (1)

869-869: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant cache entry check.

This check is redundant and can be safely removed. If expectedCacheEntry is provided, line 860 ensures existing is truthy, so the block at line 863 is guaranteed to execute. Since no asynchronous operations occur between the identical check at line 865 and here, cache[uid] cannot change in the interim.

♻️ Proposed refactor
-  if (expectedCacheEntry && cache[uid] !== expectedCacheEntry) return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/routes/events.ts` at line 869, Remove the redundant
`expectedCacheEntry` versus `cache[uid]` comparison from the surrounding
cache-update logic in `events.ts`; retain the earlier validation and the rest of
the block unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/src/routes/events.ts`:
- Line 869: Remove the redundant `expectedCacheEntry` versus `cache[uid]`
comparison from the surrounding cache-update logic in `events.ts`; retain the
earlier validation and the rest of the block unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ef356164-f556-46c0-b77a-26e276bbe04f

📥 Commits

Reviewing files that changed from the base of the PR and between 6a1505d and ba57534.

📒 Files selected for processing (1)
  • backend/src/routes/events.ts

fxiang1 added 2 commits July 19, 2026 18:02
Signed-off-by: fxiang1 <fxiang@redhat.com>
Signed-off-by: fxiang1 <fxiang@redhat.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
64.5% Coverage on New Code (required ≥ 70%)

See analysis details on SonarQube Cloud

Signed-off-by: fxiang1 <fxiang@redhat.com>
@openshift-ci

openshift-ci Bot commented Jul 20, 2026

Copy link
Copy Markdown

@fxiang1: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/unit-tests-sonarcloud c24b247 link true /test unit-tests-sonarcloud

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@coderabbitai coderabbitai Bot mentioned this pull request Jul 20, 2026
6 tasks
@fxiang1 fxiang1 closed this Jul 22, 2026
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.

1 participant