The following test fails intermittently. It caches an item in MemoryCache with an absolute expiration time, and an update callback that should be called before the item is removed. However sometimes the callback is invoked before the test finishes, and sometimes not at all.
With a large enough buffer time it will always be invoked at least once. But that does not serve my purposes, since I require that the cache always attempts to update the data before it expired.
Now in my real world scenario I will not have 10 second expiration time and granularity, but it still bothers me that this test fails intermittently.
Anyone have thoughts on why this is happening?
Note: Also intermittently fails with 60 second expiry and 5 second buffer.
using System;
using System.Runtime.Caching;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class MemoryCacheTest
{
private const double ExpiryInSeconds = 10;
private const double ExpiryBufferInSeconds = 5;
private readonly object updateItemCounterLock = new object();
private int updateItemCounter = 0;
[TestMethod]
public async Task MemoryCacheUpdateTest()
{
// Set item in cache with absolute expiration defined above
MemoryCache cache = MemoryCache.Default;
CacheItem cacheItem = new CacheItem("key", "value");
CacheItemPolicy cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.Now + TimeSpan.FromSeconds(ExpiryInSeconds),
UpdateCallback = new CacheEntryUpdateCallback(this.UpdateItem)
};
cache.Set(cacheItem, cacheItemPolicy);
// Delay for absolute expiration time + buffer
await Task.Delay(TimeSpan.FromSeconds(ExpiryInSeconds) + TimeSpan.FromSeconds(ExpiryBufferInSeconds));
// Test that the update callback was invoked once
Assert.AreEqual(1, updateItemCounter);
}
// Incrememnts the updateItemCounter
private void UpdateItem(CacheEntryUpdateArguments args)
{
lock (updateItemCounterLock)
{
updateItemCounter++;
}
}
}
I suppose calling new CacheEntryUpdateCallback is redundant. You can call:
UpdateCallback = new CacheEntryUpdateCallback(this.UpdateItem) instead
Since there was no solution to this question, I abstracted the MemoryCache methods that I needed into an interface and tested against that. At that point the test became invalid because I would have just been testing my own implementation of the interface.
Related
I have a web service (ItemWebService) which calls an API and get a list of items (productList). This service is called from a UWP application.
Requirements are:
Cache the list of products for a certain time period (eg: 1 hour) and return the cached list if available and not timed out when called the GetProductListAsync().
No need to cache each hour, because this process is going to be a very rare process and the UWP application is run across multiple devices in an organisation. Therefore if set an interval to cache, the API would get hundreds of requests at the same time each hour.
Whenever a new item is added to the product list from method AddProductAsync(AddProductRequest addProductRequest ), the cache should be refreshed.
To meet the above requirements, a customized cache was implemented in the ItemWebService.
using NodaTime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
namespace MyNamespace.Products
{
public class ItemWebService : IItemService
{
private readonly IApiRestClient _restClient;
private readonly string _serviceUrl = "api/products";
private static IEnumerable<ProductListItem> _cachedProductist = null;
private readonly IClock _clock;
private readonly Duration _productlistValidFor = Duration.FromHours(1); // Set the timeout
private static Instant _lastUpdate = Instant.MinValue;
public ItemWebService (IApiRestClient restClient)
{
_restClient = restClient;
_clock = SystemClock.Instance; // using NodaTime
}
public async Task AddProductAsync(AddProductRequest addProductRequest)
{
await _restClient.Put($"{_serviceUrl}/add", addProductRequest);
// Expire cache manually to update product list on next call
_lastUpdate = _clock.GetCurrentInstant() - _productlistValidFor ;
}
public async Task<IObservable<ProductListItem>> GetProductListAsync()
{
if (_cachedProductist == null || (_lastUpdate + _productlistValidFor) < _clock.GetCurrentInstant())
{
_cachedProductist = await _restClient.Get<IEnumerable<ProductListItem>>($"{_serviceUrl}/productList");
// Update the last updated time
_lastUpdate = _clock.GetCurrentInstant();
}
return _cachedProductist.ToObservable();
}
}
}
With this implementation I was able to avoid setting an interval which would have cause hundreds of API calls(because there are hundreds of devices running the same app) to refresh cache each hour.
Now, whenever the device running the UWP app requests a list of products, the service would check if cache exists and not expired on that device and calls the server to refresh cache if necessary.
In my web application I need to cache some data as they are required frequently
but changes less often. To hold them I have made a sepearate static class which hold these field as static values. These field get initialized on first call. see a sample below.
public static class gtu
{
private static string mostsearchpagedata = "";
public static string getmostsearchpagedata()
{
if (mostsearchpagedata == "")
{
using (WebClient client = new WebClient())
{
mostsearchpagedata = client.DownloadString("https://xxx.yxc");
}
}
return mostsearchpagedata;
}
}
Here webrequest is only made one time, it works ok but if they are called in quick succession when there are large no. of users and apppool has restarted,
webrequest is made multiple times depending upon mostsearchpagedata was initialized or not.
How can I make sure that webrequest happens only one time, and all other request wait till the completion of the first webrequest?
You could use System.Lazy<T>:
public static class gtu
{
private static readonly Lazy<string> mostsearchedpagedata =
new Lazy<string>(
() => {
using (WebClient client = new WebClient())
{
mostsearchpagedata =
client.DownloadString("https://xxx.yxc");
}
},
// See https://msdn.microsoft.com/library/system.threading.lazythreadsafetymode(v=vs.110).aspx for more info
// on the relevance of this.
// Hint: since fetching a web page is potentially
// expensive you really want to do it only once.
LazyThreadSafeMode.ExecutionAndPublication
);
// Optional: provide a "wrapper" to hide the fact that Lazy is used.
public static string MostSearchedPageData => mostsearchedpagedata.Value;
}
In short, the lambda code (your DownloadString essentially) will be called, when the first thread calls .Value on the Lazy-instance. Other threads will either do the same or wait for the first thread to finish (see LazyThreadSafeMode for more info). Subsequent invocations of the Value-property will get the value already stored in the Lazy-instance.
I am writing an in-memory cache (for lack of a better term) in C#. Writing the cache was the easy part, but testing it is another...
All of my research into testing classes that use timers is to mock the timer and inject it into the class. With this, a new timer needs to be initialized for each order that is added to the cache. Passing a timer into the Add function would solve this, but classes consuming the OrderCache shouldn't be responsible for passing a timer to it.
I need to verify that
the timer has been initialized
Remove is called for the proper order after the specified duration
the order is updated in the Dictionary and no timer is created when adding the same order twice
private readonly Dictionary<int, Order> _orders;
private TimeSpan _cacheDuration;
public OrderCache(TimeSpan cacheDuration)
{
_cacheDuration = cacheDuration;
_orders = new Dictionary<int, Order>();
}
public void Add(Order order)
{
var cachedOrder = GetOrderById(order.Id);
if (cachedOrder == null)
{
_orders.Add(order.Id, order);
var timer = new Timer(_cacheDuration.TotalMilliseconds);
timer.Elapsed += (sender, args) => Remove(order.Id);
}
else
{
_orders[order.Id] = order;
}
}
public Order GetOrderById(int orderId)
{
return _orders.ContainsKey(orderId) ? _orders[orderId] : null;
}
public void Remove(int orderId)
{
if (_orders.ContainsKey(orderId))
{
_orders.Remove(orderId);
}
}
A: the timer has been initialized
Q: You should avoid White-Box Testing, because if internal implementation changed, but not behavior, interface or contract, you should rewrite test again.
A: Remove is called for the proper order after the specified duration
Q: You can write test:
[Test]
void Should_remove_...()
{
MockTimer timer = new MockTimer();
MyCache cache = new MyCache(timer);
DateTime expiredAt = DateTime.Now.Add(..);
cache.Add("key", "value", expiredAt);
timer.SetTime(expiredAt);
Assert.That(cache, Is.Empty)
}
PS: I recommend the use http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache.aspx
With that being said :):
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
#JonSkeet's advise is the way to go - either cleanup the cache on each poll (if happens often), or have a thread to cleanup regularly.
If you still think to preserve your approach, instead of passing in a timer into the cache, pass ITimerFactory. Then each Add call can use the factory to create a timer, and set it accordingly.
You can mock the factory to produce mock timer and set it to the proper time. which you can control for your tests.
Your test will have the form (pseudo):
var timer = new Mock<ITimer>();
var factory = new Mock<ITimerFactory>();
factory.Setup(f => f.CreateTimer()).Returns(timer.Object);
....
....
timer.Verify(t => t.SetTime(expiredAt), Times.Once());
Here is my code:
public class ConfigCache
{
private static volatile ObjectCache _cache = MemoryCache.Default;
private const string KeyModule = "MODULE_XDOC_KEY";
private static string _settingFile;
public ConfigCache(string file)
{
_settingFile = file;
}
public XDocument Get()
{
var doc = _cache[KeyModule] as XDocument;
if (doc == null)
{
doc = XDocument.Load(_settingFile);
var policy = new CacheItemPolicy();
var filePaths = new List<string> {_settingFile};
policy.ChangeMonitors.Add(new HostFileChangeMonitor(filePaths));
var callback = new CacheEntryRemovedCallback(this.MyCachedItemRemovedCallback);
policy.RemovedCallback = callback;
_cache.Set(KeyModule, doc, policy);
}
return _cache[KeyModule] as XDocument;
}
private void MyCachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
// Log these values from arguments list
}
}
When run into _cache.Set() first time, it works fine:
_cache.Set() works well, it add the xdoc into cache.
But after several minutes(1 or 2 minutes), cache will not work anymore:
_cache.Set() does not insert anything into cache
_cache.Set() does not report any error.
the callback MyCachedItemRemovedCallback never triggered.
Someone met same issue:
MemoryCache always returns "null" after first expiration
But it seems not resolved yet. Anyone have any idea on this?
Your problem might be that the cache is being disposed. This will result in the MemoryCache silently returning null rather than throwing any exception. First search your code to be sure you are not disposing it. If you are sure you aren't disposing it, try breaking on the AppDomain.UnhandledException event. MemoryCache subscribes to this event (see this answer and disposes itself. If you are handling the UnhandledException event in your app as a global error handler, this could be the problem.
If this is the issue, a simple workaround is to handle the event and recreate a new instance of the cache. (See my answer here)
I've got a simple object being cached like this:
_myCache.Add(someKey, someObj, policy);
Where _myCache is declared as ObjectCache (but injected via DI as MemoryCache.Default), someObj is the object i'm adding, and policy is a CacheItemPolicy.
If i have a CacheItemPolicy like this:
var policy = new CacheItemPolicy
{
Priority = CacheItemPriority.Default,
SlidingExpiration = TimeSpan.FromHours(1)
};
It means it will expire in 1 hour. Cool.
But what will happen is that unlucky first user after the hour will have to wait for the hit.
Is there any way i can hook into an "expired" event/delegate and manually refresh the cache?
I see there is a mention of CacheEntryChangeMonitor but can't find any meaninful doco/examples on how to utilize it in my example.
PS. I know i can use CacheItemPriority.NotRemovable and expire it manually, but i can't do that in my current example because the cached data is a bit too complicated (e.g i would need to "invalidate" in like 10 different places in my code).
Any ideas?
There's a property on the CacheItemPolicy called RemovedCallback which is of type: CacheEntryRemovedCallback. Not sure why they didn't go the standard event route, but that should do what you need.
http://msdn.microsoft.com/en-us/library/system.runtime.caching.cacheitempolicy.removedcallback.aspx
Late to the party with this one but I've just noticed an interesting difference between CacheItemUpdate and CacheItemRemove callbacks.
http://msdn.microsoft.com/en-us/library/system.web.caching.cacheitemupdatereason.aspx
In particular this comment:
Unlike the CacheItemRemovedReason enumeration, this enumeration does
not include the Removed or Underused values. Updateable cache items
are not removable and can thus never be automatically removed by
ASP.NET even if there is a need to free memory.
This is my way to use CacheRemovedCallback event when cache expired.
I share for whom concern.
public static void SetObjectToCache<T>(string cacheItemName, T obj, long expireTime)
{
ObjectCache cache = MemoryCache.Default;
var cachedObject = (T)cache[cacheItemName];
if (cachedObject != null)
{
// remove it
cache.Remove(cacheItemName);
}
CacheItemPolicy policy = new CacheItemPolicy()
{
AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(expireTime),
RemovedCallback = new CacheEntryRemovedCallback(CacheRemovedCallback)
};
cachedObject = obj;
cache.Set(cacheItemName, cachedObject, policy);
}
public static void CacheRemovedCallback(CacheEntryRemovedArguments arguments)
{
var configServerIpAddress = Thread.CurrentPrincipal.ConfigurationServerIpAddress();
long configId = Thread.CurrentPrincipal.ConfigurationId();
int userId = Thread.CurrentPrincipal.UserId();
var tagInfoService = new TagInfoService();
string returnCode = string.Empty;
if (arguments.CacheItem.Key.Contains("DatatableTags_"))
{
// do what's needed
Task.Run(() =>
{
});
}
}