-
Notifications
You must be signed in to change notification settings - Fork 40
Description
Description
A data race exists in (*SyncLockMap[K, V]).triggerCleanupIfNeeded() where the lastCleanup field is accessed without proper sync. This causes sporadic test failures when the race detector is enabled.
Root cause
In SyncLockMap[K, V], the triggerCleanupIfNeeded() method performs unsync'ed read-modify-write ops on (*SyncLockMap[K, V]).triggerCleanupIfNeeded():
Lines 68 to 83 in 5ef874c
| // triggerCleanupIfNeeded triggers a one-shot cleanup if it hasn't run in the cleanup interval | |
| func (s *SyncLockMap[K, V]) triggerCleanupIfNeeded() { | |
| if s.inactivityDuration <= 0 { | |
| return | |
| } | |
| // Check if cleanup is needed using instance-specific interval | |
| now := time.Now() | |
| if now.Sub(s.lastCleanup) < s.cleanupInterval { | |
| return | |
| } | |
| // Update last cleanup time and trigger async cleanup | |
| s.lastCleanup = now | |
| go s.evictInactiveEntries() | |
| } |
L76: Read w/o lock
L81: Write w/o lock
This method is called from Get() after releasing the (*SyncLockMap[K, V]).mu.
Output:
WARNING: DATA RACE
Read at 0x00c00146c4b8 by goroutine 73:
github.com/projectdiscovery/utils/maps.(*SyncLockMap[...]).triggerCleanupIfNeeded()
/home/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/maps/synclock_map.go:76 +0x74
github.com/projectdiscovery/utils/maps.(*SyncLockMap[...]).Get()
/home/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/maps/synclock_map.go:184 +0x27d
Previous write at 0x00c00146c4b8 by goroutine 96:
github.com/projectdiscovery/utils/maps.(*SyncLockMap[...]).triggerCleanupIfNeeded()
/home/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/maps/synclock_map.go:81 +0xd8
github.com/projectdiscovery/utils/maps.(*SyncLockMap[...]).Get()
/home/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/maps/synclock_map.go:184 +0x27d
Ref: https://github.com/projectdiscovery/nuclei/actions/runs/19495312364/job/55796169237
Why is this race sporadic?
The race manifests infrequently because:
-
Narrow race window: With typical cleanup intervals (12-24hrs), only the first concurrent access triggers a write. Subsequent accesses see the updated
lastCleanupand return early, closing the race window within μs. -
Non-deterministic: The race detector only catches races that actually occur during execution. Retries may pass due to different timing/scheduling.
Impact
Sporadic CI test failures and potential UB (though typically non-critical due to narrow race window).
Additional Context
time.Time is a 24-byte struct (on 64-bit systems) and cannot be read/written atomically. According to the Go memory model, all concurrent access to shared mutable state must be sync'ed, even if the consequences seem minor.