Repository: minio/minio (language: go)

Issue:
MinIO ignores `stale_uploads_cleanup_interval` at start-up
The `stale_uploads_cleanup_interval` and `stale_uploads_expiry` settings are used to control the interval when abandoned multi-part uploads are being cleaned. During startup the `cmd.(*erasureSets).cleanupStaleUploads()` method is invoked that looks like this:
```go
func (s *erasureSets) cleanupStaleUploads(ctx context.Context) {
	timer := time.NewTimer(globalAPIConfig.getStaleUploadsCleanupInterval())
	defer timer.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-timer.C:
			// ...actual clean-up removed...

			// Reset for the next interval
			timer.Reset(globalAPIConfig.getStaleUploadsCleanupInterval())
		}
	}
}
```
The `globalAPIConfig.getStaleUploadsCleanupInterval()` method returns the interval, but it looks like `cleanupStaleUploads` is called, before the actual API configuration has been loaded. Another issue is that changing the interval doesn't reset the loop, so it still sticks to the old setting until that expiry has passed.


Discussion / hints:

Fix patch:
diff --git a/cmd/erasure-sets.go b/cmd/erasure-sets.go
index 2552f67d789c4..22e8f1229b088 100644
--- a/cmd/erasure-sets.go
+++ b/cmd/erasure-sets.go
@@ -86,6 +86,8 @@ type erasureSets struct {
 	lastConnectDisksOpTime time.Time
 }
 
+var staleUploadsCleanupIntervalChangedCh = make(chan struct{})
+
 func (s *erasureSets) getDiskMap() map[Endpoint]StorageAPI {
 	diskMap := make(map[Endpoint]StorageAPI)
 
@@ -532,10 +534,11 @@ func (s *erasureSets) cleanupStaleUploads(ctx context.Context) {
 				}(set)
 			}
 			wg.Wait()
-
-			// Reset for the next interval
-			timer.Reset(globalAPIConfig.getStaleUploadsCleanupInterval())
+		case <-staleUploadsCleanupIntervalChangedCh:
 		}
+
+		// Reset for the next interval
+		timer.Reset(globalAPIConfig.getStaleUploadsCleanupInterval())
 	}
 }
 
diff --git a/cmd/handler-api.go b/cmd/handler-api.go
index c0f1d6800efda..24f0d941ca368 100644
--- a/cmd/handler-api.go
+++ b/cmd/handler-api.go
@@ -183,13 +183,22 @@ func (t *apiConfig) init(cfg api.Config, setDriveCounts []int, legacy bool) {
 	t.transitionWorkers = cfg.TransitionWorkers
 
 	t.staleUploadsExpiry = cfg.StaleUploadsExpiry
-	t.staleUploadsCleanupInterval = cfg.StaleUploadsCleanupInterval
 	t.deleteCleanupInterval = cfg.DeleteCleanupInterval
 	t.enableODirect = cfg.EnableODirect
 	t.gzipObjects = cfg.GzipObjects
 	t.rootAccess = cfg.RootAccess
 	t.syncEvents = cfg.SyncEvents
 	t.objectMaxVersions = cfg.ObjectMaxVersions
+
+	if t.staleUploadsCleanupInterval != cfg.StaleUploadsCleanupInterval {
+		t.staleUploadsCleanupInterval = cfg.StaleUploadsCleanupInterval
+
+		// signal that cleanup interval has changed
+		select {
+		case staleUploadsCleanupIntervalChangedCh <- struct{}{}:
+		default: // in case the channel is blocked...
+		}
+	}
 }
 
 func (t *apiConfig) odirectEnabled() bool {


Test patch: