-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Threading/performance fixes - delegate value factory invocation guara…
…nteed to be atomic (due to that amount of computations related to redundant parsing are decreased).
- Loading branch information
Showing
8 changed files
with
206 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
src/ExpressiveAnnotations.MvcUnobtrusive.Tests/MapCacheTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
using System.Collections.Concurrent; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Threading; | ||
using Castle.Core.Internal; | ||
using ExpressiveAnnotations.MvcUnobtrusive.Caching; | ||
using Xunit; | ||
|
||
namespace ExpressiveAnnotations.MvcUnobtrusive.Tests | ||
{ | ||
public class MapCacheTest | ||
{ | ||
public MapCacheTest() | ||
{ | ||
MapCache<string, TestItem>.Clear(); | ||
} | ||
|
||
[Fact] | ||
public void verify_bahaviour_for_concurrent_access_under_different_keys() | ||
{ | ||
var keys = new[] {"a", "b"}; | ||
var counter = new ConcurrentStack<int>(); // value factory threads | ||
var storage = new ConcurrentStack<TestItem>(); // cached items | ||
|
||
// first run | ||
var threads = MakeThreads(keys); | ||
threads.ForEach(t => t.Start(new object[] {storage, counter})); | ||
threads.ForEach(t => t.Join()); | ||
|
||
Assert.Equal(2, counter.Count); | ||
Assert.Equal(2, storage.Count); | ||
Assert.NotSame(storage.First(), storage.Last()); | ||
var a = storage.FirstOrDefault(x => x.Id == "a"); | ||
var b = storage.FirstOrDefault(x => x.Id == "b"); | ||
|
||
// cleanups and second run | ||
storage.Clear(); | ||
counter.Clear(); | ||
|
||
threads = MakeThreads(keys); | ||
threads.ForEach(t => t.Start(new object[] {storage, counter})); | ||
threads.ForEach(t => t.Join()); | ||
|
||
Assert.Equal(0, counter.Count); | ||
Assert.Equal(2, storage.Count); | ||
Assert.NotSame(storage.First(), storage.Last()); | ||
var aa = storage.FirstOrDefault(x => x.Id == "a"); | ||
var bb = storage.FirstOrDefault(x => x.Id == "b"); | ||
Assert.Same(a, aa); | ||
Assert.Same(b, bb); | ||
} | ||
|
||
[Fact] | ||
public void verify_bahaviour_for_concurrent_access_under_identical_keys() | ||
{ | ||
var keys = new[] {"a", "a"}; | ||
var counter = new ConcurrentStack<int>(); | ||
var storage = new ConcurrentStack<TestItem>(); | ||
|
||
// first run | ||
var threads = MakeThreads(keys); | ||
threads.ForEach(t => t.Start(new object[] {storage, counter})); | ||
threads.ForEach(t => t.Join()); | ||
|
||
Assert.Equal(1, counter.Count); | ||
Assert.Equal(2, storage.Count); | ||
var a = storage.First(); | ||
Assert.Same(storage.First(), storage.Last()); | ||
|
||
// cleanups and second run | ||
storage.Clear(); | ||
counter.Clear(); | ||
|
||
threads = MakeThreads(keys); | ||
threads.ForEach(t => t.Start(new object[] {storage, counter})); | ||
threads.ForEach(t => t.Join()); | ||
|
||
Assert.Equal(0, counter.Count); | ||
Assert.Equal(2, storage.Count); | ||
var aa = storage.First(); | ||
Assert.Same(storage.First(), storage.Last()); | ||
Assert.Same(a, aa); | ||
} | ||
|
||
private Thread[] MakeThreads(string[] keys) | ||
{ | ||
var threads = keys.Select(key => | ||
new Thread(load => | ||
{ | ||
var storage = (ConcurrentStack<TestItem>) ((object[]) load)[0]; | ||
var counter = (ConcurrentStack<int>) ((object[]) load)[1]; | ||
|
||
var item = MapCache<string, TestItem>.GetOrAdd(key.ToString(), _ => | ||
{ | ||
Debug.WriteLine($"{key} :: {Thread.CurrentThread.ManagedThreadId}"); | ||
counter.Push(Thread.CurrentThread.ManagedThreadId); // we want to test that this value factory delegate is invoked only once, even if map is accessed concurrently for the same key | ||
Thread.Sleep(500); | ||
return new TestItem {Id = key}; | ||
}); | ||
storage.Push(item); | ||
})).ToArray(); | ||
return threads; | ||
} | ||
|
||
private class TestItem | ||
{ | ||
public string Id { get; set; } | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
src/ExpressiveAnnotations.MvcUnobtrusive/Caching/MapCache.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* https://github.com/jwaliszko/ExpressiveAnnotations | ||
* Copyright (c) 2014 Jarosław Waliszko | ||
* Licensed MIT: http://opensource.org/licenses/MIT */ | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
|
||
namespace ExpressiveAnnotations.MvcUnobtrusive.Caching | ||
{ | ||
/// <summary> | ||
/// Persists decomposed expressions parts for entire application instance. Implementation is concurrent and lazy. | ||
/// </summary> | ||
internal static class MapCache<TKey, TValue> // http://stackoverflow.com/q/3037203/270315 | ||
{ | ||
private static readonly ConcurrentDictionary<TKey, Lazy<TValue>> _cache = new ConcurrentDictionary<TKey, Lazy<TValue>>(); // why lazy? -> http://stackoverflow.com/q/12611167/270315, https://blogs.endjin.com/2015/10/using-lazy-and-concurrentdictionary-to-ensure-a-thread-safe-run-once-lazy-loaded-collection/ | ||
|
||
public static TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) // delegate value factory invocation guaranteed to be atomic | ||
{ | ||
var lazyResult = _cache.GetOrAdd( | ||
key, | ||
k => new Lazy<TValue>( | ||
() => valueFactory(k), | ||
LazyThreadSafetyMode.ExecutionAndPublication)); | ||
return lazyResult.Value; | ||
} | ||
|
||
public static void Clear() | ||
{ | ||
_cache.Clear(); | ||
} | ||
} | ||
|
||
internal class CacheItem | ||
{ | ||
public IDictionary<string, string> FieldsMap { get; set; } | ||
public IDictionary<string, object> ConstsMap { get; set; } | ||
public IDictionary<string, string> ParsersMap { get; set; } | ||
} | ||
} |
86 changes: 43 additions & 43 deletions
86
...otations.MvcUnobtrusive/RequestStorage.cs → ....MvcUnobtrusive/Caching/RequestStorage.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,43 @@ | ||
/* https://github.com/jwaliszko/ExpressiveAnnotations | ||
* Copyright (c) 2014 Jarosław Waliszko | ||
* Licensed MIT: http://opensource.org/licenses/MIT */ | ||
|
||
using System; | ||
using System.Collections; | ||
using System.Web; | ||
|
||
namespace ExpressiveAnnotations.MvcUnobtrusive | ||
{ | ||
/// <summary> | ||
/// Persists arbitrary data for the current HTTP request. | ||
/// </summary> | ||
internal static class RequestStorage | ||
{ | ||
private static IDictionary Items | ||
{ | ||
get | ||
{ | ||
if (HttpContext.Current == null) | ||
throw new ApplicationException("HttpContext not available."); | ||
return HttpContext.Current.Items; // location that could be used throughtout the entire HTTP request lifetime | ||
} // (contrary to a session, this one exists only within the period of a single request). | ||
} | ||
|
||
public static T Get<T>(string key) | ||
{ | ||
return Items[key] == null | ||
? default(T) | ||
: (T) Items[key]; | ||
} | ||
|
||
public static void Set<T>(string key, T value) | ||
{ | ||
Items[key] = value; | ||
} | ||
|
||
public static void Remove(string key) | ||
{ | ||
Items.Remove(key); | ||
} | ||
} | ||
} | ||
/* https://github.com/jwaliszko/ExpressiveAnnotations | ||
* Copyright (c) 2014 Jarosław Waliszko | ||
* Licensed MIT: http://opensource.org/licenses/MIT */ | ||
|
||
using System; | ||
using System.Collections; | ||
using System.Web; | ||
|
||
namespace ExpressiveAnnotations.MvcUnobtrusive.Caching | ||
{ | ||
/// <summary> | ||
/// Persists arbitrary data for the current HTTP request. | ||
/// </summary> | ||
internal static class RequestStorage | ||
{ | ||
private static IDictionary Items | ||
{ | ||
get | ||
{ | ||
if (HttpContext.Current == null) | ||
throw new ApplicationException("HttpContext not available."); | ||
return HttpContext.Current.Items; // location that could be used throughtout the entire HTTP request lifetime | ||
} // (contrary to a session, this one exists only within the period of a single request). | ||
} | ||
|
||
public static T Get<T>(string key) | ||
{ | ||
return Items[key] == null | ||
? default(T) | ||
: (T) Items[key]; | ||
} | ||
|
||
public static void Set<T>(string key, T value) | ||
{ | ||
Items[key] = value; | ||
} | ||
|
||
public static void Remove(string key) | ||
{ | ||
Items.Remove(key); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters