/// <summary> /// Invoke the passed <see cref="IEntryProcessor"/> against the /// entries specified by the passed cache and entries. /// </summary> /// <remarks> /// The invocation is made thread safe by locking the corresponding /// keys on the cache. If an attempt to lock all the entries at once /// fails, they will be processed individually one-by-one. /// </remarks> /// <param name="cache"> /// The <see cref="IConcurrentCache"/> that the /// <b>IEntryProcessor</b> works against. /// </param> /// <param name="entries"> /// A collection of <see cref="IInvocableCacheEntry"/> objects to /// process. /// </param> /// <param name="agent"> /// The <b>IEntryProcessor</b> to use to process the specified keys. /// </param> /// <returns> /// An <b>IDictionary</b> containing the results of invoking the /// <b>IEntryProcessor</b> against each of the specified entry. /// </returns> public static IDictionary InvokeAllLocked(IConcurrentCache cache, ICollection entries, IEntryProcessor agent) { ICollection keys = ConverterCollections.GetCollection(entries, ENTRY_TO_KEY_CONVERTER, NullImplementation.GetConverter()); // try to lock them all at once var listLocked = LockAll(cache, keys, 0); if (listLocked == null) { // the attempt failed; do it one-by-one var result = new HashDictionary(entries.Count); foreach (IInvocableCacheEntry entry in entries) { result[entry.Key] = InvokeLocked(cache, entry, agent); } return(result); } try { return(agent.ProcessAll(entries)); } finally { UnlockAll(cache, listLocked); } }
/// <summary> /// Unlock all the specified keys. /// </summary> /// <param name="cache"> /// The <see cref="IConcurrentCache"/> to use. /// </param> /// <param name="keys"> /// A collection of keys to unlock. /// </param> public static void UnlockAll(IConcurrentCache cache, ICollection keys) { foreach (var key in keys) { cache.Unlock(key); } }
/// <summary> /// Attempt to lock all the specified keys within a specified period /// of time. /// </summary> /// <param name="cache"> /// The <see cref="IConcurrentCache"/> to use. /// </param> /// <param name="keys"> /// A collection of keys to lock. /// </param> /// <param name="waitMillis"> /// The number of milliseconds to continue trying to obtain locks; /// pass zero to return immediately; pass -1 to block the calling /// thread until the lock could be obtained. /// </param> /// <returns> /// An <b>IList</b> containing all the locked keys in the order /// opposite to the locking order (LIFO); <c>null</c> if timeout has /// occurred. /// </returns> public static IList LockAll(IConcurrentCache cache, ICollection keys, int waitMillis) { // remove the duplicates HashSet setKeys = keys is HashSet ? (HashSet)keys : new HashSet(keys); // copy the keys into a list to fully control the iteration order var listKeys = new ArrayList(setKeys); var listLocked = new ArrayList(); int keysCount = listKeys.Count; bool isSuccess = true; do { int waitNextMillis = waitMillis; // allow blocking wait for the very first key for (int i = 0; i < keysCount; i++) { var key = listKeys[i]; isSuccess = cache.Lock(key, waitNextMillis); if (isSuccess) { // add the last locked item into the front of the locked // list so it behaves as a stack (FILO strategy) listLocked.Insert(0, key); // to prevent a deadlock don't wait afterwards waitNextMillis = 0; } else { if (i == 0) { // the very first key cannot be locked -- timeout return(null); } // unlock all we hold and try again foreach (var o in listLocked) { cache.Unlock(o); } listLocked.Clear(); // move the "offending" key to the top of the list // so next iteration we will attempt to lock it first listKeys.RemoveAt(i); listKeys.Insert(0, key); } } }while (!isSuccess); return(listLocked); }
/// <summary> /// Invoke the passed <see cref="IEntryProcessor"/> against the /// specified <see cref="IInvocableCacheEntry"/>. /// </summary> /// <remarks> /// The invocation is made thread safe by locking the corresponding key /// on the cache. /// </remarks> /// <param name="cache"> /// The <see cref="IConcurrentCache"/> that the /// <b>IEntryProcessor</b> works against. /// </param> /// <param name="entry"> /// The <b>IInvocableCacheEntry</b> to process; it is not required to /// exist within the cache. /// </param> /// <param name="agent"> /// The <b>IEntryProcessor</b> to use to process the specified key. /// </param> /// <returns> /// The result of the invocation as returned from the /// <b>IEntryProcessor</b>. /// </returns> public static object InvokeLocked(IConcurrentCache cache, IInvocableCacheEntry entry, IEntryProcessor agent) { var key = entry.Key; cache.Lock(key, -1); try { return(agent.Process(entry)); } finally { cache.Unlock(key); } }
public void ConverterConcurrentCacheTests() { IConcurrentCache cache = InstantiateCache(); IConverter cDown = new ConvertDown(); IConverter cUp = new ConvertUp(); IConcurrentCache convCache = ConverterCollections.GetConcurrentCache(cache, cUp, cDown, cUp, cDown); Assert.IsNotNull(convCache); Assert.IsInstanceOf(typeof(ConverterCollections.ConverterConcurrentCache), convCache); ConverterCollections.ConverterConcurrentCache cc = convCache as ConverterCollections.ConverterConcurrentCache; Assert.IsNotNull(cc); Assert.AreEqual(cc.ConcurrentCache, cache); }
/// <summary> /// Creates an instance of RequestProcessor. /// </summary> /// <param name="cache">The cache from which the requests will be served.</param> public RequestProcessor(IConcurrentCache <string, string> cache) { _cache = cache ?? throw new ArgumentNullException($"{nameof(cache)} cannot be null."); }