Skip to content

fix(scheduler): stop leaked test informers to prevent data races - #2399

Closed
v0idheaven wants to merge 1 commit into
Project-HAMi:masterfrom
v0idheaven:fix/leaked-test-informers-data-race
Closed

fix(scheduler): stop leaked test informers to prevent data races#2399
v0idheaven wants to merge 1 commit into
Project-HAMi:masterfrom
v0idheaven:fix/leaked-test-informers-data-race

Conversation

@v0idheaven

@v0idheaven v0idheaven commented Aug 6, 2026

Copy link
Copy Markdown

What this PR does

Fixes #2389

Tests that start informers via informerFactory.Start(s.stopCh) but never close stopCh leave goroutines running after the test completes. These leaked informer goroutines keep dispatching pod events into onAddPod, which reads device.SupportDevices a package-level map[string]string with no mutex, causing unsynchronised concurrent map access and intermittent -race failures.

The root cause:

  • The write is in the test's own setup: device.SupportDevices["test"] = "hami.io/test-allocated"
  • The read is a shared informer goroutine still running from an earlier test
  • Because tests call informerFactory.Start(s.stopCh) and never close stopCh, the listener goroutines outlive the test that created them and keep dispatching pod events into onAddPod for the rest of the binary's life

Changes

Added t.Cleanup(func() { close(s.stopCh) }) to every test that starts an informer without a corresponding stop:

  • Test_getPodUsage
  • Test_Filter
  • TestRegisterSkipsCleanupForUntrackedVendor
  • Test_ResourceQuota
  • Test_Bind_DelPodOnGetPodFailure
  • Test_Bind_DelPodOnGetNodeFailure

Also removed redundant s.stopCh = make(chan struct{}) reassignments in Test_RegisterFromNodeAnnotations and Test_RegisterFromNodeAnnotations_NIL, NewScheduler() already initialises the channel, so recreating it orphans the original.

How to verify

for i in $(seq 1 12); do go test ./pkg/scheduler/ -count 1 -race; done

Should pass consistently. Before this fix it fails on attempt 3-4 out of 12 on average (reproduced with go test ./pkg/scheduler/ --race on master).

Summary by CodeRabbit

  • Tests
    • Improved automatic cleanup in scheduler tests.
    • Enhanced test reliability by consistently shutting down informer resources and scheduler stop channels.
    • Simplified cleanup handling for bind lock retry scenarios.

@hami-robot
hami-robot Bot requested review from FouoF and mesutoezdil August 6, 2026 07:21
@hami-robot

hami-robot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Welcome @v0idheaven! It looks like this is your first PR to Project-HAMi/HAMi 🎉

@github-actions github-actions Bot added the kind/bug Something isn't working label Aug 6, 2026
@hami-robot hami-robot Bot added the size/XS label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cf901e3d-9ccf-41e5-8266-7252a58d567a

📥 Commits

Reviewing files that changed from the base of the PR and between c35cb50 and 02e4b90.

📒 Files selected for processing (1)
  • pkg/scheduler/scheduler_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/scheduler/scheduler_test.go

📝 Walkthrough

Walkthrough

Scheduler tests now use automatic cleanup to stop schedulers and informer factories. Several tests remove manual stop-channel setup. Bind-lock retry cleanup restores shared state and stops the scheduler.

Changes

Scheduler test cleanup

Layer / File(s) Summary
Register automatic test shutdown
pkg/scheduler/scheduler_test.go
Pod usage, filtering, quota, vendor registration, and bind tests register scheduler cleanup. Annotation and vendor tests remove redundant stop-channel setup. Bind-lock retry cleanup restores configuration and device state, then stops the scheduler and informer factory.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: fouof

Poem

A rabbit checks each test at night,
Stops schedulers before moonlight.
Informers rest and channels close,
Clean test paths bloom like a rose.
Hop, hop, all is right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the scheduler test informer leak fix and its race-prevention purpose.
Linked Issues check ✅ Passed The changes stop and drain informer goroutines in affected tests, directly addressing the race caused by leaked informers in issue #2389.
Out of Scope Changes check ✅ Passed All changes are limited to scheduler test cleanup and removal of redundant stop-channel setup, which match the stated objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

🤖 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 `@pkg/scheduler/scheduler_test.go`:
- Line 368: Update the informer test cleanups in pkg/scheduler/scheduler_test.go
at lines 368, 480, 1312, 1382, 2098, and 2142 so each cleanup closes s.stopCh
and waits for all informer/event-handler goroutines to finish before returning.
Ensure handlers cannot be triggered after shutdown begins when they cannot exit
promptly.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fc5448f-5272-4ec6-9b22-d6b825e2b4de

📥 Commits

Reviewing files that changed from the base of the PR and between 96207fc and 695bdcc.

📒 Files selected for processing (1)
  • pkg/scheduler/scheduler_test.go

Comment thread pkg/scheduler/scheduler_test.go Outdated
v0idheaven added a commit to v0idheaven/HAMi that referenced this pull request Aug 6, 2026
Replace t.Cleanup(func() { close(s.stopCh) }) with t.Cleanup(informerFactory.Shutdown).

Closing stopCh signals goroutines to stop but does not wait for them to exit. informerFactory.Shutdown() both signals the stop and blocks until all event-handler goroutines have returned, ensuring no informer goroutine outlives the test that started it.

Also add informerFactory.Shutdown() to setupBindLockRetryTest's existing cleanup func alongside close(s.stopCh) for the same reason.

Addresses review feedback on Project-HAMi#2399.
@hami-robot hami-robot Bot added size/S and removed size/XS labels Aug 6, 2026
@v0idheaven

Copy link
Copy Markdown
Author

Updated per review feedback from @coderabbitai.

Changed from .Cleanup(func() { close(s.stopCh) }) to .Cleanup(informerFactory.Shutdown).

Why this is better:

  • close(s.stopCh) only signals goroutines to stop but returns immediately
  • informerFactory.Shutdown() waits until all event-handler goroutines have actually exited

This ensures the informer goroutines are fully drained before the test function returns, preventing any possibility of them outliving the test and racing against subsequent tests.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git diff --unified=80 origin/"$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')" -- pkg/scheduler/scheduler_test.go || true
printf '\n--- Relevant factory, startup, cleanup, and stop-channel sites ---\n'
rg -n -C 5 'NewSharedInformerFactory|\.Start\(|\.Shutdown\(|t\.Cleanup|stopCh' pkg/scheduler/scheduler_test.go

Length of output: 47556


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Kubernetes dependency versions ---'
rg -n -C 2 'k8s.io/client-go|k8s.io/apimachinery' go.mod go.sum

printf '%s\n' '--- SharedInformerFactory Shutdown implementation ---'
module_dir="$(go env GOPATH 2>/dev/null)/pkg/mod"
if [ -d "$module_dir" ]; then
  rg -n -U -C 12 'func \(.*\*sharedInformerFactory\) Shutdown\(\) \{' "$module_dir/k8s.io/client-go"* 2>/dev/null || true
else
  echo "Go module cache is unavailable."
fi

Length of output: 2041


🤖 web_search executed:

k8s.io/client-go v0.36.3 sharedInformerFactory Shutdown implementation github

Length of output: 15243


@v0idheaven: informerFactory.Shutdown() waits for the informer goroutines, but it does not close the channel passed to informerFactory.Start(s.stopCh).

With the current cleanup, the six affected tests can block in Shutdown() because s.stopCh remains open. client-go documents that the start channel must be closed before, or while, Shutdown() waits.

Use both operations in this order:

t.Cleanup(func() {
	close(s.stopCh)
	informerFactory.Shutdown()
})

This signals the informers to exit and waits until they have exited. The existing setupBindLockRetryTest cleanup follows this required ordering.

🐇🔍


✏️ Learnings added
Learnt from: v0idheaven
URL: https://github.com/Project-HAMi/HAMi/pull/2399

Timestamp: 2026-08-06T07:33:52.222Z
Learning: In `pkg/scheduler/scheduler_test.go`, factories started with `informerFactory.Start(s.stopCh)` must close `s.stopCh` before calling `informerFactory.Shutdown()`. With `k8s.io/client-go v0.36.3`, `SharedInformerFactory.Shutdown()` only waits for informer goroutines. It does not close the stop channel passed to `Start`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@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: 2

🤖 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 `@pkg/scheduler/scheduler_test.go`:
- Line 374: Update each test cleanup registering informerFactory.Shutdown to use
a closure that closes s.stopCh before calling informerFactory.Shutdown. Apply
this at all corresponding cleanup sites, preserving the existing shutdown
behavior while ensuring informer goroutines can exit.
- Line 2220: In setupBindLockRetryTest, declare informerFactory before defining
the cleanup closure so cleanup’s informerFactory.Shutdown() reference is in
scope; preserve the existing cleanup behavior.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f7a9fc47-336d-4fc5-9f67-b637f71f1bc6

📥 Commits

Reviewing files that changed from the base of the PR and between 695bdcc and 39e0d0c.

📒 Files selected for processing (1)
  • pkg/scheduler/scheduler_test.go

Comment thread pkg/scheduler/scheduler_test.go Outdated
Comment thread pkg/scheduler/scheduler_test.go Outdated
@Eshiv-Pandey

Copy link
Copy Markdown
Contributor

@v0idheaven wlcm to the community. pls sign off your commits dco check is failing.

@Eshiv-Pandey Eshiv-Pandey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this still regresses. Shutdown() only waits for goroutines it doesnt stop them. The stop signal is close(s.stopCh), which these six tests never call, so Shutdown() blocks until the test times out. Like setupBindLockRetryTest, use:

t.Cleanup(func() {
close(s.stopCh)
informerFactory.Shutdown()
})

a 12x-race loop should hang on the first run with the current changes.

Tests that call informerFactory.Start(s.stopCh) without closing stopCh
leave informer goroutines running after the test ends. These goroutines
keep dispatching pod events into onAddPod, which reads device.SupportDevices
— a package-level map with no mutex — causing data races under -race.

Add t.Cleanup(s.Stop) to each test that starts an informer factory but
never stops it. s.Stop() closes s.stopCh, which is the stop signal the
informer goroutines watch, causing them to exit cleanly.

Also remove redundant s.stopCh reassignments in
Test_RegisterFromNodeAnnotations and Test_RegisterFromNodeAnnotations_NIL
since NewScheduler() already initialises the channel.

In setupBindLockRetryTest, replace the manual close(s.stopCh) in the
existing cleanup closure with s.Stop() for consistency.

Affected tests:
- Test_getPodUsage
- Test_Filter
- TestRegisterSkipsCleanupForUntrackedVendor
- Test_ResourceQuota
- Test_Bind_DelPodOnGetPodFailure
- Test_Bind_DelPodOnGetNodeFailure
- setupBindLockRetryTest

Fixes Project-HAMi#2389

Signed-off-by: v0idheaven <dahiyavarun2007@gmail.com>
@v0idheaven
v0idheaven force-pushed the fix/leaked-test-informers-data-race branch from 85e959a to 02e4b90 Compare August 6, 2026 09:47
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@v0idheaven

v0idheaven commented Aug 6, 2026

Copy link
Copy Markdown
Author

Thanks for the review @Eshiv-Pandey! Updated the fix:

  • Replaced all informerFactory.Shutdown() usages with .Cleanup(s.Stop) s.Stop() calls close(s.stopCh) which is the actual stop signal the informer goroutines watch, so they exit cleanly without any deadlock risk
  • Also squashed all commits into one and added the DCO sign-off (Signed-off-by)

Let me know if anything else needs changing!

@archlitchi archlitchi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

/lgtm

@hami-robot

hami-robot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: archlitchi, v0idheaven

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

@hami-robot hami-robot Bot added the approved label Aug 6, 2026
@archlitchi

Copy link
Copy Markdown
Member

please sync with master to pass the CI

@archlitchi

Copy link
Copy Markdown
Member

/lgtm cancel

@hami-robot hami-robot Bot removed the lgtm label Aug 7, 2026
*/
func Test_Filter(t *testing.T) {
s := NewScheduler()
t.Cleanup(s.Stop)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@v0idheaven I test this PR with the following commands

go test ./pkg/scheduler/ \
      -run '^(Test_Filter|Test_onAddPod_BadDeviceAnnotation)$' \
      -count=1 -race

And here comes an error

E0809 23:37:49.760989  973437 scheduler.go:162] "failed to decode pod devices" err="pod annotation format error, missing fields, do not use nodeName in task spec" pod="default/bad-anno-pod"
--- FAIL: Test_onAddPod_BadDeviceAnnotation (0.00s)
    testing.go:1712: race detected during execution of test

Is this an expected error?

@mesutoezdil

Copy link
Copy Markdown
Contributor

You can view the relevant rule here.
https://github.com/Project-HAMi/HAMi/blob/master/CONTRIBUTING.md#contribution-gates
"4. Review replies. The reply you post must be written by you and must address the specific point raised. Verbatim or canned AI replies, or replies that do not engage the comment, lead to the PR being closed."

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.

Flaky: data race on global device.SupportDevices between leaked test informers and Test_onAddPod_BadDeviceAnnotation

5 participants