[scudo] Update secondary cache time-based release logic (#107507)

Secondary cache entries are now released to the OS from least recent to
most recent entries. This helps to avoid unnecessary scans of the cache
since entries ready to be released (specifically, entries that are
considered old relative to the configurable release interval) will
always be at the tail of the list of committed entries by the LRU
ordering. For this same reason, the `OldestTime` variable is no longer
needed to indicate when releases are necessary so it has been removed.
This commit is contained in:
Joshua Baehring 2024-09-16 14:33:03 -04:00 committed by GitHub
parent 4b4dbaaede
commit e5271fef8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -247,6 +247,7 @@ public:
// The cache is initially empty
LRUHead = CachedBlock::InvalidEntry;
LRUTail = CachedBlock::InvalidEntry;
LastUnreleasedEntry = CachedBlock::InvalidEntry;
// Available entries will be retrieved starting from the beginning of the
// Entries array
@ -321,9 +322,10 @@ public:
}
CachedBlock PrevEntry = Quarantine[QuarantinePos];
Quarantine[QuarantinePos] = Entry;
if (OldestTime == 0)
OldestTime = Entry.Time;
Entry = PrevEntry;
// Update the entry time to reflect the time that the
// quarantined memory is placed in the Entries array
Entry.Time = Time;
}
// All excess entries are evicted from the cache
@ -334,9 +336,6 @@ public:
}
insert(Entry);
if (OldestTime == 0)
OldestTime = Entry.Time;
} while (0);
for (MemMapT &EvictMemMap : EvictionMemMaps)
@ -535,6 +534,9 @@ private:
Entries[LRUHead].Prev = static_cast<u16>(FreeIndex);
}
if (LastUnreleasedEntry == CachedBlock::InvalidEntry)
LastUnreleasedEntry = static_cast<u16>(FreeIndex);
Entries[FreeIndex] = Entry;
Entries[FreeIndex].Next = LRUHead;
Entries[FreeIndex].Prev = CachedBlock::InvalidEntry;
@ -552,6 +554,9 @@ private:
Entries[I].invalidate();
if (I == LastUnreleasedEntry)
LastUnreleasedEntry = Entries[LastUnreleasedEntry].Prev;
if (I == LRUHead)
LRUHead = Entries[I].Next;
else
@ -593,35 +598,37 @@ private:
}
}
void releaseIfOlderThan(CachedBlock &Entry, u64 Time) REQUIRES(Mutex) {
if (!Entry.isValid() || !Entry.Time)
return;
if (Entry.Time > Time) {
if (OldestTime == 0 || Entry.Time < OldestTime)
OldestTime = Entry.Time;
return;
}
inline void release(CachedBlock &Entry) {
DCHECK(Entry.Time != 0);
Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase, Entry.CommitSize);
Entry.Time = 0;
}
void releaseOlderThan(u64 Time) EXCLUDES(Mutex) {
ScopedLock L(Mutex);
if (!EntriesCount || OldestTime == 0 || OldestTime > Time)
if (!EntriesCount)
return;
OldestTime = 0;
for (uptr I = 0; I < Config::getQuarantineSize(); I++)
releaseIfOlderThan(Quarantine[I], Time);
for (uptr I = 0; I < Config::getEntriesArraySize(); I++)
releaseIfOlderThan(Entries[I], Time);
}
for (uptr I = 0; I < Config::getQuarantineSize(); I++) {
CachedBlock &ReleaseEntry = Quarantine[I];
if (!ReleaseEntry.isValid() || !ReleaseEntry.Time ||
ReleaseEntry.Time > Time)
continue;
release(ReleaseEntry);
}
// Release oldest entries first by releasing from decommit base
while (LastUnreleasedEntry != CachedBlock::InvalidEntry &&
Entries[LastUnreleasedEntry].Time <= Time) {
release(Entries[LastUnreleasedEntry]);
LastUnreleasedEntry = Entries[LastUnreleasedEntry].Prev;
}
}
HybridMutex Mutex;
u32 EntriesCount GUARDED_BY(Mutex) = 0;
u32 QuarantinePos GUARDED_BY(Mutex) = 0;
atomic_u32 MaxEntriesCount = {};
atomic_uptr MaxEntrySize = {};
u64 OldestTime GUARDED_BY(Mutex) = 0;
atomic_s32 ReleaseToOsIntervalMs = {};
u32 CallsToRetrieve GUARDED_BY(Mutex) = 0;
u32 SuccessfulRetrieves GUARDED_BY(Mutex) = 0;
@ -636,6 +643,9 @@ private:
u16 LRUTail GUARDED_BY(Mutex) = 0;
// The AvailableHead is the top of the stack of available entries
u16 AvailableHead GUARDED_BY(Mutex) = 0;
// The LastUnreleasedEntry is the least recently used entry that has not
// been released
u16 LastUnreleasedEntry GUARDED_BY(Mutex) = 0;
};
template <typename Config> class MapAllocator {