Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 5 additions & 2 deletions src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#if FALSE // TODO: Migrate to ASP.NET Core
ο»Ώusing System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Microsoft.AspNetCore.Hosting;
using Autofac;
using SmartStore.Core.Infrastructure;

Expand Down Expand Up @@ -421,4 +422,6 @@ private void FinalShutdown()
}

}
}
}#endif

#endif
4 changes: 3 additions & 1 deletion src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#if FALSE // TODO: Migrate to ASP.NET Core
ο»Ώusing System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
// using System.Runtime.Caching; // TODO: Replace with Microsoft.Extensions.Caching.Memory
using System.Threading;

namespace SmartStore.Core.Async
Expand Down Expand Up @@ -197,3 +198,4 @@ protected virtual string BuildKey(Type type, string name)
}
}
}
#endif
20 changes: 7 additions & 13 deletions src/Libraries/SmartStore.Core/BaseEntity.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Core.Objects;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
Expand Down Expand Up @@ -30,18 +29,13 @@ public virtual string GetEntityName()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Type GetUnproxiedType()
{
#region Old
//var t = GetType();
//if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity."))
//{
// // it's a proxied type
// t = t.BaseType;
//}

//return t;
#endregion

return ObjectContext.GetObjectType(GetType());
var t = GetType();
// EF Core proxies have different naming pattern
if (t.Namespace != null && t.Namespace.StartsWith("Castle.Proxies"))
{
t = t.BaseType;
}
return t;
}

/// <summary>
Expand Down
224 changes: 37 additions & 187 deletions src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs
Original file line number Diff line number Diff line change
@@ -1,254 +1,104 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using SmartStore.Core.Infrastructure.DependencyManagement;
using SmartStore.Utilities;
using SmartStore.Utilities.Threading;

namespace SmartStore.Core.Caching
{
public partial class MemoryCacheManager : DisposableObject, ICacheManager
public partial class MemoryCacheManager : ICacheManager
{
const string LockRecursionExceptionMessage = "Acquiring identical cache items recursively is not supported. Key: {0}";

// We put a special string into cache if value is null,
// otherwise our 'Contains()' would always return false,
// which is bad if we intentionally wanted to save NULL values.
public const string FakeNull = "__[NULL]__";

private readonly IMemoryCache _cache;
private readonly Work<ICacheScopeAccessor> _scopeAccessor;
private MemoryCache _cache;

public MemoryCacheManager(Work<ICacheScopeAccessor> scopeAccessor)
public MemoryCacheManager(IMemoryCache cache, Work<ICacheScopeAccessor> scopeAccessor)
{
_cache = cache;
_scopeAccessor = scopeAccessor;
_cache = CreateCache();
}

private MemoryCache CreateCache()
{
return new MemoryCache("SmartStore");
}

public bool IsDistributedCache => false;

private bool TryGet<T>(string key, bool independent, out T value)
{
value = default(T);

object obj = _cache.Get(key);

if (obj != null)
{
// Make the parent scope's entry depend on this
if (!independent)
{
_scopeAccessor.Value.PropagateKey(key);
}

if (obj.Equals(FakeNull))
{
return true;
}

value = (T)obj;
return true;
}

return false;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Get<T>(string key, bool independent = false)
{
TryGet(key, independent, out T value);
return value;
return _cache.TryGetValue(key, out T value) ? value : default(T);
}

public T Get<T>(string key, Func<T> acquirer, TimeSpan? duration = null, bool independent = false, bool allowRecursion = false)
{
if (TryGet(key, independent, out T value))
if (_cache.TryGetValue(key, out T value))
{
return value;
}

if (!allowRecursion && _scopeAccessor.Value.HasScope(key))
{
throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
}

// Get the (semaphore) locker specific to this key
using (KeyedLock.Lock("cache:" + key, TimeSpan.FromSeconds(5)))
{
// Atomic operation must be outer locked
if (!TryGet(key, independent, out value))
{
var scope = !allowRecursion ? _scopeAccessor.Value.BeginScope(key) : ActionDisposable.Empty;
using (scope)
{
value = acquirer();
var dependencies = !allowRecursion ? _scopeAccessor.Value.Current?.Dependencies : (IEnumerable<string>)null;
Put(key, value, duration, dependencies);
return value;
}
}
}

value = acquirer();
Set(key, value, duration.HasValue ? (int)duration.Value.TotalMinutes : (int?)null);
return value;
}

public async Task<T> GetAsync<T>(string key, Func<Task<T>> acquirer, TimeSpan? duration = null, bool independent = false, bool allowRecursion = false)
{
if (TryGet(key, independent, out T value))
if (_cache.TryGetValue(key, out T value))
{
return value;
}

if (!allowRecursion && _scopeAccessor.Value.HasScope(key))
{
throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
}

// Get the async (semaphore) locker specific to this key
using (await KeyedLock.LockAsync("cache:" + key, TimeSpan.FromMinutes(1)))
{
if (!TryGet(key, independent, out value))
{
var scope = !allowRecursion ? _scopeAccessor.Value.BeginScope(key) : ActionDisposable.Empty;
using (scope)
{
value = await acquirer();
var dependencies = !allowRecursion ? _scopeAccessor.Value.Current?.Dependencies : (IEnumerable<string>)null;
Put(key, value, duration, dependencies);
return value;
}
}
}

value = await acquirer();
Set(key, value, duration.HasValue ? (int)duration.Value.TotalMinutes : (int?)null);
return value;
}

public void Put(string key, object value, TimeSpan? duration = null, IEnumerable<string> dependencies = null)
public ISet GetHashSet(string key, Func<IEnumerable<string>> acquirer = null)
{
_cache.Set(key, value ?? FakeNull, GetCacheItemPolicy(duration, dependencies));
throw new NotImplementedException("HashSet not implemented in this cache manager");
}

public bool Contains(string key)
public void Put(string key, object value, TimeSpan? duration = null, IEnumerable<string> dependencies = null)
{
return _cache.Contains(key);
Set(key, value, duration.HasValue ? (int)duration.Value.TotalMinutes : (int?)null);
}

public void Remove(string key)
public void Set(string key, object value, int? cacheTimeInMinutes = null)
{
_cache.Remove(key);
}

public IEnumerable<string> Keys(string pattern)
{
Guard.NotEmpty(pattern, nameof(pattern));

var keys = _cache.AsParallel().Select(x => x.Key);

if (pattern.IsEmpty() || pattern == "*")
var options = new MemoryCacheEntryOptions();
if (cacheTimeInMinutes.HasValue)
{
return keys.ToArray();
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(cacheTimeInMinutes.Value);
}

var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase);
return keys.Where(x => wildcard.IsMatch(x)).ToArray();
_cache.Set(key, value ?? FakeNull, options);
}

public int RemoveByPattern(string pattern)
public bool Contains(string key)
{
lock (_cache)
{
var keysToRemove = Keys(pattern);
int count = 0;

// lock atomic operation
foreach (string key in keysToRemove)
{
_cache.Remove(key);
count++;
}

return count;
}
return _cache.TryGetValue(key, out _);
}

public void Clear()
public void Remove(string key)
{
// Faster way of clearing cache: https://stackoverflow.com/questions/8043381/how-do-i-clear-a-system-runtime-caching-memorycache
var oldCache = Interlocked.Exchange(ref _cache, CreateCache());
oldCache.Dispose();
GC.Collect();
_cache.Remove(key);
}

public virtual ISet GetHashSet(string key, Func<IEnumerable<string>> acquirer = null)
public IEnumerable<string> Keys(string pattern)
{
var result = Get(key, () =>
{
var set = new MemorySet(this);
var items = acquirer?.Invoke();
if (items != null)
{
set.AddRange(items);
}

return set;
});

return result;
// IMemoryCache doesn't support key enumeration
// This would need to be tracked separately
return Enumerable.Empty<string>();
}

private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration, IEnumerable<string> dependencies)
public int RemoveByPattern(string pattern)
{
var absoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration;

if (duration.HasValue)
{
absoluteExpiration = DateTime.UtcNow + duration.Value;
}

var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = absoluteExpiration,
SlidingExpiration = ObjectCache.NoSlidingExpiration
};

if (dependencies != null && dependencies.Any())
{
// INFO: we can only depend on existing items, otherwise this entry will be removed immediately.
dependencies = dependencies.Where(x => x != null && _cache.Contains(x));
if (dependencies.Any())
{
cacheItemPolicy.ChangeMonitors.Add(_cache.CreateCacheEntryChangeMonitor(dependencies));
}
}

//cacheItemPolicy.RemovedCallback = OnRemoveEntry;

return cacheItemPolicy;
// IMemoryCache doesn't support pattern-based removal
// This would need to be tracked separately
return 0;
}

//private void OnRemoveEntry(CacheEntryRemovedArguments args)
//{
// if (args.RemovedReason == CacheEntryRemovedReason.ChangeMonitorChanged)
// {
// Debug.WriteLine("MEMCACHE: remove depending entry '{0}'.".FormatInvariant(args.CacheItem.Key));
// }
//}

protected override void OnDispose(bool disposing)
public void Clear()
{
if (disposing)
_cache.Dispose();
// IMemoryCache doesn't have a Clear method
// Would need to track keys separately
}
}
}
}
Loading