Skip to content

Fix device plugin recovery from Unhealthy state - #2201

Closed
itsvishalyadav wants to merge 3 commits into
Project-HAMi:masterfrom
itsvishalyadav:fix-unhealthy-recovery
Closed

Fix device plugin recovery from Unhealthy state#2201
itsvishalyadav wants to merge 3 commits into
Project-HAMi:masterfrom
itsvishalyadav:fix-unhealthy-recovery

Conversation

@itsvishalyadav

@itsvishalyadav itsvishalyadav commented Jul 29, 2026

Copy link
Copy Markdown

What type of PR is this?

/kind bug
/kind cleanup

What this PR does / why we need it:
This PR refactors the device plugin's health signaling mechanism to support bidirectional health reporting (both Healthy and Unhealthy events) as groundwork for recovering devices from an Unhealthy state.

Note: Initially, this PR attempted to implement an automatic recovery mechanism by polling GetMemoryInfo every 5 seconds. However, as noted in review by @mesutoezdil, GetMemoryInfo can still succeed on a GPU that just hit an XID fault, which would unsafely hide real hardware faults. Therefore, the automatic polling mechanism has been reverted. This PR now focuses purely on the API rename and mock fixes to lay the foundation for a safe (likely manual/endpoint-driven) recovery mechanism in the future.

AI Assistance Notice:

I consulted ChatGPT to understand the codebase and help draft the initial attempt at this feature.

Which issue(s) this PR fixes:
Fixes #2200

Special notes for your reviewer:
N/A

Does this PR introduce a user-facing change?:
No

Summary by CodeRabbit

  • Bug Fixes
    • Improved GPU health monitoring so devices affected by placement failures, monitoring errors, or critical hardware events are reported with the appropriate health status.
    • Ensured health state updates are handled consistently across supported device management implementations.
    • Improved reliability when health information cannot be determined by marking affected devices appropriately.

Signed-off-by: itsvishalyadav <vishalyadav87695@gmail.com>
@hami-robot

hami-robot Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: itsvishalyadav
Once this PR has been reviewed and has the lgtm label, please assign shouren for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found 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 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Welcome @itsvishalyadav! 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 Jul 29, 2026
@hami-robot hami-robot Bot added the size/M label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 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: 2b5bc893-f2f1-4de9-945d-7da34d82423f

📥 Commits

Reviewing files that changed from the base of the PR and between e831337 and 4f39770.

📒 Files selected for processing (5)
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/health.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/nvml_manager.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/rm.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/rm_mock.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/tegra_manager.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/rm.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/tegra_manager.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/nvml_manager.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/health.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/rm_mock.go

📝 Walkthrough

Walkthrough

The NVIDIA resource manager renames its health-reporting channel from unhealthy to health across interfaces, implementations, mocks, and NVML health-check failure paths.

Changes

NVIDIA health channel

Layer / File(s) Summary
Health channel contract and wiring
pkg/device-plugin/nvidiadevice/nvinternal/rm/rm.go, pkg/device-plugin/nvidiadevice/nvinternal/rm/*manager.go, pkg/device-plugin/nvidiadevice/nvinternal/rm/rm_mock.go
The CheckHealth interface, implementations, mock call records, and NVML manager forwarding consistently use the health channel.
Health event emission
pkg/device-plugin/nvidiadevice/nvinternal/rm/health.go
Device updates from placement failures, NVML wait errors, UUID-resolution failures, and XidCriticalError handling are sent through the health channel.

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

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: dsfans2014

Poem

A rabbit hops through channels bright,
Renaming health from wrong to right.
GPUs send their status clear,
NVML signals reach the ear.
“Healthy paths!” we cheer tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR only renames health channels and adds groundwork; it does not implement periodic re-evaluation or auto-transition from Unhealthy to Healthy. Add logic to periodically recheck unhealthy devices and restore them to Healthy when recovery is confirmed.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the PR's focus on device plugin unhealthy-state recovery.
Out of Scope Changes check ✅ Passed The changes stay within health-signaling and mock/interface updates needed for the recovery work.
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: 4

🤖 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/device-plugin/nvidiadevice/nvinternal/rm/health.go`:
- Around line 127-133: Move the unhealthyDevices map initialization before the
device registration loop, and when registration marks a device Unhealthy in the
setup path, add it to this map. Preserve the existing recovery and timeout
polling behavior so setup-time failures can later transition back to Healthy.
- Around line 60-61: Update nvmlResourceManager.checkHealth so every device
health result send uses the renamed health channel parameter instead of the
undefined unhealthy channel, including the sends at the referenced branches,
while preserving the existing send conditions and values.

In `@pkg/device-plugin/nvidiadevice/nvinternal/rm/nvml_manager.go`:
- Line 116: Update the comment for CheckHealth to state that the health channel
reports device health updates or transitions, including both healthy and
unhealthy devices, instead of referring only to unhealthy devices.

In `@pkg/device-plugin/nvidiadevice/nvinternal/rm/rm_mock.go`:
- Around line 139-144: Update the mock call-record type used by
mock.calls.CheckHealth to consistently use the Health field instead of
Unhealthy, including its stored field, accessor return type, and related
comments; ensure the append involving callInfo compiles without changing the
surrounding behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aca90f67-cc17-40ff-ae74-38234aa1c668

📥 Commits

Reviewing files that changed from the base of the PR and between e831337 and a4e86bf.

📒 Files selected for processing (6)
  • pkg/device-plugin/nvidiadevice/nvinternal/plugin/server.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/health.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/nvml_manager.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/rm.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/rm_mock.go
  • pkg/device-plugin/nvidiadevice/nvinternal/rm/tegra_manager.go

Comment thread pkg/device-plugin/nvidiadevice/nvinternal/rm/health.go
Comment thread pkg/device-plugin/nvidiadevice/nvinternal/rm/health.go Outdated
Comment thread pkg/device-plugin/nvidiadevice/nvinternal/rm/nvml_manager.go Outdated
Comment thread pkg/device-plugin/nvidiadevice/nvinternal/rm/rm_mock.go
Signed-off-by: itsvishalyadav <vishalyadav87695@gmail.com>
@itsvishalyadav

itsvishalyadav commented Jul 29, 2026

Copy link
Copy Markdown
Author

Hi @DSFans2014, @FouoF and @ouyangluwei163
Could you please take a look at this PR when you have a moment? It introduces a mechanism to automatically recover devices from the Unhealthy state without requiring a full device plugin restart, addressing a known \FIXME\ in the code. I've also addressed the initial feedback from CodeRabbit. Let me know if there's anything else you'd like me to change. Thank you!

@mesutoezdil mesutoezdil 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.

could you add a unit test for the recovery path? nothing covers it rn.

and no ai assistance disclosure is present. if any ai tool was used, it must be disclosed per CONTRIBUTING.md: https://github.com/Project-HAMi/HAMi/blob/master/CONTRIBUTING.md#ai-assistance-notice

// CheckHealth performs health checks on a set of devices, writing to the 'unhealthy' channel with any unhealthy devices
func (r *nvmlResourceManager) checkHealth(stop <-chan interface{}, devices Devices, unhealthy chan<- *Device, disableNVML <-chan bool) error {
// CheckHealth performs health checks on a set of devices, writing to the 'health' channel with any unhealthy devices
func (r *nvmlResourceManager) checkHealth(stop <-chan interface{}, devices Devices, health chan<- *Device, disableNVML <-chan bool) error {

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.

the rename stops at the sig, l.102/112/119 still send to unhealthy so this pkg doesn't compile.

_ = eventSet.Free()
}()

unhealthyDevices := make(map[string]*Device)

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.

devices marked unhealthy in the registration loop below never land in this map, so they can never recover.

if ret != nvml.SUCCESS {
klog.Infof("Marking device %v as unhealthy: %v", d.ID, ret)
unhealthy <- d
d.Health = kubeletdevicepluginv1beta1.Unhealthy

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.

we now write d.Health from the health goroutine while listandwatch reads the same *Device through apiDevices(), that's a data race afaict.

e, ret := eventSet.Wait(5000)
if ret == nvml.ERROR_TIMEOUT {
for id, d := range unhealthyDevices {
uuid, _, _, err := r.getDevicePlacement(d)

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.

for a mig device getDevicePlacement gives back the parent gpu uuid, so the probe below says nothing abt the mig instance itself.

if ret != nvml.SUCCESS {
continue
}
_, ret = gpu.GetMemoryInfo()

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.

GetMemoryInfo still succeeds on a gpu that just hit an xid, so imo this flips nearly every device back to healthy after 5s and we lose the signal for real faults.

Stop <-chan interface{}
Health chan<- *Device
} {
var calls []struct {

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 local still says Unhealthy while the return type above says Health, so it doesn't compile.

Signed-off-by: itsvishalyadav <vishalyadav87695@gmail.com>
@itsvishalyadav
itsvishalyadav force-pushed the fix-unhealthy-recovery branch from 382df9b to 4f39770 Compare July 29, 2026 18:22
@itsvishalyadav

Copy link
Copy Markdown
Author

Thank you for the detailed review @mesutoezdil! You raised excellent points. I have pushed a new commit that addresses your feedback:

  1. Flawed Recovery Signal & MIG Support: You are entirely right that \GetMemoryInfo\ is not a safe signal for recovery from an XID fault since it can still succeed, meaning we would blindly restore faulty devices. Since NVML does not provide a robust Boolean flag for 'is recovered' without a workload test, I have reverted the automatic 5-second polling recovery mechanism completely to avoid hiding real hardware faults.
  2. Data Race: By reverting the automatic recovery, we no longer mutate \d.Health\ asynchronously inside \health.go's goroutine. The write to \d.Health\ now happens synchronously back inside \ListAndWatch, fixing the data race you correctly identified.
  3. Mock Compilation: The un-updated mock field names have been fixed.
  4. AI Assistance Disclosure: Added to the PR description per the guidelines.

This PR now serves purely as a refactoring groundwork to change the internal channel from \unhealthy\ to \health\ (supporting bidirectional signaling). Since automatic recovery via polling is unsafe with our current signals, what is the project's preferred approach to resolving the original \FIXME? Should we provide an API endpoint or SIGHUP signal to allow cluster admins to manually clear the Unhealthy state without a full pod restart?

@mesutoezdil

Copy link
Copy Markdown
Contributor

Thank you for the detailed review @mesutoezdil! You raised excellent points. I have pushed a new commit that addresses your feedback:

  1. Flawed Recovery Signal & MIG Support: You are entirely right that \GetMemoryInfo\ is not a safe signal for recovery from an XID fault since it can still succeed, meaning we would blindly restore faulty devices. Since NVML does not provide a robust Boolean flag for 'is recovered' without a workload test, I have reverted the automatic 5-second polling recovery mechanism completely to avoid hiding real hardware faults.
  2. Data Race: By reverting the automatic recovery, we no longer mutate \d.Health\ asynchronously inside \health.go's goroutine. The write to \d.Health\ now happens synchronously back inside \ListAndWatch, fixing the data race you correctly identified.
  3. Mock Compilation: The un-updated mock field names have been fixed.
  4. AI Assistance Disclosure: Added to the PR description per the guidelines.

This PR now serves purely as a refactoring groundwork to change the internal channel from \unhealthy\ to \health\ (supporting bidirectional signaling). Since automatic recovery via polling is unsafe with our current signals, what is the project's preferred approach to resolving the original \FIXME? Should we provide an API endpoint or SIGHUP signal to allow cluster admins to manually clear the Unhealthy state without a full pod restart?

Reminder: Answers must be written by human being. 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."

@itsvishalyadav

Copy link
Copy Markdown
Author

Thank you for the detailed review @mesutoezdil! You raised excellent points. I have pushed a new commit that addresses your feedback:

  1. Flawed Recovery Signal & MIG Support: You are entirely right that \GetMemoryInfo\ is not a safe signal for recovery from an XID fault since it can still succeed, meaning we would blindly restore faulty devices. Since NVML does not provide a robust Boolean flag for 'is recovered' without a workload test, I have reverted the automatic 5-second polling recovery mechanism completely to avoid hiding real hardware faults.
  2. Data Race: By reverting the automatic recovery, we no longer mutate \d.Health\ asynchronously inside \health.go's goroutine. The write to \d.Health\ now happens synchronously back inside \ListAndWatch, fixing the data race you correctly identified.
  3. Mock Compilation: The un-updated mock field names have been fixed.
  4. AI Assistance Disclosure: Added to the PR description per the guidelines.

This PR now serves purely as a refactoring groundwork to change the internal channel from \unhealthy\ to \health\ (supporting bidirectional signaling). Since automatic recovery via polling is unsafe with our current signals, what is the project's preferred approach to resolving the original \FIXME? Should we provide an API endpoint or SIGHUP signal to allow cluster admins to manually clear the Unhealthy state without a full pod restart?

Reminder: Answers must be written by human being. 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."

My bad for the previous comment.

I have made the requested changes in a new commit. Since the condition on GetMemoryInfo was not sufficient as a condition for the GPU recovery from XID error (since it might conceal a problem with the hardware), I have completely deleted the polling mechanism, thus solving the data race problem with ListAndWatch. Additionally, I have fixed the mock compilation errors.

The safe polling scheme will not work for the recovery of the devices, so do you think it would make sense to tackle the original FIXME? Can we proceed with the idea that I expose an API or a signal for the administrators of the cluster to mark the device as healthy without having to restart the pod?

@FouoF

FouoF commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@itsvishalyadav Actually these codes are forked form https://github.com/NVIDIA/k8s-device-plugin and they will be synced periodically. We'd better not change them if not necessary to avoid potential conflict.

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.

Device Plugin: Devices cannot recover from Unhealthy state

3 participants