You've already forked linux-packaging-mono
Imported Upstream version 4.6.0.125
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
This commit is contained in:
parent
a569aebcfd
commit
e79aa3c0ed
@ -0,0 +1,352 @@
|
||||
// <copyright file="MemoryCacheStore.cs" company="Microsoft">
|
||||
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace System.Runtime.Caching {
|
||||
internal sealed class MemoryCacheStore : IDisposable {
|
||||
const int INSERT_BLOCK_WAIT = 10000;
|
||||
const int MAX_COUNT = Int32.MaxValue / 2;
|
||||
|
||||
private Hashtable _entries;
|
||||
private Object _entriesLock;
|
||||
private CacheExpires _expires;
|
||||
private CacheUsage _usage;
|
||||
private int _disposed;
|
||||
private ManualResetEvent _insertBlock;
|
||||
private volatile bool _useInsertBlock;
|
||||
private MemoryCache _cache;
|
||||
private PerfCounters _perfCounters;
|
||||
|
||||
internal MemoryCacheStore(MemoryCache cache, PerfCounters perfCounters) {
|
||||
_cache = cache;
|
||||
_perfCounters = perfCounters;
|
||||
_entries = new Hashtable(new MemoryCacheEqualityComparer());
|
||||
_entriesLock = new Object();
|
||||
_expires = new CacheExpires(this);
|
||||
_usage = new CacheUsage(this);
|
||||
InitDisposableMembers();
|
||||
}
|
||||
|
||||
// private members
|
||||
|
||||
private void AddToCache(MemoryCacheEntry entry) {
|
||||
// add outside of lock
|
||||
if (entry != null) {
|
||||
if (entry.HasExpiration()) {
|
||||
_expires.Add(entry);
|
||||
}
|
||||
|
||||
if (entry.HasUsage()
|
||||
&& (!entry.HasExpiration() || entry.UtcAbsExp - DateTime.UtcNow >= CacheUsage.MIN_LIFETIME_FOR_USAGE)) {
|
||||
_usage.Add(entry);
|
||||
}
|
||||
|
||||
entry.State = EntryState.AddedToCache;
|
||||
entry.CallNotifyOnChanged();
|
||||
if (_perfCounters != null) {
|
||||
_perfCounters.Increment(PerfCounterName.Entries);
|
||||
_perfCounters.Increment(PerfCounterName.Turnover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitDisposableMembers() {
|
||||
_insertBlock = new ManualResetEvent(true);
|
||||
_expires.EnableExpirationTimer(true);
|
||||
}
|
||||
|
||||
|
||||
private void RemoveFromCache(MemoryCacheEntry entry, CacheEntryRemovedReason reason, bool delayRelease = false) {
|
||||
// release outside of lock
|
||||
if (entry != null) {
|
||||
if (entry.InExpires()) {
|
||||
_expires.Remove(entry);
|
||||
}
|
||||
|
||||
if (entry.InUsage()) {
|
||||
_usage.Remove(entry);
|
||||
}
|
||||
|
||||
Dbg.Assert(entry.State == EntryState.RemovingFromCache, "entry.State = EntryState.RemovingFromCache");
|
||||
|
||||
entry.State = EntryState.RemovedFromCache;
|
||||
if (!delayRelease) {
|
||||
entry.Release(_cache, reason);
|
||||
}
|
||||
if (_perfCounters != null) {
|
||||
_perfCounters.Decrement(PerfCounterName.Entries);
|
||||
_perfCounters.Increment(PerfCounterName.Turnover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 'updatePerfCounters' defaults to true since this method is called by all Get() operations
|
||||
// to update both the performance counters and the sliding expiration. Callers that perform
|
||||
// nested sliding expiration updates (like a MemoryCacheEntry touching its update sentinel)
|
||||
// can pass false to prevent these from unintentionally showing up in the perf counters.
|
||||
internal void UpdateExpAndUsage(MemoryCacheEntry entry, bool updatePerfCounters = true) {
|
||||
if (entry != null) {
|
||||
if (entry.InUsage() || entry.SlidingExp > TimeSpan.Zero) {
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
entry.UpdateSlidingExp(utcNow, _expires);
|
||||
entry.UpdateUsage(utcNow, _usage);
|
||||
}
|
||||
|
||||
// DevDiv #67021: If this entry has an update sentinel, the sliding expiration is actually associated
|
||||
// with that sentinel, not with this entry. We need to update the sentinel's sliding expiration to
|
||||
// keep the sentinel from expiring, which in turn would force a removal of this entry from the cache.
|
||||
entry.UpdateSlidingExpForUpdateSentinel();
|
||||
|
||||
if (updatePerfCounters && _perfCounters != null) {
|
||||
_perfCounters.Increment(PerfCounterName.Hits);
|
||||
_perfCounters.Increment(PerfCounterName.HitRatio);
|
||||
_perfCounters.Increment(PerfCounterName.HitRatioBase);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (updatePerfCounters && _perfCounters != null) {
|
||||
_perfCounters.Increment(PerfCounterName.Misses);
|
||||
_perfCounters.Increment(PerfCounterName.HitRatioBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitInsertBlock() {
|
||||
_insertBlock.WaitOne(INSERT_BLOCK_WAIT, false);
|
||||
}
|
||||
|
||||
|
||||
// public/internal members
|
||||
|
||||
internal CacheUsage Usage { get { return _usage; } }
|
||||
|
||||
internal MemoryCacheEntry AddOrGetExisting(MemoryCacheKey key, MemoryCacheEntry entry) {
|
||||
if (_useInsertBlock && entry.HasUsage()) {
|
||||
WaitInsertBlock();
|
||||
}
|
||||
MemoryCacheEntry existingEntry = null;
|
||||
MemoryCacheEntry toBeReleasedEntry = null;
|
||||
bool added = false;
|
||||
lock (_entriesLock) {
|
||||
if (_disposed == 0) {
|
||||
existingEntry = _entries[key] as MemoryCacheEntry;
|
||||
// has it expired?
|
||||
if (existingEntry != null && existingEntry.UtcAbsExp <= DateTime.UtcNow) {
|
||||
toBeReleasedEntry = existingEntry;
|
||||
toBeReleasedEntry.State = EntryState.RemovingFromCache;
|
||||
existingEntry = null;
|
||||
}
|
||||
// can we add entry to the cache?
|
||||
if (existingEntry == null) {
|
||||
entry.State = EntryState.AddingToCache;
|
||||
added = true;
|
||||
_entries[key] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
// release outside of lock
|
||||
RemoveFromCache(toBeReleasedEntry, CacheEntryRemovedReason.Expired, delayRelease:true);
|
||||
if (added) {
|
||||
// add outside of lock
|
||||
AddToCache(entry);
|
||||
}
|
||||
// update outside of lock
|
||||
UpdateExpAndUsage(existingEntry);
|
||||
|
||||
// Dev10 861163: Call Release after the new entry has been completely added so
|
||||
// that the CacheItemRemovedCallback can take a dependency on the newly inserted item.
|
||||
if (toBeReleasedEntry != null) {
|
||||
toBeReleasedEntry.Release(_cache, CacheEntryRemovedReason.Expired);
|
||||
}
|
||||
return existingEntry;
|
||||
}
|
||||
|
||||
internal void BlockInsert() {
|
||||
_insertBlock.Reset();
|
||||
_useInsertBlock = true;
|
||||
}
|
||||
|
||||
internal void CopyTo(IDictionary h) {
|
||||
lock (_entriesLock) {
|
||||
if (_disposed == 0) {
|
||||
foreach (DictionaryEntry e in _entries) {
|
||||
MemoryCacheKey key = e.Key as MemoryCacheKey;
|
||||
MemoryCacheEntry entry = e.Value as MemoryCacheEntry;
|
||||
if (entry.UtcAbsExp > DateTime.UtcNow) {
|
||||
h[key.Key] = entry.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal int Count {
|
||||
get {
|
||||
return _entries.Count;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (Interlocked.Exchange(ref _disposed, 1) == 0) {
|
||||
// disable CacheExpires timer
|
||||
_expires.EnableExpirationTimer(false);
|
||||
// build array list of entries
|
||||
ArrayList entries = new ArrayList(_entries.Count);
|
||||
lock (_entriesLock) {
|
||||
foreach (DictionaryEntry e in _entries) {
|
||||
MemoryCacheEntry entry = e.Value as MemoryCacheEntry;
|
||||
entries.Add(entry);
|
||||
}
|
||||
foreach (MemoryCacheEntry entry in entries) {
|
||||
MemoryCacheKey key = entry as MemoryCacheKey;
|
||||
entry.State = EntryState.RemovingFromCache;
|
||||
_entries.Remove(key);
|
||||
}
|
||||
}
|
||||
// release entries outside of lock
|
||||
foreach (MemoryCacheEntry entry in entries) {
|
||||
RemoveFromCache(entry, CacheEntryRemovedReason.CacheSpecificEviction);
|
||||
}
|
||||
|
||||
// MemoryCacheStatistics has been disposed, and therefore nobody should be using
|
||||
// _insertBlock except for potential threads in WaitInsertBlock (which won't care if we call Close).
|
||||
Dbg.Assert(_useInsertBlock == false, "_useInsertBlock == false");
|
||||
_insertBlock.Close();
|
||||
|
||||
|
||||
// Don't need to call GC.SuppressFinalize(this) for sealed types without finalizers.
|
||||
}
|
||||
}
|
||||
|
||||
internal MemoryCacheEntry Get(MemoryCacheKey key) {
|
||||
MemoryCacheEntry entry = _entries[key] as MemoryCacheEntry;
|
||||
// has it expired?
|
||||
if (entry != null && entry.UtcAbsExp <= DateTime.UtcNow) {
|
||||
Remove(key, entry, CacheEntryRemovedReason.Expired);
|
||||
entry = null;
|
||||
}
|
||||
// update outside of lock
|
||||
UpdateExpAndUsage(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
internal MemoryCacheEntry Remove(MemoryCacheKey key, MemoryCacheEntry entryToRemove, CacheEntryRemovedReason reason) {
|
||||
MemoryCacheEntry entry = null;
|
||||
lock (_entriesLock) {
|
||||
if (_disposed == 0) {
|
||||
// get current entry
|
||||
entry = _entries[key] as MemoryCacheEntry;
|
||||
// remove if it matches the entry to be removed (but always remove if entryToRemove is null)
|
||||
if (entryToRemove == null || Object.ReferenceEquals(entry, entryToRemove)) {
|
||||
// Dev10 865887: MemoryCache.Remove("\ue637\ud22a\u3e17") causes NullReferenceEx
|
||||
if (entry != null) {
|
||||
entry.State = EntryState.RemovingFromCache;
|
||||
_entries.Remove(key);
|
||||
}
|
||||
}
|
||||
else {
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// release outside of lock
|
||||
RemoveFromCache(entry, reason);
|
||||
return entry;
|
||||
}
|
||||
|
||||
internal void Set(MemoryCacheKey key, MemoryCacheEntry entry) {
|
||||
if (_useInsertBlock && entry.HasUsage()) {
|
||||
WaitInsertBlock();
|
||||
}
|
||||
MemoryCacheEntry existingEntry = null;
|
||||
bool added = false;
|
||||
lock (_entriesLock) {
|
||||
if (_disposed == 0) {
|
||||
existingEntry = _entries[key] as MemoryCacheEntry;
|
||||
if (existingEntry != null) {
|
||||
existingEntry.State = EntryState.RemovingFromCache;
|
||||
}
|
||||
entry.State = EntryState.AddingToCache;
|
||||
added = true;
|
||||
_entries[key] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
CacheEntryRemovedReason reason = CacheEntryRemovedReason.Removed;
|
||||
if (existingEntry != null) {
|
||||
if (existingEntry.UtcAbsExp <= DateTime.UtcNow) {
|
||||
reason = CacheEntryRemovedReason.Expired;
|
||||
}
|
||||
RemoveFromCache(existingEntry, reason, delayRelease:true);
|
||||
}
|
||||
if (added) {
|
||||
AddToCache(entry);
|
||||
}
|
||||
// Dev10 861163: Call Release after the new entry has been completely added so
|
||||
// that the CacheItemRemovedCallback can take a dependency on the newly inserted item.
|
||||
if (existingEntry != null) {
|
||||
existingEntry.Release(_cache, reason);
|
||||
}
|
||||
}
|
||||
|
||||
internal long TrimInternal(int percent) {
|
||||
Dbg.Assert(percent <= 100, "percent <= 100");
|
||||
|
||||
int count = Count;
|
||||
int toTrim = 0;
|
||||
// do we need to drop a percentage of entries?
|
||||
if (percent > 0) {
|
||||
toTrim = (int)Math.Ceiling(((long)count * (long)percent) / 100D);
|
||||
// would this leave us above MAX_COUNT?
|
||||
int minTrim = count - MAX_COUNT;
|
||||
if (toTrim < minTrim) {
|
||||
toTrim = minTrim;
|
||||
}
|
||||
}
|
||||
// do we need to trim?
|
||||
if (toTrim <= 0 || _disposed == 1) {
|
||||
return 0;
|
||||
}
|
||||
int trimmed = 0; // total number of entries trimmed
|
||||
int trimmedOrExpired = 0;
|
||||
#if DBG
|
||||
int beginTotalCount = count;
|
||||
#endif
|
||||
|
||||
trimmedOrExpired = _expires.FlushExpiredItems(true);
|
||||
if (trimmedOrExpired < toTrim) {
|
||||
trimmed = _usage.FlushUnderUsedItems(toTrim - trimmedOrExpired);
|
||||
trimmedOrExpired += trimmed;
|
||||
}
|
||||
|
||||
if (trimmed > 0 && _perfCounters != null) {
|
||||
// Update values for perfcounters
|
||||
_perfCounters.IncrementBy(PerfCounterName.Trims, trimmed);
|
||||
}
|
||||
|
||||
#if DBG
|
||||
Dbg.Trace("MemoryCacheStore", "TrimInternal:"
|
||||
+ " beginTotalCount=" + beginTotalCount
|
||||
+ ", endTotalCount=" + count
|
||||
+ ", percent=" + percent
|
||||
+ ", trimmed=" + trimmed);
|
||||
#endif
|
||||
return trimmedOrExpired;
|
||||
|
||||
}
|
||||
|
||||
internal void UnblockInsert() {
|
||||
_useInsertBlock = false;
|
||||
_insertBlock.Set();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user