What happened?
kubernetes/kubernetes#141471 describes a real race: when a pod using a DRA claim is force-deleted (kubectl delete pod --grace-period=0 --force, or the identical mechanism podgc uses automatically on node loss), the control plane can deallocate and reallocate the claim's device to a new claim before the original pod's container has actually stopped on the node.
Reproduced twice against this driver (v0.4.0):
Once with two manually-created pods and once with a single-replica Deployment where the ReplicaSet controller recreated the replacement entirely on its own.
Both were reproduced from a single force-delete, no exotic setup:
- Manual-pods variant: 1.52s driver-level double-booking window, ≥0.57s confirmed container-level overlap.
- Deployment variant (automatic self-healing, no manual intervention): 2.05s driver-level double-booking window, ≥1.61s confirmed container-level overlap. The ReplicaSet controller created the replacement in 79ms, faster than kubelet finished tearing down the original.
In both cases, NodePrepareResources for the new claim succeeded immediately and unconditionally, while the original claim's container was still confirmed Running on the same device. There was no rejection, error, retry, or queuing from the driver.
Where this lives in the code
DeviceState.Prepare() → prepareDevices() → computeDeviceConfig() (cmd/dra-example-kubeletplugin/state.go, current main as of 2026-09-01) only checks that a requested device exists in s.allocatable, the driver's static set of known devices.
Nothing anywhere in this path cross-checks checkpoint.PreparedClaims for whether a different claim UID already holds that same device before accepting a new Prepare() call for it.
Confirmed this gap is still present after the recent checkpoint-restore fix (#259) — that fix touched the already-restored-from-checkpoint branch only, not this path.
Why this looks like the intended contract
staging/src/k8s.io/dynamic-resource-allocation/kubeletplugin/draplugin.go's godoc (recently extended by kubernetes/kubernetes#141479, merged 2026-08-31) now explicitly documents this exact scenario:
Real-world scenarios in which this can occur: Pod force-deletion... Node loss. In these cases,
the control-plane record can be removed immediately while node-level teardown runs
asynchronously... Because Kubernetes cannot guarantee exclusive device handoff in this scenario,
the DRA driver must enforce idempotence and device exclusivity to prevent concurrent
preparation.
As the reference implementation most third-party driver authors look to for how to implement this contract, this driver currently provides no example of that enforcement.
What did you expect to happen?
Some form of rejection, queuing, or wait when a Prepare() call arrives for a device the driver's own checkpoint still shows as held by a different, not-yet-unprepared claim, rather than silently succeeding for both.
Anything else we need to know?
None
What happened?
kubernetes/kubernetes#141471describes a real race: when a pod using a DRA claim is force-deleted (kubectl delete pod --grace-period=0 --force, or the identical mechanismpodgcuses automatically on node loss), the control plane can deallocate and reallocate the claim's device to a new claim before the original pod's container has actually stopped on the node.Reproduced twice against this driver (v0.4.0):
Once with two manually-created pods and once with a single-replica Deployment where the ReplicaSet controller recreated the replacement entirely on its own.
Both were reproduced from a single force-delete, no exotic setup:
In both cases,
NodePrepareResourcesfor the new claim succeeded immediately and unconditionally, while the original claim's container was still confirmedRunningon the same device. There was no rejection, error, retry, or queuing from the driver.Where this lives in the code
DeviceState.Prepare()→prepareDevices()→computeDeviceConfig()(cmd/dra-example-kubeletplugin/state.go, currentmainas of 2026-09-01) only checks that a requested device exists ins.allocatable, the driver's static set of known devices.Nothing anywhere in this path cross-checks
checkpoint.PreparedClaimsfor whether a different claim UID already holds that same device before accepting a newPrepare()call for it.Confirmed this gap is still present after the recent checkpoint-restore fix (#259) — that fix touched the already-restored-from-checkpoint branch only, not this path.
Why this looks like the intended contract
staging/src/k8s.io/dynamic-resource-allocation/kubeletplugin/draplugin.go's godoc (recently extended by kubernetes/kubernetes#141479, merged 2026-08-31) now explicitly documents this exact scenario:As the reference implementation most third-party driver authors look to for how to implement this contract, this driver currently provides no example of that enforcement.
What did you expect to happen?
Some form of rejection, queuing, or wait when a
Prepare()call arrives for a device the driver's own checkpoint still shows as held by a different, not-yet-unprepared claim, rather than silently succeeding for both.Anything else we need to know?
None