public void HandlesCancellation()
        {
            var asyncLock = new AsyncLock();
            var lockOne = asyncLock.LockAsync();

            var cts = new CancellationTokenSource();
            var lockTwo = asyncLock.LockAsync(cts.Token);

            Assert.True(lockOne.IsCompleted);
            Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status);
            Assert.NotNull(lockOne.Result);

            Assert.False(lockTwo.IsCompleted);

            cts.Cancel();

            Assert.True(lockTwo.IsCompleted);
            Assert.True(lockTwo.IsCanceled);

            lockOne.Result.Dispose();
        }
        /// <summary>
        /// Starts the operation queue.
        /// </summary>
        /// <returns>A disposable which when Disposed will stop the queue.</returns>
        public IDisposable Start()
        {
            if (_start is not null)
            {
                return(_start);
            }

            _shouldQuit = new CancellationTokenSource();
            var task = Task.Run(async() =>
            {
                var toProcess = new List <OperationQueueItem>();

                while (!_shouldQuit.IsCancellationRequested)
                {
                    toProcess.Clear();

                    IDisposable? @lock;

                    try
                    {
                        @lock = await _flushLock.LockAsync(_shouldQuit.Token).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                        break;
                    }

                    // Verify lock was acquired
                    if (@lock is null)
                    {
                        break;
                    }

                    using (@lock)
                    {
                        // NB: We special-case the first item because we want to
                        // in the empty list case, we want to wait until we have an item.
                        // Once we have a single item, we try to fetch as many as possible
                        // until we've got enough items.
                        OperationQueueItem?item;
                        try
                        {
                            item = _operationQueue.Take(_shouldQuit.Token);
                        }
                        catch (OperationCanceledException)
                        {
                            break;
                        }

                        // NB: We explicitly want to bail out *here* because we
                        // never want to bail out in the middle of processing
                        // operations, to guarantee that we won't orphan them
                        if (_shouldQuit.IsCancellationRequested && item is null)
                        {
                            break;
                        }

                        toProcess.Add(item);
                        while (toProcess.Count < Constants.OperationQueueChunkSize && _operationQueue.TryTake(out item) && item is not null)
                        {
                            toProcess.Add(item);
                        }

                        try
                        {
                            ProcessItems(CoalesceOperations(toProcess));
                        }
                        catch (SQLiteException)
                        {
                            // NB: If ProcessItems Failed, it explicitly means
                            // that the "BEGIN TRANSACTION" failed and that items
                            // have **not** been processed. We should add them back
                            // to the queue
                            foreach (var v in toProcess)
                            {
                                _operationQueue.Add(v);
                            }
                        }
                    }
                }
            });

            return(_start = Disposable.Create(
                       () =>
            {
                try
                {
                    _shouldQuit.Cancel();
                    task.Wait();
                }
                catch (OperationCanceledException)
                {
                }

                try
                {
                    using (_flushLock.LockAsync().Result)
                    {
                        FlushInternal();
                    }
                }
                catch (OperationCanceledException)
                {
                }

                _start = null;
            }));
        }
Exemple #3
0
        public IDisposable Start()
        {
            if (start != null)
            {
                return(start);
            }

            shouldQuit = new CancellationTokenSource();
            var task = new Task(async() =>
            {
                var toProcess = new List <OperationQueueItem>();

                while (!shouldQuit.IsCancellationRequested)
                {
                    toProcess.Clear();

                    using (await flushLock.LockAsync(shouldQuit.Token))
                    {
                        // NB: We special-case the first item because we want to
                        // in the empty list case, we want to wait until we have an item.
                        // Once we have a single item, we try to fetch as many as possible
                        // until we've got enough items.
                        var item = default(OperationQueueItem);
                        try
                        {
                            item = operationQueue.Take(shouldQuit.Token);
                        }
                        catch (OperationCanceledException)
                        {
                            break;
                        }

                        // NB: We explicitly want to bail out *here* because we
                        // never want to bail out in the middle of processing
                        // operations, to guarantee that we won't orphan them
                        if (shouldQuit.IsCancellationRequested && item == null)
                        {
                            break;
                        }

                        toProcess.Add(item);
                        while (toProcess.Count < Constants.OperationQueueChunkSize && operationQueue.TryTake(out item))
                        {
                            toProcess.Add(item);
                        }

                        try
                        {
                            ProcessItems(CoalesceOperations(toProcess));
                        }
                        catch (SQLiteException)
                        {
                            // NB: If ProcessItems Failed, it explicitly means
                            // that the "BEGIN TRANSACTION" failed and that items
                            // have **not** been processed. We should add them back
                            // to the queue
                            foreach (var v in toProcess)
                            {
                                operationQueue.Add(v);
                            }
                        }
                    }
                }
            }, TaskCreationOptions.LongRunning);

            task.Start();

            return(start = Disposable.Create(() =>
            {
                try
                {
                    shouldQuit.Cancel();
                    task.Wait();
                }
                catch (OperationCanceledException) { }

                var newQueue = new BlockingCollection <OperationQueueItem>();
                ProcessItems(CoalesceOperations(Interlocked.Exchange(ref operationQueue, newQueue).ToList()));
                start = null;
            }));
        }