@@ -19,11 +19,11 @@ type IdleCleanupManager struct {
1919 checkInterval time.Duration
2020 lastActivity atomic.Int64 // Unix timestamp in seconds
2121 stopChan chan struct {}
22- cleanupCount atomic.Int64 // Counter for cleanup operations (useful for metrics)
23- activeProcesses atomic.Int32 // Number of active image processing operations
24- cleanupMu sync.Mutex // Protects cleanup operations
22+ cleanupCount atomic.Int64 // Counter for cleanup operations (useful for metrics)
23+ activeProcesses atomic.Int32 // Number of active image processing operations
24+ processingMu sync.RWMutex // RWMutex: RLock for image processing, Lock for cleanup (blocks all processing)
2525 wg sync.WaitGroup // Tracks background goroutine to prevent leaks
26- cleanupFunc func () // Function to call for cleanup (defaults to bimg.VipsCacheDropAll, can be mocked in tests)
26+ cleanupFunc func () // Function to call for cleanup (defaults to bimg.VipsCacheDropAll, can be mocked in tests)
2727}
2828
2929// NewIdleCleanupManager creates a new IdleCleanupManager
@@ -88,25 +88,28 @@ func (m *IdleCleanupManager) RecordActivity() {
8888 m .lastActivity .Store (time .Now ().Unix ())
8989}
9090
91- // BeginProcessing increments the active process counter
91+ // BeginProcessing acquires a read lock and increments the active process counter
9292// Call this before starting image processing to prevent cleanup during processing
93+ // This will BLOCK if cleanup is currently running
9394func (m * IdleCleanupManager ) BeginProcessing () {
9495 if ! m .enabled {
9596 return
9697 }
9798
99+ m .processingMu .RLock () // Acquire read lock - blocks if cleanup is running
98100 m .activeProcesses .Add (1 )
99101 m .RecordActivity ()
100102}
101103
102- // EndProcessing decrements the active process counter
104+ // EndProcessing decrements the active process counter and releases the read lock
103105// Call this after image processing completes (use defer for safety)
104106func (m * IdleCleanupManager ) EndProcessing () {
105107 if ! m .enabled {
106108 return
107109 }
108110
109111 m .activeProcesses .Add (- 1 )
112+ m .processingMu .RUnlock () // Release read lock
110113}
111114
112115// GetCleanupCount returns the number of cleanup operations performed
@@ -163,28 +166,34 @@ func (m *IdleCleanupManager) checkAndCleanup() {
163166}
164167
165168// performCleanup actually performs the libvips cache cleanup
166- // It is thread-safe and will only perform cleanup if no image processing is active
169+ // It acquires an exclusive write lock, blocking all image processing until cleanup completes
170+ // This ensures no images are being processed during cleanup
167171func (m * IdleCleanupManager ) performCleanup () {
168- // Lock to ensure only one cleanup at a time
169- m .cleanupMu .Lock ()
170- defer m .cleanupMu .Unlock ()
172+ // Acquire write lock - this will:
173+ // 1. Wait for all current image processing (RLock holders) to complete
174+ // 2. Block any new image processing from starting
175+ m .processingMu .Lock ()
176+ defer m .processingMu .Unlock ()
171177
172- // Check if there are active image processing operations
178+ // Safety check: verify no active processes (should always be 0 due to lock)
173179 activeCount := m .activeProcesses .Load ()
174- if activeCount > 0 {
175- monitoring .Log ().Debug ( "Skipping cleanup due to active processing " ,
180+ if activeCount != 0 {
181+ monitoring .Log ().Error ( "CRITICAL: Active processes detected despite holding cleanup lock! " ,
176182 zap .Int32 ("activeProcesses" , activeCount ))
177183 return
178184 }
179185
186+ monitoring .Log ().Info ("Acquired cleanup lock, all image processing blocked" ,
187+ zap .Int32 ("activeProcesses" , activeCount ))
188+
180189 // Get memory stats before cleanup
181190 memBefore := bimg .VipsMemory ()
182191
183192 monitoring .Log ().Info ("Performing libvips cache cleanup" ,
184193 zap .Int64 ("memoryBefore" , memBefore .Memory ),
185194 zap .Int64 ("memoryAllocations" , memBefore .Allocations ))
186195
187- // Perform cleanup - safe because no active processing
196+ // Perform cleanup - safe because we have exclusive lock
188197 // Use the injected cleanup function (defaults to bimg.VipsCacheDropAll)
189198 if m .cleanupFunc != nil {
190199 m .cleanupFunc ()
0 commit comments