static string GetKeyFromTuple(OperationQueueItem item) { // NB: This method assumes that the input tuples only have a // single item, which the OperationQueue input methods guarantee switch (item.OperationType) { case OperationType.BulkInsertSqliteOperation: return((item.ParametersAsElements).First().Key); case OperationType.BulkInvalidateByTypeSqliteOperation: case OperationType.BulkInvalidateSqliteOperation: case OperationType.BulkSelectSqliteOperation: case OperationType.BulkSelectByTypeSqliteOperation: return((item.ParametersAsKeys).First()); case OperationType.GetKeysSqliteOperation: case OperationType.InvalidateAllSqliteOperation: case OperationType.VacuumSqliteOperation: case OperationType.DeleteExpiredSqliteOperation: case OperationType.DoNothing: return(default(string)); default: throw new ArgumentException("Unknown operation"); } }
public AsyncSubject <Unit> Vacuum() { // Vacuum is a special snowflake. We want to delete all the expired rows before // actually vacuuming. Unfortunately vacuum can't be run in a transaction so we'll // claim an exclusive lock on the queue, drain it and run the delete first before // running our vacuum op without any transactions. var ret = new AsyncSubject <Unit>(); Task.Run(async() => { IDisposable @lock = null; try { // NB. While the documentation for SemaphoreSlim (which powers AsyncLock) // doesn't guarantee ordering the actual (current) implementation[1] // uses a linked list to queue incoming requests so by adding ourselves // to the queue first and then sending a no-op to the main queue to // force it to finish up and release the lock we avoid any potential // race condition where the main queue reclaims the lock before we // have had a chance to acquire it. // // 1. http://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,d57f52e0341a581f var lockTask = flushLock.LockAsync(shouldQuit.Token); operationQueue.Add(OperationQueueItem.CreateUnit(OperationType.DoNothing)); try { @lock = await lockTask; } catch (OperationCanceledException) { } var deleteOp = OperationQueueItem.CreateUnit(OperationType.DeleteExpiredSqliteOperation); operationQueue.Add(deleteOp); FlushInternal(); await deleteOp.CompletionAsUnit; var vacuumOp = OperationQueueItem.CreateUnit(OperationType.VacuumSqliteOperation); MarshalCompletion(vacuumOp.Completion, vacuum.Value.PrepareToExecute(), Observable.Return(Unit.Default)); await vacuumOp.CompletionAsUnit; } finally { if (@lock != null) { @lock.Dispose(); } } }) .ToObservable() .ObserveOn(scheduler) .Multicast(ret) .PermaRef(); return(ret); }
public AsyncSubject <IEnumerable <string> > GetAllKeys() { var ret = OperationQueueItem.CreateGetAllKeys(); _operationQueue.Add(ret); return(ret.CompletionAsKeys); }
public AsyncSubject <Unit> InvalidateAll() { var ret = OperationQueueItem.CreateUnit(OperationType.InvalidateAllSqliteOperation); _operationQueue.Add(ret); return(ret.CompletionAsUnit); }
public AsyncSubject <Unit> InvalidateTypes(IEnumerable <string> types) { var ret = OperationQueueItem.CreateInvalidate(OperationType.BulkInvalidateByTypeSqliteOperation, types); _operationQueue.Add(ret); return(ret.CompletionAsUnit); }
public AsyncSubject <Unit> Insert(IEnumerable <CacheElement> items) { var ret = OperationQueueItem.CreateInsert(OperationType.BulkInsertSqliteOperation, items); _operationQueue.Add(ret); return(ret.CompletionAsUnit); }
public AsyncSubject <IEnumerable <CacheElement> > SelectTypes(IEnumerable <string> types) { var ret = OperationQueueItem.CreateSelect(OperationType.BulkSelectByTypeSqliteOperation, types); _operationQueue.Add(ret); return(ret.CompletionAsElements); }
public IObservable <Unit> Flush() { var noop = OperationQueueItem.CreateUnit(OperationType.DoNothing); _operationQueue.Add(noop); return(noop.CompletionAsUnit); }
public AsyncSubject <Unit> Vacuum() { var ret = OperationQueueItem.CreateUnit(OperationType.VacuumSqliteOperation); operationQueue.Add(ret); return(ret.CompletionAsUnit); }
static OperationQueueItem GroupUnrelatedSelects(IEnumerable <OperationQueueItem> unrelatedSelects) { var elementMap = new Dictionary <string, AsyncSubject <IEnumerable <CacheElement> > >(); if (unrelatedSelects.Count() == 1) { return(unrelatedSelects.First()); } foreach (var v in unrelatedSelects) { var key = v.ParametersAsKeys.First(); elementMap[key] = v.CompletionAsElements; } var ret = OperationQueueItem.CreateSelect(OperationType.BulkSelectSqliteOperation, elementMap.Keys); ret.CompletionAsElements.Subscribe(items => { var resultMap = items.ToDictionary(k => k.Key, v => v); foreach (var v in elementMap.Keys) { try { if (resultMap.ContainsKey(v)) { elementMap[v].OnNext(EnumerableEx.Return(resultMap[v])); } else { elementMap[v].OnNext(Enumerable.Empty <CacheElement>()); } elementMap[v].OnCompleted(); } catch (KeyNotFoundException e) { // I don't know what to do here but since an exception is swallowed anyway, // lets not stop the remaining elements to be stuck in an incompleted way } } }, ex => { foreach (var v in elementMap.Values) { v.OnError(ex); } }, () => { foreach (var v in elementMap.Values) { v.OnCompleted(); } }); return(ret); }
static OperationQueueItem GroupUnrelatedSelects(IEnumerable <OperationQueueItem> unrelatedSelects) { var elementMap = new Dictionary <string, AsyncSubject <IEnumerable <CacheElement> > >(); if (unrelatedSelects.Count() == 1) { return(unrelatedSelects.First()); } foreach (var v in unrelatedSelects) { var key = v.ParametersAsKeys.First(); elementMap[key] = v.CompletionAsElements; } var ret = OperationQueueItem.CreateSelect(OperationType.BulkSelectSqliteOperation, elementMap.Keys); ret.CompletionAsElements.Subscribe(items => { var resultMap = items.ToDictionary(k => k.Key, v => v); foreach (var v in elementMap.Keys) { if (resultMap.ContainsKey(v)) { elementMap[v].OnNext(EnumerableEx.Return(resultMap[v])); } else { elementMap[v].OnNext(Enumerable.Empty <CacheElement>()); } elementMap[v].OnCompleted(); } }, ex => { foreach (var v in elementMap.Values) { v.OnError(ex); } }, () => { foreach (var v in elementMap.Values) { v.OnCompleted(); } }); return(ret); }
static OperationQueueItem GroupUnrelatedInserts(IEnumerable <OperationQueueItem> unrelatedInserts) { if (unrelatedInserts.Count() == 1) { return(unrelatedInserts.First()); } var subj = new AsyncSubject <Unit>(); var elements = unrelatedInserts.SelectMany(x => { subj.Subscribe(x.CompletionAsUnit); return(x.ParametersAsElements); }).ToList(); return(OperationQueueItem.CreateInsert( OperationType.BulkInsertSqliteOperation, elements, subj)); }
public IObservable <Unit> Flush() { var ret = new AsyncSubject <Unit>(); return(Task.Run(async() => { // NB: We add a "DoNothing" operation so that the thread waiting // on an item will always have one instead of waiting the full timeout // before we can run the flush operationQueue.Add(OperationQueueItem.CreateUnit(OperationType.DoNothing)); using (await flushLock.LockAsync()) { FlushInternal(); } }).ToObservable()); }
private static OperationQueueItem GroupUnrelatedDeletes(IEnumerable <OperationQueueItem> unrelatedDeletes) { var subj = new AsyncSubject <Unit>(); var operationQueueItems = unrelatedDeletes.ToList(); if (operationQueueItems.Count == 1) { return(operationQueueItems[0]); } var elements = operationQueueItems.SelectMany(x => { subj.Subscribe(x.CompletionAsUnit); return(x.ParametersAsKeys); }).ToList(); return(OperationQueueItem.CreateInvalidate( OperationType.BulkInvalidateSqliteOperation, elements, subj)); }
public IObservable <Unit> Flush() { var ret = new AsyncSubject <Unit>(); return(Task.Run(async() => { // NB: We add a "DoNothing" operation so that the thread waiting // on an item will always have one instead of waiting the full timeout // before we can run the flush operationQueue.Add(OperationQueueItem.CreateUnit(OperationType.DoNothing)); using (await flushLock.LockAsync()) { var newQueue = new BlockingCollection <OperationQueueItem>(); var existingItems = Interlocked.Exchange(ref operationQueue, newQueue).ToList(); ProcessItems(CoalesceOperations(existingItems)); } }).ToObservable()); }
public AsyncSubject <Unit> Vacuum() { // Vacuum is a special snowflake. We want to delete all the expired rows before // actually vacuuming. Unfortunately vacuum can't be run in a transaction so we'll // claim an exclusive lock on the queue, drain it and run the delete first before // running our vacuum op without any transactions. var ret = new AsyncSubject <Unit>(); Task.Run(async() => { // NB: We add a "DoNothing" operation so that the thread waiting // on an item will always have one instead of waiting the full timeout // before we can run the flush operationQueue.Add(OperationQueueItem.CreateUnit(OperationType.DoNothing)); using (await flushLock.LockAsync()) { var deleteOp = OperationQueueItem.CreateUnit(OperationType.DeleteExpiredSqliteOperation); operationQueue.Add(deleteOp); FlushInternal(); await deleteOp.CompletionAsUnit; var vacuumOp = OperationQueueItem.CreateUnit(OperationType.VacuumSqliteOperation); MarshalCompletion(vacuumOp.Completion, vacuum.PrepareToExecute(), Observable.Return(Unit.Default)); await vacuumOp.CompletionAsUnit; } }) .ToObservable() .ObserveOn(scheduler) .Multicast(ret) .PermaRef(); return(ret); }