Skip to content

Commit 94153af

Browse files
Improves the performance of publishing pages that contain Archetype properties: umbraco/Umbraco.Cloud.Issues#35
Does this by adding caching (that replenishes every 30 seconds). The plumbing for the caching mechanism is also included.
1 parent 456978a commit 94153af

File tree

4 files changed

+459
-44
lines changed

4 files changed

+459
-44
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
namespace Umbraco.Deploy.Contrib.Connectors.Caching.Comparers
2+
{
3+
4+
// Namespaces.
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
/// <summary>
9+
/// Compares an array of strings.
10+
/// </summary>
11+
/// <remarks>
12+
/// Based on: https://github.com/rhythmagency/rhythm.caching.core/blob/master/src/Rhythm.Caching.Core/Comparers/StringArrayComparer.cs
13+
/// </remarks>
14+
public class StringArrayComparer : IEqualityComparer<string[]>
15+
{
16+
17+
#region Methods
18+
19+
/// <summary>
20+
/// Check if the arrays are equal.
21+
/// </summary>
22+
/// <param name="x">
23+
/// The first array.
24+
/// </param>
25+
/// <param name="y">
26+
/// The second array.
27+
/// </param>
28+
/// <returns>
29+
/// True, if the arrays are both null, are both empty, or both have
30+
/// the same strings in the same order; otherwise, false.
31+
/// </returns>
32+
public bool Equals(string[] x, string[] y)
33+
{
34+
if (x == null || y == null)
35+
{
36+
return x == null && y == null;
37+
}
38+
if (x.Length != y.Length)
39+
{
40+
return false;
41+
}
42+
for (var i = 0; i < x.Length; i++)
43+
{
44+
if (x[i] != y[i])
45+
{
46+
return false;
47+
}
48+
}
49+
return true;
50+
}
51+
52+
/// <summary>
53+
/// Generates a hash code by combining all of the hash codes for the strings in the array.
54+
/// </summary>
55+
/// <param name="items">
56+
/// The array of strings.
57+
/// </param>
58+
/// <returns>
59+
/// The combined hash code.
60+
/// </returns>
61+
public int GetHashCode(string[] items)
62+
{
63+
if (items == null || !items.Any())
64+
{
65+
return 0;
66+
}
67+
else
68+
{
69+
var hashCode = default(int);
70+
foreach (var item in items)
71+
{
72+
hashCode ^= (item ?? string.Empty).GetHashCode();
73+
}
74+
return hashCode;
75+
}
76+
}
77+
78+
#endregion
79+
80+
}
81+
82+
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
namespace Umbraco.Deploy.Contrib.Connectors.Caching
2+
{
3+
4+
// Namespaces.
5+
using Comparers;
6+
using System;
7+
using System.Collections.Generic;
8+
9+
/// <summary>
10+
/// Caches instance variables by key in a dictionary-like structure.
11+
/// </summary>
12+
/// <typeparam name="T">
13+
/// The type of value to cache.
14+
/// </typeparam>
15+
/// <typeparam name="TKey">
16+
/// The key to use when access values in the dictionary.
17+
/// </typeparam>
18+
/// <remarks>
19+
/// Adapted from: https://github.com/rhythmagency/rhythm.caching.core/blob/master/src/Rhythm.Caching.Core/Caches/InstanceByKeyCache.cs
20+
/// </remarks>
21+
public class InstanceByKeyCache<T, TKey>
22+
{
23+
24+
#region Static Variables
25+
26+
private static string[] EmptyArray = new string[] { };
27+
28+
#endregion
29+
30+
#region Properties
31+
32+
/// <summary>
33+
/// The instances stored by their key, then again by a contextual key.
34+
/// </summary>
35+
private Dictionary<TKey, Tuple<Dictionary<string[], T>, DateTime>> Instances { get; set; }
36+
37+
/// <summary>
38+
/// Object to perform locks for cross-thread safety.
39+
/// </summary>
40+
private object InstancesLock { get; set; }
41+
42+
#endregion
43+
44+
#region Constructors
45+
46+
/// <summary>
47+
/// Default constructor.
48+
/// </summary>
49+
public InstanceByKeyCache()
50+
{
51+
InstancesLock = new object();
52+
Instances = new Dictionary<TKey, Tuple<Dictionary<string[], T>, DateTime>>();
53+
}
54+
55+
#endregion
56+
57+
#region Methods
58+
59+
/// <summary>
60+
/// Gets the instance variable (either from the cache or from the specified function).
61+
/// </summary>
62+
/// <param name="key">
63+
/// The key to use when fetching the variable.
64+
/// </param>
65+
/// <param name="replenisher">
66+
/// The function that replenishes the cache.
67+
/// </param>
68+
/// <param name="duration">
69+
/// The duration to cache for.
70+
/// </param>
71+
/// <param name="method">
72+
/// Optional. The cache method to use when retrieving the value.
73+
/// </param>
74+
/// <param name="keys">
75+
/// Optional. The keys to store/retrieve a value by. Each key combination will
76+
/// be treated as a separate cache.
77+
/// </param>
78+
/// <returns>
79+
/// The value.
80+
/// </returns>
81+
public T Get(TKey key, Func<TKey, T> replenisher, TimeSpan duration, params string[] keys)
82+
{
83+
lock (InstancesLock)
84+
{
85+
86+
// Variables.
87+
var tempInstance = default(T);
88+
var now = DateTime.Now;
89+
90+
// Value already cached?
91+
var tempTuple = default(Tuple<Dictionary<string[], T>, DateTime>);
92+
if (Instances.TryGetValue(key, out tempTuple)
93+
&& tempTuple.Item1.ContainsKey(keys))
94+
{
95+
if (now.Subtract(Instances[key].Item2) >= duration)
96+
{
97+
98+
// Cache expired. Replenish the cache.
99+
tempInstance = replenisher(key);
100+
UpdateValueByKeys(keys, key, tempInstance, now, false);
101+
102+
}
103+
else
104+
{
105+
106+
// Cache still valid. Use cached value.
107+
tempInstance = TryGetByKeys(keys, key);
108+
109+
}
110+
}
111+
else
112+
{
113+
114+
// No cached value. Replenish the cache.
115+
tempInstance = replenisher(key);
116+
UpdateValueByKeys(keys, key, tempInstance, now, false);
117+
118+
}
119+
120+
// Return the instance.
121+
return tempInstance;
122+
123+
}
124+
}
125+
126+
/// <summary>
127+
/// Clears the cache.
128+
/// </summary>
129+
public void Clear()
130+
{
131+
lock (InstancesLock)
132+
{
133+
Instances.Clear();
134+
}
135+
}
136+
137+
/// <summary>
138+
/// Clears the cache of the specified keys.
139+
/// </summary>
140+
/// <param name="keys">
141+
/// The keys to clear the cache of.
142+
/// </param>
143+
public void ClearKeys(IEnumerable<TKey> keys)
144+
{
145+
lock (InstancesLock)
146+
{
147+
foreach (var key in keys)
148+
{
149+
Instances.Remove(key);
150+
}
151+
}
152+
}
153+
154+
#endregion
155+
156+
#region Private Methods
157+
158+
/// <summary>
159+
/// Trys to get the value by the specified keys.
160+
/// </summary>
161+
/// <param name="keys">
162+
/// The keys.
163+
/// </param>
164+
/// <param name="accessKey">
165+
/// The key to use to access the value.
166+
/// </param>
167+
/// <returns>
168+
/// The value, or the default for the type.
169+
/// </returns>
170+
private T TryGetByKeys(string[] keys, TKey accessKey)
171+
{
172+
var chosenKeys = keys ?? EmptyArray;
173+
var value = default(T);
174+
lock (InstancesLock)
175+
{
176+
var valueDictionary = default(Tuple<Dictionary<string[], T>, DateTime>);
177+
if (Instances.TryGetValue(accessKey, out valueDictionary))
178+
{
179+
if (valueDictionary.Item1.TryGetValue(chosenKeys, out value))
180+
{
181+
return value;
182+
}
183+
}
184+
}
185+
return default(T);
186+
}
187+
188+
/// <summary>
189+
/// Updates the cache value by the specified keys.
190+
/// </summary>
191+
/// <param name="keys">
192+
/// The keys to cache by.
193+
/// </param>
194+
/// <param name="accessKey">
195+
/// The key to use to access the value.
196+
/// </param>
197+
/// <param name="value">
198+
/// The value to update the cache with.
199+
/// </param>
200+
/// <param name="lastCache">
201+
/// The date/time to mark the cache as last updated.
202+
/// </param>
203+
/// <param name="doLock">
204+
/// Lock the instance cache during the update?
205+
/// </param>
206+
private void UpdateValueByKeys(string[] keys, TKey accessKey, T value,
207+
DateTime lastCache, bool doLock = true)
208+
{
209+
if (doLock)
210+
{
211+
lock (InstancesLock)
212+
{
213+
UpdateValueByKeysWithoutLock(keys, accessKey, value, lastCache);
214+
}
215+
}
216+
else
217+
{
218+
UpdateValueByKeysWithoutLock(keys, accessKey, value, lastCache);
219+
}
220+
}
221+
222+
/// <summary>
223+
/// Updates the cache with the specified value.
224+
/// </summary>
225+
/// <param name="keys">
226+
/// The keys to cache by.
227+
/// </param>
228+
/// <param name="accessKey">
229+
/// The key to use to access the value.
230+
/// </param>
231+
/// <param name="value">
232+
/// The value to update the cache with.
233+
/// </param>
234+
private void UpdateValueByKeysWithoutLock(string[] keys, TKey accessKey,
235+
T value, DateTime lastCache)
236+
{
237+
238+
// Variables.
239+
var instanceTuple = default(Tuple<Dictionary<string[], T>, DateTime>);
240+
var instanceDictionary = default(Dictionary<string[], T>);
241+
242+
// Get or create the dictionary.
243+
if (Instances.TryGetValue(accessKey, out instanceTuple))
244+
{
245+
instanceDictionary = instanceTuple.Item1;
246+
}
247+
else
248+
{
249+
instanceDictionary = new Dictionary<string[], T>(new StringArrayComparer());
250+
}
251+
252+
// Update the value in the dictionary.
253+
instanceDictionary[keys] = value;
254+
255+
// Update the last cache date.
256+
Instances[accessKey] = new Tuple<Dictionary<string[], T>, DateTime>(
257+
instanceDictionary, lastCache);
258+
259+
}
260+
261+
#endregion
262+
263+
}
264+
265+
}

src/Umbraco.Deploy.Contrib.Connectors/Umbraco.Deploy.Contrib.Connectors.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
</Reference>
6767
</ItemGroup>
6868
<ItemGroup>
69+
<Compile Include="Caching\Comparers\StringArrayComparer.cs" />
70+
<Compile Include="Caching\InstanceByKeyCache.cs" />
6971
<Compile Include="GridCellValueConnectors\DocTypeGridEditorCellValueConnector.cs" />
7072
<Compile Include="GridCellValueConnectors\LeBlenderGridCellValueConnector.cs" />
7173
<Compile Include="Properties\AssemblyInfo.cs" />

0 commit comments

Comments
 (0)