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)); @lock = await lockTask; 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; } finally { if (@lock != null) { @lock.Dispose(); } } }) .ToObservable() .ObserveOn(scheduler) .Multicast(ret) .PermaRef(); return(ret); }
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); }
void ProcessItems(List <OperationQueueItem> toProcess) { var commitResult = new AsyncSubject <Unit>(); begin.PrepareToExecute()(); foreach (var item in toProcess) { switch (item.OperationType) { case OperationType.DoNothing: break; case OperationType.BulkInsertSqliteOperation: MarshalCompletion(item.Completion, bulkInsertKey.PrepareToExecute(item.ParametersAsElements), commitResult); break; case OperationType.BulkInvalidateByTypeSqliteOperation: MarshalCompletion(item.Completion, bulkInvalidateType.PrepareToExecute(item.ParametersAsKeys), commitResult); break; case OperationType.BulkInvalidateSqliteOperation: MarshalCompletion(item.Completion, bulkInvalidateKey.PrepareToExecute(item.ParametersAsKeys), commitResult); break; case OperationType.BulkSelectByTypeSqliteOperation: MarshalCompletion(item.Completion, bulkSelectType.PrepareToExecute(item.ParametersAsKeys), commitResult); break; case OperationType.BulkSelectSqliteOperation: MarshalCompletion(item.Completion, bulkSelectKey.PrepareToExecute(item.ParametersAsKeys), commitResult); break; case OperationType.GetKeysSqliteOperation: MarshalCompletion(item.Completion, getAllKeys.PrepareToExecute(), commitResult); break; case OperationType.InvalidateAllSqliteOperation: MarshalCompletion(item.Completion, invalidateAll.PrepareToExecute(), commitResult); break; case OperationType.VacuumSqliteOperation: MarshalCompletion(item.Completion, vacuum.PrepareToExecute(), commitResult); break; default: throw new ArgumentException("Unknown operation"); } } try { commit.PrepareToExecute()(); // NB: We do this in a scheduled result to stop a deadlock in // First and friends scheduler.Schedule(() => { commitResult.OnNext(Unit.Default); commitResult.OnCompleted(); }); } catch (Exception ex) { scheduler.Schedule(() => commitResult.OnError(ex)); } }