How the search results caching works
When a user enters a query
to search for:
- The query is split into an array of tokens
- A unique hash is created for this array of tokens (order tokens alphabetically then MD5). This is the unique identity of the search.
- Check cache for results based on hash
- If cache doesn't exist, save results to cache using hash
Problem I'm trying to solve
If a user performs a search that takes say 10 seconds, and they impatiently refresh the page we don't want it to start the query again. This should be locked.
However, if a expensive query is running, we don't want to lock out other users performing less expensive searches.
To solve this, I need multiple locks.
Implementation
This is how I've got it currently implemented:
private static readonly object MasterManualSearchLock = new object();
private static readonly Dictionary<string, object> ManualSearchLocks = new Dictionary<string, object>();
/// <summary>
/// Search the manual
/// </summary>
public static SearchResponse DoSearch(string query, Manual forManual)
{
var tokens = Search.Functions.TokeniseSearchQuery(query);
var tokenHash = Search.Functions.GetUniqueHashOfTokens(tokens);
var cacheIndex = Settings.CachePrefix + "SavedManualSearch_" + tokenHash;
var context = HttpContext.Current;
if (context.Cache[cacheIndex] == null)
{
// Create lock if it doesn't exist
if (!ManualSearchLocks.ContainsKey(tokenHash))
{
lock (MasterManualSearchLock)
{
if (!ManualSearchLocks.ContainsKey(tokenHash))
{
ManualSearchLocks.Add(tokenHash, new object());
}
}
}
lock (ManualSearchLocks[tokenHash])
{
if (context.Cache[cacheIndex] == null)
{
var searchResponse = new SearchResponse(tokens, forManual, query);
context.Cache.Add(cacheIndex, searchResponse, null, DateTime.Now.AddMinutes(Settings.Search.SearchResultsAbsoluteTimeoutMins), Cache.NoSlidingExpiration, CacheItemPriority.BelowNormal, null);
}
ManualSearchLocks.Remove(tokenHash);
}
}
return (SearchResponse)context.Cache[cacheIndex];
}
Questions
- Is this a sensible implementation?
- Is this thread safe?
- Is including the removal of the lock within the lock itself OK?