Repository: kubernetes-csi/external-snapshotter

Issue:
external-snapshotter does not retry removal of VolumeSnapshotContent finalizers
**What happened**:
On an API server hiccup, the external-snapshotter was not able to remove its finalizer from VolumeSnapshotContent and got a timeout. Such hiccups happen all the time, however, the snapshotter did not retry with exp. backoff.

**What you expected to happen**:
The snapshotter re-tries with exponential backoff.

**How to reproduce it**:
I can't reproduce it on demand, it needs careful timing of the API server error. This small commit shows how `removeContentFinalizer()`  call error causes the snapshotter not to retry: https://github.com/jsafrane/external-snapshotter/commit/3da05bbac484c068ec58ae98673f799245528609

With that commit, I can see in the snapshotter logs just once:

```
I0515 16:52:17.267236       1 snapshot_controller.go:59] synchronizing VolumeSnapshotContent[snapcontent-2cee4522-4ae0-40d4-9718-4b51c1e261f8]
I0515 16:52:17.267260       1 snapshot_controller.go:631] Check if VolumeSnapshotContent[snapcontent-2cee4522-4ae0-40d4-9718-4b51c1e261f8] should be deleted.
I0515 16:52:17.267278       1 snapshot_controller.go:62] VolumeSnapshotContent[snapcontent-2cee4522-4ae0-40d4-9718-4b51c1e261f8]: the policy is Delete
E0515 16:52:17.267344       1 snapshot_controller_base.go:361] could not sync content "snapcontent-2cee4522-4ae0-40d4-9718-4b51c1e261f8": snapshot controller failed to update snapcontent-2cee4522-4ae0-40d4-9718-4b51c1e261f8 on API server: mock finalizer removal error
```

I want the snapshotter to retry periodically.

**Environment**:
- Driver version: csi-driver-hostpath
- csi-snapshotter: v8.2.0

Hints from issue comments:
@yati1998: Hi @jsafrane , I see similiar issue already raised: https://github.com/kubernetes-csi/external-snapshotter/issues/1282 
The volumeSnapshotContent is not requeued @jsafrane: I think its a different issue. In https://github.com/kubernetes-csi/external-snapshotter/issues/1282 re-queues a snapshot / snapshot content after 5 minutes and that may be too much in some cases. I don't see my snapshot content requeued ever.@jsafrane: /assign

Solution patch:
diff --git a/README.md b/README.md
index 54117433a..bb8f53ac3 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,8 @@ Specifically, `deploy/kubernetes/snapshot-controller/setup-snapshot-controller.y
 
 * `--resync-period <duration>`: Internal resync interval when the snapshot controller re-evaluates all existing `VolumeSnapshot` instances and tries to fulfill them, i.e. create / delete corresponding snapshots. It does not affect re-tries of failed calls! It should be used only when there is a bug in Kubernetes watch logic. Default is 15 minutes.
 
+* `--automaxprocs`: Automatically set the `GOMAXPROCS` environment variable to match the configured Linux container CPU quota. Defaults to false.
+
 * `--version`: Prints current snapshot controller version and quits.
 
 * All glog / klog arguments are supported, such as `-v <log level>` or `-alsologtostderr`.
@@ -212,6 +214,8 @@ Specifically, `deploy/kubernetes/snapshot-controller/setup-snapshot-controller.y
 
 * `--resync-period <duration>`: Internal resync interval when the CSI external-snapshotter re-evaluates all existing `VolumeSnapshotContent` instances and tries to fulfill them, i.e. update / delete corresponding snapshots. It does not affect re-tries of failed CSI calls! It should be used only when there is a bug in Kubernetes watch logic. Default is 15 minutes.
 
+* `--automaxprocs`: Automatically set the `GOMAXPROCS` environment variable to match the configured Linux container CPU quota. Defaults to false.
+
 * `--version`: Prints current CSI external-snapshotter version and quits.
 
 * All glog / klog arguments are supported, such as `-v <log level>` or `-alsologtostderr`.
diff --git a/cmd/csi-snapshotter/main.go b/cmd/csi-snapshotter/main.go
index 0c1e37f18..e135cac88 100644
--- a/cmd/csi-snapshotter/main.go
+++ b/cmd/csi-snapshotter/main.go
@@ -45,6 +45,7 @@ import (
 	"github.com/kubernetes-csi/csi-lib-utils/leaderelection"
 	"github.com/kubernetes-csi/csi-lib-utils/metrics"
 	csirpc "github.com/kubernetes-csi/csi-lib-utils/rpc"
+	"github.com/kubernetes-csi/csi-lib-utils/standardflags"
 	"github.com/kubernetes-csi/external-snapshotter/v8/pkg/features"
 	controller "github.com/kubernetes-csi/external-snapshotter/v8/pkg/sidecar-controller"
 	"github.com/kubernetes-csi/external-snapshotter/v8/pkg/snapshotter"
@@ -115,6 +116,7 @@ func main() {
 	c := logsapi.NewLoggingConfiguration()
 	logsapi.AddGoFlags(c, flag.CommandLine)
 	logs.InitLogs()
+	standardflags.AddAutomaxprocs(klog.Infof)
 	flag.Parse()
 	if err := logsapi.ValidateAndApply(c, fg); err != nil {
 		klog.ErrorS(err, "LoggingConfiguration is invalid")
diff --git a/cmd/snapshot-controller/main.go b/cmd/snapshot-controller/main.go
index 5f152792f..7404ffca9 100644
--- a/cmd/snapshot-controller/main.go
+++ b/cmd/snapshot-controller/main.go
@@ -43,6 +43,7 @@ import (
 	klog "k8s.io/klog/v2"
 
 	"github.com/kubernetes-csi/csi-lib-utils/leaderelection"
+	"github.com/kubernetes-csi/csi-lib-utils/standardflags"
 	controller "github.com/kubernetes-csi/external-snapshotter/v8/pkg/common-controller"
 	"github.com/kubernetes-csi/external-snapshotter/v8/pkg/features"
 	"github.com/kubernetes-csi/external-snapshotter/v8/pkg/metrics"
@@ -163,6 +164,7 @@ func main() {
 	c := logsapi.NewLoggingConfiguration()
 	logsapi.AddGoFlags(c, flag.CommandLine)
 	logs.InitLogs()
+	standardflags.AddAutomaxprocs(klog.Infof)
 	flag.Parse()
 	if err := logsapi.ValidateAndApply(c, fg); err != nil {
 		klog.ErrorS(err, "LoggingConfiguration is invalid")
diff --git a/go.mod b/go.mod
index 7e999600e..97d8fada0 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/container-storage-interface/spec v1.11.0
 	github.com/evanphx/json-patch v5.9.0+incompatible
 	github.com/golang/mock v1.6.0
-	github.com/kubernetes-csi/csi-lib-utils v0.21.0
+	github.com/kubernetes-csi/csi-lib-utils v0.22.0
 	github.com/kubernetes-csi/csi-test/v5 v5.3.1
 	github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0
 	github.com/prometheus/client_golang v1.22.0
@@ -16,11 +16,11 @@ require (
 	github.com/prometheus/common v0.62.0
 	google.golang.org/grpc v1.69.0
 	google.golang.org/protobuf v1.36.5
-	k8s.io/api v0.33.0
-	k8s.io/apimachinery v0.33.0
+	k8s.io/api v0.33.1
+	k8s.io/apimachinery v0.33.1
 	k8s.io/apiserver v0.33.0
-	k8s.io/client-go v0.33.0
-	k8s.io/component-base v0.33.0
+	k8s.io/client-go v0.33.1
+	k8s.io/component-base v0.33.1
 	k8s.io/component-helpers v0.33.0
 	k8s.io/klog/v2 v2.130.1
 	k8s.io/utils v0.0.0-20241210054802-24370beab758
@@ -60,6 +60,7 @@ require (
 	go.opentelemetry.io/otel v1.33.0 // indirect
 	go.opentelemetry.io/otel/metric v1.33.0 // indirect
 	go.opentelemetry.io/otel/trace v1.33.0 // indirect
+	go.uber.org/automaxprocs v1.6.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/net v0.39.0 // indirect
diff --git a/go.sum b/go.sum
index b0253608b..561f7ae4b 100644
--- a/go.sum
+++ b/go.sum
@@ -63,8 +63,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kubernetes-csi/csi-lib-utils v0.21.0 h1:dUN/iIgXLucAxyML2iPyhniIlACQumIeAJmIzsMBddc=
-github.com/kubernetes-csi/csi-lib-utils v0.21.0/go.mod h1:ZCVRTYuup+bwX9tOeE5Q3LDw64QvltSwMUQ3M3g2T+Q=
+github.com/kubernetes-csi/csi-lib-utils v0.22.0 h1:EUAs1+uHGps3OtVj4XVx16urhpI02eu+Z8Vps6plpHY=
+github.com/kubernetes-csi/csi-lib-utils v0.22.0/go.mod h1:f+PalKyS4Ujsjb9+m6Rj0W6c28y3nfea3paQ/VqjI28=
 github.com/kubernetes-csi/csi-test/v5 v5.3.1 h1:Wiukp1In+kif+BFo6q2ExjgB+MbrAz4jZWzGfijypuY=
 github.com/kubernetes-csi/csi-test/v5 v5.3.1/go.mod h1:7hA2cSYJ6T8CraEZPA6zqkLZwemjBD54XAnPsPC3VpA=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@@ -87,6 +87,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
+github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
 github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
 github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
 github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
@@ -127,6 +129,8 @@ go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4Jjx
 go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
 go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
 go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
+go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
+go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
diff --git a/pkg/sidecar-controller/snapshot_controller.go b/pkg/sidecar-controller/snapshot_controller.go
index fbd4d9c28..73b8a2c88 100644
--- a/pkg/sidecar-controller/snapshot_controller.go
+++ b/pkg/sidecar-controller/snapshot_controller.go
@@ -67,14 +67,15 @@ func (ctrl *csiSnapshotSideCarController) syncContent(content *crdv1.VolumeSnaps
 			// Note that the deletion snapshot operation will update content SnapshotHandle
 			// to nil upon a successful deletion. At this
 			// point, the finalizer on content should NOT be removed to avoid leaking.
-			err := ctrl.deleteCSISnapshot(content)
+			var err error
+			content, err = ctrl.deleteCSISnapshot(content)
 			if err != nil {
 				return true, err
 			}
-			return false, nil
+			// Continue removing the finalizer
 		}
-		// otherwise, either the snapshot has been deleted from the underlying
-		// storage system, or it belongs to a volumegroupsnapshot, or the deletion policy is Retain,
+		// the snapshot has been deleted from the underlying storage system, or
+		// it belongs to a volumegroupsnapshot, or the deletion policy is Retain,
 		// remove the finalizer if there is one so that API server could delete
 		// the object if there is no other finalizer.
 		err := ctrl.removeContentFinalizer(content)
@@ -82,7 +83,6 @@ func (ctrl *csiSnapshotSideCarController) syncContent(content *crdv1.VolumeSnaps
 			return true, err
 		}
 		return false, nil
-
 	}
 
 	// Create snapshot calling the CSI driver only if it is a dynamic
@@ -109,7 +109,7 @@ func (ctrl *csiSnapshotSideCarController) syncContent(content *crdv1.VolumeSnaps
 }
 
 // deleteCSISnapshot starts delete action.
-func (ctrl *csiSnapshotSideCarController) deleteCSISnapshot(content *crdv1.VolumeSnapshotContent) error {
+func (ctrl *csiSnapshotSideCarController) deleteCSISnapshot(content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshotContent, error) {
 	klog.V(5).Infof("Deleting snapshot for content: %s", content.Name)
 	return ctrl.deleteCSISnapshotOperation(content)
 }
@@ -406,31 +406,29 @@ func (ctrl *csiSnapshotSideCarController) createSnapshotWrapper(content *crdv1.V
 }
 
 // Delete a snapshot: Ask the backend to remove the snapshot device
-func (ctrl *csiSnapshotSideCarController) deleteCSISnapshotOperation(content *crdv1.VolumeSnapshotContent) error {
+func (ctrl *csiSnapshotSideCarController) deleteCSISnapshotOperation(content *crdv1.VolumeSnapshotContent) (*crdv1.VolumeSnapshotContent, error) {
 	klog.V(5).Infof("deleteCSISnapshotOperation [%s] started", content.Name)
 
 	snapshotterCredentials, err := ctrl.GetCredentialsFromAnnotation(content)
 	if err != nil {
 		ctrl.eventRecorder.Event(content, v1.EventTypeWarning, "SnapshotDeleteError", "Failed to get snapshot credentials")
-		return fmt.Errorf("failed to get input parameters to delete snapshot for content %s: %q", content.Name, err)
+		return content, fmt.Errorf("failed to get input parameters to delete snapshot for content %s: %q", content.Name, err)
 	}
 
 	err = ctrl.handler.DeleteSnapshot(content, snapshotterCredentials)
 	if err != nil {
 		ctrl.eventRecorder.Event(content, v1.EventTypeWarning, "SnapshotDeleteError", "Failed to delete snapshot")
-		return fmt.Errorf("failed to delete snapshot %#v, err: %v", content.Name, err)
+		return content, fmt.Errorf("failed to delete snapshot %#v, err: %v", content.Name, err)
 	}
 	// the snapshot has been deleted from the underlying storage system, update
 	// content status to remove snapshot handle etc.
+	// This triggers a re-sync of the content object, which will continue cleaning the object (e.g. finalizers)
 	newContent, err := ctrl.clearVolumeContentStatus(content.Name)
 	if err != nil {
 		ctrl.eventRecorder.Event(content, v1.EventTypeWarning, "SnapshotDeleteError", "Failed to clear content status")
-		return err
+		return content, err
 	}
-	// trigger syncContent
-	// TODO: just enqueue the content object instead of calling syncContent directly
-	ctrl.updateContentInInformerCache(newContent)
-	return nil
+	return newContent, nil
 }
 
 // clearVolumeContentStatus resets all fields to nil related to a snapshot in
diff --git a/pkg/sidecar-controller/snapshot_controller_base.go b/pkg/sidecar-controller/snapshot_controller_base.go
index 0e0c7295d..9b0a51a63 100644
--- a/pkg/sidecar-controller/snapshot_controller_base.go
+++ b/pkg/sidecar-controller/snapshot_controller_base.go
@@ -260,11 +260,27 @@ func (ctrl *csiSnapshotSideCarController) syncContentByKey(key string) (requeue
 	// been add/update/sync
 	if err == nil {
 		if ctrl.isDriverMatch(content) {
-			requeue, err = ctrl.updateContentInInformerCache(content)
-		}
-		if err != nil {
-			// If error occurs we add this item back to the queue
-			return true, err
+			// Store the new content version in the cache and do not process it if this is
+			// an old version.
+			new, err := ctrl.storeContentUpdate(content)
+			if err != nil {
+				klog.Errorf("%v", err)
+			}
+			if !new {
+				return false, nil
+			}
+			requeue, err = ctrl.syncContent(content)
+			if err != nil {
+				if errors.IsConflict(err) {
+					// Version conflict error happens quite often and the controller
+					// recovers from it easily.
+					klog.V(3).Infof("could not sync content %q: %+v", content.Name, err)
+				} else {
+					klog.Errorf("could not sync content %q: %+v", content.Name, err)
+				}
+				// If error occurs we add this item back to the queue
+				return true, err
+			}
 		}
 		return requeue, nil
 	}
@@ -339,32 +355,6 @@ func (ctrl *csiSnapshotSideCarController) isDriverMatch(object interface{}) bool
 	return false
 }
 
-// updateContentInInformerCache runs in worker thread and handles "content added",
-// "content updated" and "periodic sync" events.
-func (ctrl *csiSnapshotSideCarController) updateContentInInformerCache(content *crdv1.VolumeSnapshotContent) (requeue bool, err error) {
-	// Store the new content version in the cache and do not process it if this is
-	// an old version.
-	new, err := ctrl.storeContentUpdate(content)
-	if err != nil {
-		klog.Errorf("%v", err)
-	}
-	if !new {
-		return false, nil
-	}
-	requeue, err = ctrl.syncContent(content)
-	if err != nil {
-		if errors.IsConflict(err) {
-			// Version conflict error happens quite often and the controller
-			// recovers from it easily.
-			klog.V(3).Infof("could not sync content %q: %+v", content.Name, err)
-		} else {
-			klog.Errorf("could not sync content %q: %+v", content.Name, err)
-		}
-		return requeue, err
-	}
-	return requeue, nil
-}
-
 // deleteContent runs in worker thread and handles "content deleted" event.
 func (ctrl *csiSnapshotSideCarController) deleteContentInCacheStore(content *crdv1.VolumeSnapshotContent) {
 	_ = ctrl.contentStore.Delete(content)
diff --git a/pkg/utils/util.go b/pkg/utils/util.go
index 0df49d193..f4f82b34a 100644
--- a/pkg/utils/util.go
+++ b/pkg/utils/util.go
@@ -680,6 +680,10 @@ func GetDynamicSnapshotContentNameForGroupSnapshot(groupSnapshot *crdv1beta1.Vol
 // If the VolumeSnapshotContent object still contains other changes after this sanitization, the changes
 // are potentially meaningful and the object is enqueued to be considered for syncing
 func ShouldEnqueueContentChange(old *crdv1.VolumeSnapshotContent, new *crdv1.VolumeSnapshotContent) bool {
+	// Always enqueue resyncs, which show up as an update with no change (thus no new version)
+	if old.ResourceVersion == new.ResourceVersion {
+		return true
+	}
 	sanitized := new.DeepCopy()
 	// ResourceVersion always changes between revisions
 	sanitized.ResourceVersion = old.ResourceVersion
@@ -693,7 +697,7 @@ func ShouldEnqueueContentChange(old *crdv1.VolumeSnapshotContent, new *crdv1.Vol
 		if sanitized.Annotations == nil {
 			sanitized.Annotations = map[string]string{}
 		}
-		for annotation, _ := range sidecarControlledContentAnnotations {
+		for annotation := range sidecarControlledContentAnnotations {
 			if value, ok := old.Annotations[annotation]; ok {
 				sanitized.Annotations[annotation] = value
 			} else {
@@ -702,7 +706,7 @@ func ShouldEnqueueContentChange(old *crdv1.VolumeSnapshotContent, new *crdv1.Vol
 		}
 	} else {
 		// Old content has no annotations, so delete any sidecar-controlled annotations present on the new content
-		for annotation, _ := range sidecarControlledContentAnnotations {
+		for annotation := range sidecarControlledContentAnnotations {
 			delete(sanitized.Annotations, annotation)
 		}
 	}
diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/leaderelection/leader_election.go b/vendor/github.com/kubernetes-csi/csi-lib-utils/leaderelection/leader_election.go
index ea2b4f13b..4045127c5 100644
--- a/vendor/github.com/kubernetes-csi/csi-lib-utils/leaderelection/leader_election.go
+++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/leaderelection/leader_election.go
@@ -40,6 +40,8 @@ const (
 	defaultRenewDeadline = 10 * time.Second
 	defaultRetryPeriod   = 5 * time.Second
 
+	defaultReleaseOnCancel = false
+
 	DefaultHealthCheckTimeout = 20 * time.Second
 
 	// HealthCheckerAddress is the address at which the leader election health
@@ -66,6 +68,8 @@ type leaderElection struct {
 	// within a timeout period.
 	healthCheck *leaderelection.HealthzAdaptor
 
+	releaseOnCancel bool
+
 	leaseDuration time.Duration
 	renewDeadline time.Duration
 	retryPeriod   time.Duration
@@ -83,13 +87,14 @@ func NewLeaderElection(clientset kubernetes.Interface, lockName string, runFunc
 // NewLeaderElectionWithLeases returns an implementation of leader election using Leases
 func NewLeaderElectionWithLeases(clientset kubernetes.Interface, lockName string, runFunc func(ctx context.Context)) *leaderElection {
 	return &leaderElection{
-		runFunc:       runFunc,
-		lockName:      lockName,
-		resourceLock:  resourcelock.LeasesResourceLock,
-		leaseDuration: defaultLeaseDuration,
-		renewDeadline: defaultRenewDeadline,
-		retryPeriod:   defaultRetryPeriod,
-		clientset:     clientset,
+		runFunc:         runFunc,
+		lockName:        lockName,
+		resourceLock:    resourcelock.LeasesResourceLock,
+		leaseDuration:   defaultLeaseDuration,
+		renewDeadline:   defaultRenewDeadline,
+		releaseOnCancel: defaultReleaseOnCancel,
+		retryPeriod:     defaultRetryPeriod,
+		clientset:       clientset,
 	}
 }
 
@@ -113,6 +118,10 @@ func (l *leaderElection) WithRetryPeriod(retryPeriod time.Duration) {
 	l.retryPeriod = retryPeriod
 }
 
+func (l *leaderElection) WithReleaseOnCancel(releaseOnCancel bool) {
+	l.releaseOnCancel = releaseOnCancel
+}
+
 // WithContext Add context
 func (l *leaderElection) WithContext(ctx context.Context) {
 	l.ctx = ctx
@@ -174,10 +183,11 @@ func (l *leaderElection) Run() error {
 	}
 
 	leaderConfig := leaderelection.LeaderElectionConfig{
-		Lock:          lock,
-		LeaseDuration: l.leaseDuration,
-		RenewDeadline: l.renewDeadline,
-		RetryPeriod:   l.retryPeriod,
+		Lock:            lock,
+		LeaseDuration:   l.leaseDuration,
+		RenewDeadline:   l.renewDeadline,
+		RetryPeriod:     l.retryPeriod,
+		ReleaseOnCancel: l.releaseOnCancel,
 		Callbacks: leaderelection.LeaderCallbacks{
 			OnStartedLeading: func(ctx context.Context) {
 				logger := klog.FromContext(ctx)
diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/standardflags/automaxprocs.go b/vendor/github.com/kubernetes-csi/csi-lib-utils/standardflags/automaxprocs.go
new file mode 100644
index 000000000..3c0006308
--- /dev/null
+++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/standardflags/automaxprocs.go
@@ -0,0 +1,105 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package standardflags contains flags that multiple CSI sidecars and
+// drivers may want to support.
+package standardflags
+
+import (
+	"flag"
+	"fmt"
+	"strconv"
+
+	"go.uber.org/automaxprocs/maxprocs"
+)
+
+var (
+	logFunc          func(format string, args ...interface{}) = nil
+	undoAutomaxprocs func()                                   = nil
+)
+
+// AddAutomaxprocs adds the -automaxprocs boolean flag to the commandline options.
+// By default the flag is disabled, use [EnableAutomaxprocs] to enable it during
+// startup of an application.
+//
+// The printf function that is passed as an argument, will be used by the
+// [maxprocs.Logger] option when the GOMAXPROCS runtime configuration is adjusted.
+func AddAutomaxprocs(printf func(format string, args ...interface{})) {
+	flag.BoolFunc("automaxprocs",
+		"automatically set GOMAXPROCS to match Linux container CPU quota",
+		handleAutomaxprocs,
+	)
+
+	if printf != nil {
+		// maxprocs.Logger expects a Printf like function.
+		// klog.Info() isn't one, so wrap the contents in a
+		// fmt.Sprintf() for %-formatting substitution.
+		logFunc = func(f string, a ...interface{}) {
+			printf(fmt.Sprintf(f, a...))
+		}
+	}
+}
+
+// EnableAutomaxprocs can be used as an equivalent of -automaxprocs=true on the
+// commandline.
+func EnableAutomaxprocs() {
+	if automaxprocsIsEnabled() {
+		// enabled already, don't enable again
+		return
+	}
+
+	flag.Set("automaxprocs", "true")
+}
+
+// automaxprocsIsEnabled returns true if maxprocs.Set() was successfully
+// executed.
+func automaxprocsIsEnabled() bool {
+	return undoAutomaxprocs != nil
+}
+
+// handleAutomaxprocs parses the passed string into a bool, and enables
+// automaxprocs according to it. If the passed string is empty, automaxprocs
+// is enabled as well.
+func handleAutomaxprocs(s string) error {
+	var err error
+	enabled := true
+
+	if s == "" {
+		EnableAutomaxprocs()
+		return nil
+	}
+
+	enabled, err = strconv.ParseBool(s)
+	if err != nil {
+		return err
+	}
+
+	switch enabled {
+	case true:
+		opts := make([]maxprocs.Option, 0)
+		if logFunc != nil {
+			opts = append(opts, maxprocs.Logger(logFunc))
+		}
+		undoAutomaxprocs, err = maxprocs.Set(opts...)
+	case false:
+		if undoAutomaxprocs != nil {
+			undoAutomaxprocs()
+			undoAutomaxprocs = nil
+		}
+	}
+
+	return err
+}
diff --git a/vendor/go.uber.org/automaxprocs/LICENSE b/vendor/go.uber.org/automaxprocs/LICENSE
new file mode 100644
index 000000000..20dcf51d9
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2017 Uber Technologies, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go
new file mode 100644
index 000000000..fe4ecf561
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go
@@ -0,0 +1,79 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+	"bufio"
+	"io"
+	"os"
+	"path/filepath"
+	"strconv"
+)
+
+// CGroup represents the data structure for a Linux control group.
+type CGroup struct {
+	path string
+}
+
+// NewCGroup returns a new *CGroup from a given path.
+func NewCGroup(path string) *CGroup {
+	return &CGroup{path: path}
+}
+
+// Path returns the path of the CGroup*.
+func (cg *CGroup) Path() string {
+	return cg.path
+}
+
+// ParamPath returns the path of the given cgroup param under itself.
+func (cg *CGroup) ParamPath(param string) string {
+	return filepath.Join(cg.path, param)
+}
+
+// readFirstLine reads the first line from a cgroup param file.
+func (cg *CGroup) readFirstLine(param string) (string, error) {
+	paramFile, err := os.Open(cg.ParamPath(param))
+	if err != nil {
+		return "", err
+	}
+	defer paramFile.Close()
+
+	scanner := bufio.NewScanner(paramFile)
+	if scanner.Scan() {
+		return scanner.Text(), nil
+	}
+	if err := scanner.Err(); err != nil {
+		return "", err
+	}
+	return "", io.ErrUnexpectedEOF
+}
+
+// readInt parses the first line from a cgroup param file as int.
+func (cg *CGroup) readInt(param string) (int, error) {
+	text, err := cg.readFirstLine(param)
+	if err != nil {
+		return 0, err
+	}
+	return strconv.Atoi(text)
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go
new file mode 100644
index 000000000..e89f54360
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package cgroups
+
+const (
+	// _cgroupFSType is the Linux CGroup file system type used in
+	// `/proc/$PID/mountinfo`.
+	_cgroupFSType = "cgroup"
+	// _cgroupSubsysCPU is the CPU CGroup subsystem.
+	_cgroupSubsysCPU = "cpu"
+	// _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem.
+	_cgroupSubsysCPUAcct = "cpuacct"
+	// _cgroupSubsysCPUSet is the CPUSet CGroup subsystem.
+	_cgroupSubsysCPUSet = "cpuset"
+	// _cgroupSubsysMemory is the Memory CGroup subsystem.
+	_cgroupSubsysMemory = "memory"
+
+	// _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota
+	// parameter.
+	_cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us"
+	// _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period
+	// parameter.
+	_cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us"
+)
+
+const (
+	_procPathCGroup    = "/proc/self/cgroup"
+	_procPathMountInfo = "/proc/self/mountinfo"
+)
+
+// CGroups is a map that associates each CGroup with its subsystem name.
+type CGroups map[string]*CGroup
+
+// NewCGroups returns a new *CGroups from given `mountinfo` and `cgroup` files
+// under for some process under `/proc` file system (see also proc(5) for more
+// information).
+func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) {
+	cgroupSubsystems, err := parseCGroupSubsystems(procPathCGroup)
+	if err != nil {
+		return nil, err
+	}
+
+	cgroups := make(CGroups)
+	newMountPoint := func(mp *MountPoint) error {
+		if mp.FSType != _cgroupFSType {
+			return nil
+		}
+
+		for _, opt := range mp.SuperOptions {
+			subsys, exists := cgroupSubsystems[opt]
+			if !exists {
+				continue
+			}
+
+			cgroupPath, err := mp.Translate(subsys.Name)
+			if err != nil {
+				return err
+			}
+			cgroups[opt] = NewCGroup(cgroupPath)
+		}
+
+		return nil
+	}
+
+	if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil {
+		return nil, err
+	}
+	return cgroups, nil
+}
+
+// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current
+// process.
+func NewCGroupsForCurrentProcess() (CGroups, error) {
+	return NewCGroups(_procPathMountInfo, _procPathCGroup)
+}
+
+// CPUQuota returns the CPU quota applied with the CPU cgroup controller.
+// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of
+// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`.
+func (cg CGroups) CPUQuota() (float64, bool, error) {
+	cpuCGroup, exists := cg[_cgroupSubsysCPU]
+	if !exists {
+		return -1, false, nil
+	}
+
+	cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam)
+	if defined := cfsQuotaUs > 0; err != nil || !defined {
+		return -1, defined, err
+	}
+
+	cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam)
+	if defined := cfsPeriodUs > 0; err != nil || !defined {
+		return -1, defined, err
+	}
+
+	return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go
new file mode 100644
index 000000000..78556062f
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go
@@ -0,0 +1,176 @@
+// Copyright (c) 2022 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+)
+
+const (
+	// _cgroupv2CPUMax is the file name for the CGroup-V2 CPU max and period
+	// parameter.
+	_cgroupv2CPUMax = "cpu.max"
+	// _cgroupFSType is the Linux CGroup-V2 file system type used in
+	// `/proc/$PID/mountinfo`.
+	_cgroupv2FSType = "cgroup2"
+
+	_cgroupv2MountPoint = "/sys/fs/cgroup"
+
+	_cgroupV2CPUMaxDefaultPeriod = 100000
+	_cgroupV2CPUMaxQuotaMax      = "max"
+)
+
+const (
+	_cgroupv2CPUMaxQuotaIndex = iota
+	_cgroupv2CPUMaxPeriodIndex
+)
+
+// ErrNotV2 indicates that the system is not using cgroups2.
+var ErrNotV2 = errors.New("not using cgroups2")
+
+// CGroups2 provides access to cgroups data for systems using cgroups2.
+type CGroups2 struct {
+	mountPoint string
+	groupPath  string
+	cpuMaxFile string
+}
+
+// NewCGroups2ForCurrentProcess builds a CGroups2 for the current process.
+//
+// This returns ErrNotV2 if the system is not using cgroups2.
+func NewCGroups2ForCurrentProcess() (*CGroups2, error) {
+	return newCGroups2From(_procPathMountInfo, _procPathCGroup)
+}
+
+func newCGroups2From(mountInfoPath, procPathCGroup string) (*CGroups2, error) {
+	isV2, err := isCGroupV2(mountInfoPath)
+	if err != nil {
+		return nil, err
+	}
+
+	if !isV2 {
+		return nil, ErrNotV2
+	}
+
+	subsystems, err := parseCGroupSubsystems(procPathCGroup)
+	if err != nil {
+		return nil, err
+	}
+
+	// Find v2 subsystem by looking for the `0` id
+	var v2subsys *CGroupSubsys
+	for _, subsys := range subsystems {
+		if subsys.ID == 0 {
+			v2subsys = subsys
+			break
+		}
+	}
+
+	if v2subsys == nil {
+		return nil, ErrNotV2
+	}
+
+	return &CGroups2{
+		mountPoint: _cgroupv2MountPoint,
+		groupPath:  v2subsys.Name,
+		cpuMaxFile: _cgroupv2CPUMax,
+	}, nil
+}
+
+func isCGroupV2(procPathMountInfo string) (bool, error) {
+	var (
+		isV2          bool
+		newMountPoint = func(mp *MountPoint) error {
+			isV2 = isV2 || (mp.FSType == _cgroupv2FSType && mp.MountPoint == _cgroupv2MountPoint)
+			return nil
+		}
+	)
+
+	if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil {
+		return false, err
+	}
+
+	return isV2, nil
+}
+
+// CPUQuota returns the CPU quota applied with the CPU cgroup2 controller.
+// It is a result of reading cpu quota and period from cpu.max file.
+// It will return `cpu.max / cpu.period`. If cpu.max is set to max, it returns
+// (-1, false, nil)
+func (cg *CGroups2) CPUQuota() (float64, bool, error) {
+	cpuMaxParams, err := os.Open(path.Join(cg.mountPoint, cg.groupPath, cg.cpuMaxFile))
+	if err != nil {
+		if os.IsNotExist(err) {
+			return -1, false, nil
+		}
+		return -1, false, err
+	}
+	defer cpuMaxParams.Close()
+
+	scanner := bufio.NewScanner(cpuMaxParams)
+	if scanner.Scan() {
+		fields := strings.Fields(scanner.Text())
+		if len(fields) == 0 || len(fields) > 2 {
+			return -1, false, fmt.Errorf("invalid format")
+		}
+
+		if fields[_cgroupv2CPUMaxQuotaIndex] == _cgroupV2CPUMaxQuotaMax {
+			return -1, false, nil
+		}
+
+		max, err := strconv.Atoi(fields[_cgroupv2CPUMaxQuotaIndex])
+		if err != nil {
+			return -1, false, err
+		}
+
+		var period int
+		if len(fields) == 1 {
+			period = _cgroupV2CPUMaxDefaultPeriod
+		} else {
+			period, err = strconv.Atoi(fields[_cgroupv2CPUMaxPeriodIndex])
+			if err != nil {
+				return -1, false, err
+			}
+
+			if period == 0 {
+				return -1, false, errors.New("zero value for period is not allowed")
+			}
+		}
+
+		return float64(max) / float64(period), true, nil
+	}
+
+	if err := scanner.Err(); err != nil {
+		return -1, false, err
+	}
+
+	return 0, false, io.ErrUnexpectedEOF
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go
new file mode 100644
index 000000000..113555f63
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go
@@ -0,0 +1,23 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package cgroups provides utilities to access Linux control group (CGroups)
+// parameters (CPU quota, for example) for a given process.
+package cgroups
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go
new file mode 100644
index 000000000..94ac75a46
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package cgroups
+
+import "fmt"
+
+type cgroupSubsysFormatInvalidError struct {
+	line string
+}
+
+type mountPointFormatInvalidError struct {
+	line string
+}
+
+type pathNotExposedFromMountPointError struct {
+	mountPoint string
+	root       string
+	path       string
+}
+
+func (err cgroupSubsysFormatInvalidError) Error() string {
+	return fmt.Sprintf("invalid format for CGroupSubsys: %q", err.line)
+}
+
+func (err mountPointFormatInvalidError) Error() string {
+	return fmt.Sprintf("invalid format for MountPoint: %q", err.line)
+}
+
+func (err pathNotExposedFromMountPointError) Error() string {
+	return fmt.Sprintf("path %q is not a descendant of mount point root %q and cannot be exposed from %q", err.path, err.root, err.mountPoint)
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go
new file mode 100644
index 000000000..f3877f78a
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go
@@ -0,0 +1,171 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+	"bufio"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+const (
+	_mountInfoSep               = " "
+	_mountInfoOptsSep           = ","
+	_mountInfoOptionalFieldsSep = "-"
+)
+
+const (
+	_miFieldIDMountID = iota
+	_miFieldIDParentID
+	_miFieldIDDeviceID
+	_miFieldIDRoot
+	_miFieldIDMountPoint
+	_miFieldIDOptions
+	_miFieldIDOptionalFields
+
+	_miFieldCountFirstHalf
+)
+
+const (
+	_miFieldOffsetFSType = iota
+	_miFieldOffsetMountSource
+	_miFieldOffsetSuperOptions
+
+	_miFieldCountSecondHalf
+)
+
+const _miFieldCountMin = _miFieldCountFirstHalf + _miFieldCountSecondHalf
+
+// MountPoint is the data structure for the mount points in
+// `/proc/$PID/mountinfo`. See also proc(5) for more information.
+type MountPoint struct {
+	MountID        int
+	ParentID       int
+	DeviceID       string
+	Root           string
+	MountPoint     string
+	Options        []string
+	OptionalFields []string
+	FSType         string
+	MountSource    string
+	SuperOptions   []string
+}
+
+// NewMountPointFromLine parses a line read from `/proc/$PID/mountinfo` and
+// returns a new *MountPoint.
+func NewMountPointFromLine(line string) (*MountPoint, error) {
+	fields := strings.Split(line, _mountInfoSep)
+
+	if len(fields) < _miFieldCountMin {
+		return nil, mountPointFormatInvalidError{line}
+	}
+
+	mountID, err := strconv.Atoi(fields[_miFieldIDMountID])
+	if err != nil {
+		return nil, err
+	}
+
+	parentID, err := strconv.Atoi(fields[_miFieldIDParentID])
+	if err != nil {
+		return nil, err
+	}
+
+	for i, field := range fields[_miFieldIDOptionalFields:] {
+		if field == _mountInfoOptionalFieldsSep {
+			// End of optional fields.
+			fsTypeStart := _miFieldIDOptionalFields + i + 1
+
+			// Now we know where the optional fields end, split the line again with a
+			// limit to avoid issues with spaces in super options as present on WSL.
+			fields = strings.SplitN(line, _mountInfoSep, fsTypeStart+_miFieldCountSecondHalf)
+			if len(fields) != fsTypeStart+_miFieldCountSecondHalf {
+				return nil, mountPointFormatInvalidError{line}
+			}
+
+			miFieldIDFSType := _miFieldOffsetFSType + fsTypeStart
+			miFieldIDMountSource := _miFieldOffsetMountSource + fsTypeStart
+			miFieldIDSuperOptions := _miFieldOffsetSuperOptions + fsTypeStart
+
+			return &MountPoint{
+				MountID:        mountID,
+				ParentID:       parentID,
+				DeviceID:       fields[_miFieldIDDeviceID],
+				Root:           fields[_miFieldIDRoot],
+				MountPoint:     fields[_miFieldIDMountPoint],
+				Options:        strings.Split(fields[_miFieldIDOptions], _mountInfoOptsSep),
+				OptionalFields: fields[_miFieldIDOptionalFields:(fsTypeStart - 1)],
+				FSType:         fields[miFieldIDFSType],
+				MountSource:    fields[miFieldIDMountSource],
+				SuperOptions:   strings.Split(fields[miFieldIDSuperOptions], _mountInfoOptsSep),
+			}, nil
+		}
+	}
+
+	return nil, mountPointFormatInvalidError{line}
+}
+
+// Translate converts an absolute path inside the *MountPoint's file system to
+// the host file system path in the mount namespace the *MountPoint belongs to.
+func (mp *MountPoint) Translate(absPath string) (string, error) {
+	relPath, err := filepath.Rel(mp.Root, absPath)
+
+	if err != nil {
+		return "", err
+	}
+	if relPath == ".." || strings.HasPrefix(relPath, "../") {
+		return "", pathNotExposedFromMountPointError{
+			mountPoint: mp.MountPoint,
+			root:       mp.Root,
+			path:       absPath,
+		}
+	}
+
+	return filepath.Join(mp.MountPoint, relPath), nil
+}
+
+// parseMountInfo parses procPathMountInfo (usually at `/proc/$PID/mountinfo`)
+// and yields parsed *MountPoint into newMountPoint.
+func parseMountInfo(procPathMountInfo string, newMountPoint func(*MountPoint) error) error {
+	mountInfoFile, err := os.Open(procPathMountInfo)
+	if err != nil {
+		return err
+	}
+	defer mountInfoFile.Close()
+
+	scanner := bufio.NewScanner(mountInfoFile)
+
+	for scanner.Scan() {
+		mountPoint, err := NewMountPointFromLine(scanner.Text())
+		if err != nil {
+			return err
+		}
+		if err := newMountPoint(mountPoint); err != nil {
+			return err
+		}
+	}
+
+	return scanner.Err()
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go
new file mode 100644
index 000000000..cddc3eaec
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+	"bufio"
+	"os"
+	"strconv"
+	"strings"
+)
+
+const (
+	_cgroupSep       = ":"
+	_cgroupSubsysSep = ","
+)
+
+const (
+	_csFieldIDID = iota
+	_csFieldIDSubsystems
+	_csFieldIDName
+	_csFieldCount
+)
+
+// CGroupSubsys represents the data structure for entities in
+// `/proc/$PID/cgroup`. See also proc(5) for more information.
+type CGroupSubsys struct {
+	ID         int
+	Subsystems []string
+	Name       string
+}
+
+// NewCGroupSubsysFromLine returns a new *CGroupSubsys by parsing a string in
+// the format of `/proc/$PID/cgroup`
+func NewCGroupSubsysFromLine(line string) (*CGroupSubsys, error) {
+	fields := strings.SplitN(line, _cgroupSep, _csFieldCount)
+
+	if len(fields) != _csFieldCount {
+		return nil, cgroupSubsysFormatInvalidError{line}
+	}
+
+	id, err := strconv.Atoi(fields[_csFieldIDID])
+	if err != nil {
+		return nil, err
+	}
+
+	cgroup := &CGroupSubsys{
+		ID:         id,
+		Subsystems: strings.Split(fields[_csFieldIDSubsystems], _cgroupSubsysSep),
+		Name:       fields[_csFieldIDName],
+	}
+
+	return cgroup, nil
+}
+
+// parseCGroupSubsystems parses procPathCGroup (usually at `/proc/$PID/cgroup`)
+// and returns a new map[string]*CGroupSubsys.
+func parseCGroupSubsystems(procPathCGroup string) (map[string]*CGroupSubsys, error) {
+	cgroupFile, err := os.Open(procPathCGroup)
+	if err != nil {
+		return nil, err
+	}
+	defer cgroupFile.Close()
+
+	scanner := bufio.NewScanner(cgroupFile)
+	subsystems := make(map[string]*CGroupSubsys)
+
+	for scanner.Scan() {
+		cgroup, err := NewCGroupSubsysFromLine(scanner.Text())
+		if err != nil {
+			return nil, err
+		}
+		for _, subsys := range cgroup.Subsystems {
+			subsystems[subsys] = cgroup
+		}
+	}
+
+	if err := scanner.Err(); err != nil {
+		return nil, err
+	}
+
+	return subsystems, nil
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go
new file mode 100644
index 000000000..f9057fd27
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build linux
+// +build linux
+
+package runtime
+
+import (
+	"errors"
+
+	cg "go.uber.org/automaxprocs/internal/cgroups"
+)
+
+// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
+// to a valid GOMAXPROCS value. The quota is converted from float to int using round.
+// If round == nil, DefaultRoundFunc is used.
+func CPUQuotaToGOMAXPROCS(minValue int, round func(v float64) int) (int, CPUQuotaStatus, error) {
+	if round == nil {
+		round = DefaultRoundFunc
+	}
+	cgroups, err := _newQueryer()
+	if err != nil {
+		return -1, CPUQuotaUndefined, err
+	}
+
+	quota, defined, err := cgroups.CPUQuota()
+	if !defined || err != nil {
+		return -1, CPUQuotaUndefined, err
+	}
+
+	maxProcs := round(quota)
+	if minValue > 0 && maxProcs < minValue {
+		return minValue, CPUQuotaMinUsed, nil
+	}
+	return maxProcs, CPUQuotaUsed, nil
+}
+
+type queryer interface {
+	CPUQuota() (float64, bool, error)
+}
+
+var (
+	_newCgroups2 = cg.NewCGroups2ForCurrentProcess
+	_newCgroups  = cg.NewCGroupsForCurrentProcess
+	_newQueryer  = newQueryer
+)
+
+func newQueryer() (queryer, error) {
+	cgroups, err := _newCgroups2()
+	if err == nil {
+		return cgroups, nil
+	}
+	if errors.Is(err, cg.ErrNotV2) {
+		return _newCgroups()
+	}
+	return nil, err
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go
new file mode 100644
index 000000000..e74701508
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go
@@ -0,0 +1,31 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build !linux
+// +build !linux
+
+package runtime
+
+// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
+// to a valid GOMAXPROCS value. This is Linux-specific and not supported in the
+// current OS.
+func CPUQuotaToGOMAXPROCS(_ int, _ func(v float64) int) (int, CPUQuotaStatus, error) {
+	return -1, CPUQuotaUndefined, nil
+}
diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go b/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go
new file mode 100644
index 000000000..f8a2834ac
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package runtime
+
+import "math"
+
+// CPUQuotaStatus presents the status of how CPU quota is used
+type CPUQuotaStatus int
+
+const (
+	// CPUQuotaUndefined is returned when CPU quota is undefined
+	CPUQuotaUndefined CPUQuotaStatus = iota
+	// CPUQuotaUsed is returned when a valid CPU quota can be used
+	CPUQuotaUsed
+	// CPUQuotaMinUsed is returned when CPU quota is smaller than the min value
+	CPUQuotaMinUsed
+)
+
+// DefaultRoundFunc is the default function to convert CPU quota from float to int. It rounds the value down (floor).
+func DefaultRoundFunc(v float64) int {
+	return int(math.Floor(v))
+}
diff --git a/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go b/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go
new file mode 100644
index 000000000..e561fe60b
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go
@@ -0,0 +1,139 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Package maxprocs lets Go programs easily configure runtime.GOMAXPROCS to
+// match the configured Linux CPU quota. Unlike the top-level automaxprocs
+// package, it lets the caller configure logging and handle errors.
+package maxprocs // import "go.uber.org/automaxprocs/maxprocs"
+
+import (
+	"os"
+	"runtime"
+
+	iruntime "go.uber.org/automaxprocs/internal/runtime"
+)
+
+const _maxProcsKey = "GOMAXPROCS"
+
+func currentMaxProcs() int {
+	return runtime.GOMAXPROCS(0)
+}
+
+type config struct {
+	printf         func(string, ...interface{})
+	procs          func(int, func(v float64) int) (int, iruntime.CPUQuotaStatus, error)
+	minGOMAXPROCS  int
+	roundQuotaFunc func(v float64) int
+}
+
+func (c *config) log(fmt string, args ...interface{}) {
+	if c.printf != nil {
+		c.printf(fmt, args...)
+	}
+}
+
+// An Option alters the behavior of Set.
+type Option interface {
+	apply(*config)
+}
+
+// Logger uses the supplied printf implementation for log output. By default,
+// Set doesn't log anything.
+func Logger(printf func(string, ...interface{})) Option {
+	return optionFunc(func(cfg *config) {
+		cfg.printf = printf
+	})
+}
+
+// Min sets the minimum GOMAXPROCS value that will be used.
+// Any value below 1 is ignored.
+func Min(n int) Option {
+	return optionFunc(func(cfg *config) {
+		if n >= 1 {
+			cfg.minGOMAXPROCS = n
+		}
+	})
+}
+
+// RoundQuotaFunc sets the function that will be used to covert the CPU quota from float to int.
+func RoundQuotaFunc(rf func(v float64) int) Option {
+	return optionFunc(func(cfg *config) {
+		cfg.roundQuotaFunc = rf
+	})
+}
+
+type optionFunc func(*config)
+
+func (of optionFunc) apply(cfg *config) { of(cfg) }
+
+// Set GOMAXPROCS to match the Linux container CPU quota (if any), returning
+// any error encountered and an undo function.
+//
+// Set is a no-op on non-Linux systems and in Linux environments without a
+// configured CPU quota.
+func Set(opts ...Option) (func(), error) {
+	cfg := &config{
+		procs:          iruntime.CPUQuotaToGOMAXPROCS,
+		roundQuotaFunc: iruntime.DefaultRoundFunc,
+		minGOMAXPROCS:  1,
+	}
+	for _, o := range opts {
+		o.apply(cfg)
+	}
+
+	undoNoop := func() {
+		cfg.log("maxprocs: No GOMAXPROCS change to reset")
+	}
+
+	// Honor the GOMAXPROCS environment variable if present. Otherwise, amend
+	// `runtime.GOMAXPROCS()` with the current process' CPU quota if the OS is
+	// Linux, and guarantee a minimum value of 1. The minimum guaranteed value
+	// can be overridden using `maxprocs.Min()`.
+	if max, exists := os.LookupEnv(_maxProcsKey); exists {
+		cfg.log("maxprocs: Honoring GOMAXPROCS=%q as set in environment", max)
+		return undoNoop, nil
+	}
+
+	maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS, cfg.roundQuotaFunc)
+	if err != nil {
+		return undoNoop, err
+	}
+
+	if status == iruntime.CPUQuotaUndefined {
+		cfg.log("maxprocs: Leaving GOMAXPROCS=%v: CPU quota undefined", currentMaxProcs())
+		return undoNoop, nil
+	}
+
+	prev := currentMaxProcs()
+	undo := func() {
+		cfg.log("maxprocs: Resetting GOMAXPROCS to %v", prev)
+		runtime.GOMAXPROCS(prev)
+	}
+
+	switch status {
+	case iruntime.CPUQuotaMinUsed:
+		cfg.log("maxprocs: Updating GOMAXPROCS=%v: using minimum allowed GOMAXPROCS", maxProcs)
+	case iruntime.CPUQuotaUsed:
+		cfg.log("maxprocs: Updating GOMAXPROCS=%v: determined from CPU quota", maxProcs)
+	}
+
+	runtime.GOMAXPROCS(maxProcs)
+	return undo, nil
+}
diff --git a/vendor/go.uber.org/automaxprocs/maxprocs/version.go b/vendor/go.uber.org/automaxprocs/maxprocs/version.go
new file mode 100644
index 000000000..cc7fc5aee
--- /dev/null
+++ b/vendor/go.uber.org/automaxprocs/maxprocs/version.go
@@ -0,0 +1,24 @@
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package maxprocs
+
+// Version is the current package version.
+const Version = "1.6.0"
diff --git a/vendor/modules.txt b/vendor/modules.txt
index dfa3a5d28..3e80bc0aa 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -78,13 +78,14 @@ github.com/josharian/intern
 # github.com/json-iterator/go v1.1.12
 ## explicit; go 1.12
 github.com/json-iterator/go
-# github.com/kubernetes-csi/csi-lib-utils v0.21.0
-## explicit; go 1.23.1
+# github.com/kubernetes-csi/csi-lib-utils v0.22.0
+## explicit; go 1.24.0
 github.com/kubernetes-csi/csi-lib-utils/connection
 github.com/kubernetes-csi/csi-lib-utils/leaderelection
 github.com/kubernetes-csi/csi-lib-utils/metrics
 github.com/kubernetes-csi/csi-lib-utils/protosanitizer
 github.com/kubernetes-csi/csi-lib-utils/rpc
+github.com/kubernetes-csi/csi-lib-utils/standardflags
 # github.com/kubernetes-csi/csi-test/v5 v5.3.1
 ## explicit; go 1.18
 github.com/kubernetes-csi/csi-test/v5/driver
@@ -186,6 +187,11 @@ go.opentelemetry.io/otel/metric/noop
 go.opentelemetry.io/otel/trace
 go.opentelemetry.io/otel/trace/embedded
 go.opentelemetry.io/otel/trace/noop
+# go.uber.org/automaxprocs v1.6.0
+## explicit; go 1.20
+go.uber.org/automaxprocs/internal/cgroups
+go.uber.org/automaxprocs/internal/runtime
+go.uber.org/automaxprocs/maxprocs
 # go.uber.org/multierr v1.11.0
 ## explicit; go 1.19
 go.uber.org/multierr
@@ -346,7 +352,7 @@ gopkg.in/inf.v0
 # gopkg.in/yaml.v3 v3.0.1
 ## explicit
 gopkg.in/yaml.v3
-# k8s.io/api v0.33.0 => k8s.io/api v0.33.0
+# k8s.io/api v0.33.1 => k8s.io/api v0.33.0
 ## explicit; go 1.24.0
 k8s.io/api/admissionregistration/v1
 k8s.io/api/admissionregistration/v1alpha1
@@ -406,7 +412,7 @@ k8s.io/api/storage/v1
 k8s.io/api/storage/v1alpha1
 k8s.io/api/storage/v1beta1
 k8s.io/api/storagemigration/v1alpha1
-# k8s.io/apimachinery v0.33.0 => k8s.io/apimachinery v0.33.0
+# k8s.io/apimachinery v0.33.1 => k8s.io/apimachinery v0.33.0
 ## explicit; go 1.24.0
 k8s.io/apimachinery/pkg/api/equality
 k8s.io/apimachinery/pkg/api/errors
@@ -465,7 +471,7 @@ k8s.io/apimachinery/third_party/forked/golang/reflect
 # k8s.io/apiserver v0.33.0 => k8s.io/apiserver v0.33.0
 ## explicit; go 1.24.0
 k8s.io/apiserver/pkg/util/feature
-# k8s.io/client-go v0.33.0 => k8s.io/client-go v0.33.0
+# k8s.io/client-go v0.33.1 => k8s.io/client-go v0.33.0
 ## explicit; go 1.24.0
 k8s.io/client-go/applyconfigurations
 k8s.io/client-go/applyconfigurations/admissionregistration/v1
@@ -798,7 +804,7 @@ k8s.io/client-go/util/keyutil
 k8s.io/client-go/util/retry
 k8s.io/client-go/util/watchlist
 k8s.io/client-go/util/workqueue
-# k8s.io/component-base v0.33.0 => k8s.io/component-base v0.33.0
+# k8s.io/component-base v0.33.1 => k8s.io/component-base v0.33.0
 ## explicit; go 1.24.0
 k8s.io/component-base/cli/flag
 k8s.io/component-base/featuregate



Test patch:
diff --git a/pkg/utils/util_test.go b/pkg/utils/util_test.go
index 62b1b7fc7..353220bcb 100644
--- a/pkg/utils/util_test.go
+++ b/pkg/utils/util_test.go
@@ -489,9 +489,30 @@ func TestShouldEnqueueContentChange(t *testing.T) {
 			},
 			expectedResult: true,
 		},
+		{
+			name: "resync from informer (old matches new including resource version)",
+			old: &crdv1.VolumeSnapshotContent{
+				ObjectMeta: metav1.ObjectMeta{
+					ResourceVersion: oldValue,
+				},
+			},
+			new: &crdv1.VolumeSnapshotContent{
+				ObjectMeta: metav1.ObjectMeta{
+					ResourceVersion: oldValue,
+				},
+			},
+			expectedResult: true,
+		},
 	}
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			// Inject resource version unless it is already set in test object
+			if tc.old.ResourceVersion == "" {
+				tc.old.ResourceVersion = oldValue
+			}
+			if tc.new.ResourceVersion == "" {
+				tc.old.ResourceVersion = newValue
+			}
 			result := ShouldEnqueueContentChange(tc.old, tc.new)
 			if result != tc.expectedResult {
 				t.Fatalf("Incorrect result: Expected %v received %v", tc.expectedResult, result)