2018-06-29 19:52:40 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2019-02-14 08:46:53 +01:00
|
|
|
|
using System.Threading;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
using System.Web;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Cache
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2019-01-17 11:01:23 +01:00
|
|
|
|
/// Implements a fast <see cref="IAppCache"/> on top of HttpContext.Items.
|
2018-06-29 19:52:40 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
2019-01-17 11:01:23 +01:00
|
|
|
|
/// <para>If no current HttpContext items can be found (no current HttpContext,
|
|
|
|
|
|
/// or no Items...) then this cache acts as a pass-through and does not cache
|
|
|
|
|
|
/// anything.</para>
|
2018-06-29 19:52:40 +02:00
|
|
|
|
/// </remarks>
|
2019-11-08 14:51:20 +11:00
|
|
|
|
public class HttpRequestAppCache : FastDictionaryAppCacheBase
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-17 11:01:23 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
|
|
|
|
|
|
/// </summary>
|
2019-11-08 14:51:20 +11:00
|
|
|
|
public HttpRequestAppCache(Func<IDictionary> requestItems)
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-11-08 14:51:20 +11:00
|
|
|
|
ContextItems = requestItems;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-08 14:51:20 +11:00
|
|
|
|
private Func<IDictionary> ContextItems { get; }
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-11-08 14:51:20 +11:00
|
|
|
|
private bool TryGetContextItems(out IDictionary items)
|
|
|
|
|
|
{
|
|
|
|
|
|
items = ContextItems?.Invoke();
|
|
|
|
|
|
return items != null;
|
|
|
|
|
|
}
|
2019-01-17 11:01:23 +01:00
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public override object Get(string key, Func<object> factory)
|
2018-06-29 19:52:40 +02:00
|
|
|
|
{
|
2019-01-17 11:01:23 +01:00
|
|
|
|
//no place to cache so just return the callback result
|
2019-11-08 14:51:20 +11:00
|
|
|
|
if (!TryGetContextItems(out var items)) return factory();
|
2019-01-17 11:01:23 +01:00
|
|
|
|
|
|
|
|
|
|
key = GetCacheKey(key);
|
|
|
|
|
|
|
|
|
|
|
|
Lazy<object> result;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
EnterWriteLock();
|
2019-11-08 14:51:20 +11:00
|
|
|
|
result = items[key] as Lazy<object>; // null if key not found
|
2019-01-17 11:01:23 +01:00
|
|
|
|
|
|
|
|
|
|
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
|
|
|
|
|
// do nothing here - means that if creation throws, a race condition could cause
|
|
|
|
|
|
// more than one thread to reach the return statement below and throw - accepted.
|
|
|
|
|
|
|
2019-11-07 19:16:45 +11:00
|
|
|
|
if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
2019-01-17 11:01:23 +01:00
|
|
|
|
{
|
2019-11-07 19:16:45 +11:00
|
|
|
|
result = SafeLazy.GetSafeLazy(factory);
|
2019-11-08 14:51:20 +11:00
|
|
|
|
items[key] = result;
|
2019-01-17 11:01:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
ExitWriteLock();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
|
|
|
|
|
// exceptions (but try again and again) and silently eat them - however at
|
|
|
|
|
|
// some point we have to report them - so need to re-throw here
|
|
|
|
|
|
|
|
|
|
|
|
// this does not throw anymore
|
|
|
|
|
|
//return result.Value;
|
|
|
|
|
|
|
|
|
|
|
|
var value = result.Value; // will not throw (safe lazy)
|
2019-11-07 19:16:45 +11:00
|
|
|
|
if (value is SafeLazy.ExceptionHolder eh) eh.Exception.Throw(); // throw once!
|
2019-01-17 11:01:23 +01:00
|
|
|
|
return value;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-17 11:01:23 +01:00
|
|
|
|
#region Entries
|
|
|
|
|
|
|
2018-06-29 19:52:40 +02:00
|
|
|
|
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
|
|
|
|
|
|
{
|
|
|
|
|
|
const string prefix = CacheItemPrefix + "-";
|
|
|
|
|
|
|
2019-11-08 14:51:20 +11:00
|
|
|
|
if (!TryGetContextItems(out var items)) return Enumerable.Empty<DictionaryEntry>();
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-11-08 14:51:20 +11:00
|
|
|
|
return items.Cast<DictionaryEntry>()
|
2019-01-17 11:01:23 +01:00
|
|
|
|
.Where(x => x.Key is string s && s.StartsWith(prefix));
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void RemoveEntry(string key)
|
|
|
|
|
|
{
|
2019-11-08 14:51:20 +11:00
|
|
|
|
if (!TryGetContextItems(out var items)) return;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
2019-11-08 14:51:20 +11:00
|
|
|
|
items.Remove(key);
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override object GetEntry(string key)
|
|
|
|
|
|
{
|
2019-11-08 14:51:20 +11:00
|
|
|
|
return !TryGetContextItems(out var items) ? null : items[key];
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-17 11:01:23 +01:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2018-06-29 19:52:40 +02:00
|
|
|
|
#region Lock
|
|
|
|
|
|
|
2019-02-14 08:46:53 +01:00
|
|
|
|
private const string ContextItemsLockKey = "Umbraco.Core.Cache.HttpRequestCache::LockEntered";
|
2018-06-29 19:52:40 +02:00
|
|
|
|
|
|
|
|
|
|
protected override void EnterReadLock() => EnterWriteLock();
|
|
|
|
|
|
|
|
|
|
|
|
protected override void EnterWriteLock()
|
|
|
|
|
|
{
|
2019-11-08 14:51:20 +11:00
|
|
|
|
if (!TryGetContextItems(out var items)) return;
|
2019-02-14 08:46:53 +01:00
|
|
|
|
|
2019-11-08 14:51:20 +11:00
|
|
|
|
// note: cannot keep 'entered' as a class variable here,
|
|
|
|
|
|
// since there is one per request - so storing it within
|
|
|
|
|
|
// ContextItems - which is locked, so this should be safe
|
|
|
|
|
|
|
|
|
|
|
|
var entered = false;
|
|
|
|
|
|
Monitor.Enter(items.SyncRoot, ref entered);
|
|
|
|
|
|
items[ContextItemsLockKey] = entered;
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void ExitReadLock() => ExitWriteLock();
|
|
|
|
|
|
|
|
|
|
|
|
protected override void ExitWriteLock()
|
|
|
|
|
|
{
|
2019-11-08 14:51:20 +11:00
|
|
|
|
if (!TryGetContextItems(out var items)) return;
|
|
|
|
|
|
|
|
|
|
|
|
var entered = (bool?)items[ContextItemsLockKey] ?? false;
|
2019-02-14 08:46:53 +01:00
|
|
|
|
if (entered)
|
2019-11-08 14:51:20 +11:00
|
|
|
|
Monitor.Exit(items.SyncRoot);
|
|
|
|
|
|
items.Remove(ContextItemsLockKey);
|
2018-06-29 19:52:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|