예제 #1
0
        public async Task <T> ReadAsync(CancellationToken cancellationToken, Func <T, bool> acceptanceTest)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await readSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                T message;
                if (!writeQueue.TryDequeue(out message))
                {
                    throw new InvalidStateException();
                }
                if (acceptanceTest(message))
                {
                    writeSemaphore?.Release();
                    return(message);
                }
                else
                {
                    writeQueue.Enqueue(message);
                    readSemaphore.Release();
                }
            }
            // throw is guaranteed
            cancellationToken.ThrowIfCancellationRequested();
            return(default(T));
        }
예제 #2
0
        public void TestWaitRelease()
        {
            var semaphore = new AsyncSemaphore(1);

            Assert.Equal(1, semaphore.Available);
            semaphore.Wait();
            Assert.Equal(0, semaphore.Available);
            semaphore.Release();
            Assert.Equal(1, semaphore.Available);
        }
예제 #3
0
        public async Task WaitAsync_Release()
        {
            var semaphore = new AsyncSemaphore(1, 1, true);

            for (var i = 0; i < 1000; ++i)
            {
                await semaphore.WaitAsync();

                semaphore.Release();
            }
        }
예제 #4
0
        public void Release()
        {
            var semaphore = new AsyncSemaphore(0, 5, true);

            var acquiredWhenNoneAvailable = semaphore.Wait(5, 0);

            semaphore.Release(5);
            var acquiredWhenAllAvailable = semaphore.Wait(5, 0);

            Assert.False(acquiredWhenNoneAvailable);
            Assert.True(acquiredWhenAllAvailable);
        }
예제 #5
0
        public void Release_WithoutWaiters_IncrementsCount()
        {
            AsyncSemaphore semaphore = new AsyncSemaphore(0);

            Assert.Equal(0, semaphore.CurrentCount);
            semaphore.Release();
            Assert.Equal(1, semaphore.CurrentCount);
            Task task = semaphore.WaitAsync();

            Assert.Equal(0, semaphore.CurrentCount);
            Assert.True(task.IsCompleted);
        }
예제 #6
0
        public async Task Release_WithWaiters_ReleasesWaiters()
        {
            AsyncSemaphore semaphore = new AsyncSemaphore(0);

            Assert.Equal(0, semaphore.CurrentCount);
            Task task = semaphore.WaitAsync();

            Assert.Equal(0, semaphore.CurrentCount);
            Assert.False(task.IsCompleted);
            semaphore.Release();
            Assert.Equal(0, semaphore.CurrentCount);
            await task;
        }
        public void WhenClientRelease_SemaphoreShouldNotMakeOneMoreClientWaitedAsync()
        {
            var sut = new AsyncSemaphore(1);

            sut.Wait();
            var task = sut.WaitAsync();

            ConcurrentAssert.EnsureThatTaskIsNeverCompleted(task);

            sut.Release();

            ConcurrentAssert.EnsureThatTaskIsCompleted(task);
        }
예제 #8
0
        /// <summary>
        /// Downloads remoteUri to dest. This method is solely used for downloading
        /// covers from discogs, which requires OAuth Authentication. Therefore, note
        /// the use of oAuthHttpClient.
        /// </summary>
        /// <param name="remoteUri">remote URI to download</param>
        /// <returns>a local URI to the downloaded file</returns>
        public async Task <Uri> Download(Uri remoteUri, string dest)
        {
            // might throw at least IsolatedStorageException on WP
            await sem.WaitAsync();

            try {
                await fileUtils.WithFileWriteAsync(dest, stream => {
                    return(httpClient.DownloadToStream(remoteUri, stream));
                });
            } finally {
                sem.Release();
            }
            return(await UriFromPath(dest));
        }
예제 #9
0
 public void Release_WithWaiters_ReleasesWaiters()
 {
     AsyncContext.Run(async() =>
     {
         var semaphore = new AsyncSemaphore(0);
         Assert.AreEqual(0, semaphore.CurrentCount);
         var task = semaphore.WaitAsync();
         Assert.AreEqual(0, semaphore.CurrentCount);
         Assert.IsFalse(task.IsCompleted);
         semaphore.Release();
         Assert.AreEqual(0, semaphore.CurrentCount);
         await task;
     });
 }
예제 #10
0
                public bool AddOrReplace(WorkItem item)
                {
                    lock (_gate)
                    {
                        if (AddOrReplace_NoLock(item))
                        {
                            // increase count
                            _semaphore.Release();
                            return(true);
                        }

                        return(false);
                    }
                }
        /// <summary>
        /// Executes the request but limits the number of threads that can be used
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="request"></param>
        /// <returns></returns>
        public async Task <IApiResponse <T> > EnqueueAsync <T>(IApiRequest request)
            where T : class
        {
            await _throttler.WaitAsync().ConfigureAwait(false);

            try
            {
                return(await _handler.ExecuteAsync <T>(request).ConfigureAwait(false));
            }
            finally
            {
                _throttler.Release();
            }
        }
예제 #12
0
        /// <summary>
        /// Executes the request but limits the number of threads that can be used
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="request"></param>
        /// <returns></returns>
        public async Task <IBoxResponse <T> > EnqueueAsync <T>(IBoxRequest request)
            where T : class
        {
            await _throttler.WaitAsync();

            try
            {
                return(await _handler.ExecuteAsync <T>(request));
            }
            finally
            {
                _throttler.Release();
            }
        }
예제 #13
0
        protected async override Task WriteMessageInternal(string message)
        {
            await lcdLock.WaitAsync();

            try
            {
                Debug.WriteLine("HD44780GpioDriver: Writing Message - " + message);

                await this.writeValue(Register.Instruction, InstructionCode_ClearLcd);

                await this.wait(TimeSpan.FromMilliseconds(1.64));

                int totalChars = 0;
                int lineChars  = 0;
                int lines      = 1;
                foreach (char c in message)
                {
                    if (this.Width == lineChars)
                    {
                        Debug.WriteLine("HD44780GpioDriver: Message overran on line" + lines);
                    }

                    if (this.Height < lines)
                    {
                        Debug.WriteLine("HD44780GpioDriver: Message contains too many lines");
                    }

                    if (c == '\n')
                    {
                        await this.writeValue(Register.Instruction, InstructionCode_NewLine);

                        lines++;
                        lineChars = 0;
                        continue;
                    }

                    await this.writeValue(Register.Data, (short)c);

                    totalChars++;
                    lineChars++;
                    await this.wait(TimeSpan.FromMilliseconds(1));
                }

                Debug.WriteLine("HD44780GpioDriver: Message write complete");
            }
            finally
            {
                lcdLock.Release();
            }
        }
예제 #14
0
        public IExchange DeclareExchange(IAdvancedBus advancedBus, string exchangeName, string exchangeType)
        {
            IExchange exchange;

            if (exchanges.TryGetValue(exchangeName, out exchange))
            {
                return(exchange);
            }
            semaphore.Wait();
            try
            {
                if (exchanges.TryGetValue(exchangeName, out exchange))
                {
                    return(exchange);
                }
                exchange = advancedBus.ExchangeDeclare(exchangeName, exchangeType);
                exchanges[exchangeName] = exchange;
                return(exchange);
            }
            finally
            {
                semaphore.Release();
            }
        }
예제 #15
0
        public async Task WaitAsync_Random()
        {
            var semaphore = new AsyncSemaphore(1000, 1000, true);

            var random     = new Random(1234);
            var amountLeft = 1000;

            while (amountLeft > 0)
            {
                var maxToAcquire    = amountLeft / 2;
                var amountToAcquire = random.Next(1, maxToAcquire > 1 ? maxToAcquire : 1);
                Assert.True(await semaphore.WaitAsync(amountToAcquire, 0));
                amountLeft -= amountToAcquire;
            }
            semaphore.Release(1000);
            Assert.True(await semaphore.WaitAsync(1000, 0));
        }
예제 #16
0
        public override void Return(T item)
        {
            lock (SyncObject)
            {
                if (m_Stack == null)
                {
                    if (item is IDisposable disposable)
                    {
                        disposable.Dispose();
                    }

                    return;
                }

                m_Stack.Push(item);
                m_Semaphore.Release();
            }
        }
예제 #17
0
        public async Task MultipleAsynchronousAcquire_Fair()
        {
            var semaphore       = new AsyncSemaphore(0, 1, true);
            var completionTasks = Enumerable.Range(0, 10).Select(_ =>
            {
                return(new TaskCompletionSource <bool>());
            }).ToList();

            var acquireTasks = new List <Task>();

            for (var i = 0; i < 10; ++i)
            {
                var index       = i;
                var acquireTask = Task.Run(async() =>
                {
                    await semaphore.WaitAsync();
                    completionTasks[index].SetResult(true);
                });
                await Task.Delay(100);

                while (acquireTask.Status != TaskStatus.Running)
                {
                    await Task.Delay(100);
                }
            }

            Assert.True(completionTasks.All(t => t.Task.Status != TaskStatus.RanToCompletion));

            for (var i = 0; i < 10; ++i)
            {
                for (var finished = 0; finished < i; ++finished)
                {
                    Assert.True(completionTasks[finished].Task.IsCompletedSuccessfully);
                }
                for (var running = i; running < 10; ++running)
                {
                    Assert.NotEqual(TaskStatus.RanToCompletion, completionTasks[running].Task.Status);
                }
                semaphore.Release();
                await Task.Delay(100);
            }

            Assert.True(completionTasks.All(t => t.Task.IsCompletedSuccessfully));
        }
예제 #18
0
        public async Task WaitAsync_Cancelled_DoesNotTakeSlot()
        {
            AsyncSemaphore semaphore = new AsyncSemaphore(0);

            Assert.Equal(0, semaphore.CurrentCount);
            CancellationTokenSource cts = new CancellationTokenSource();
            Task task = semaphore.WaitAsync(cts.Token);

            Assert.Equal(0, semaphore.CurrentCount);
            Assert.False(task.IsCompleted);

            cts.Cancel();

            try { await task; }
            catch (OperationCanceledException) { }
            semaphore.Release();
            Assert.Equal(1, semaphore.CurrentCount);
            Assert.True(task.IsCanceled);
        }
예제 #19
0
        public void WaitAsync_Cancelled_DoesNotTakeSlot()
        {
            Test.Async(async() =>
            {
                var semaphore = new AsyncSemaphore(0);
                Assert.AreEqual(0, semaphore.CurrentCount);
                var cts  = new CancellationTokenSource();
                var task = semaphore.WaitAsync(cts.Token);
                Assert.AreEqual(0, semaphore.CurrentCount);
                Assert.IsFalse(task.IsCompleted);

                cts.Cancel();

                try { await task; }
                catch (OperationCanceledException) { }
                semaphore.Release();
                Assert.AreEqual(1, semaphore.CurrentCount);
                Assert.IsTrue(task.IsCanceled);
            });
        }
                public bool AddOrReplace(WorkItem item)
                {
                    if (!HasAnyWork)
                    {
                        // first work is added.
                        _progressReporter.Start();
                    }

                    lock (_gate)
                    {
                        if (AddOrReplace_NoLock(item))
                        {
                            // increase count
                            _semaphore.Release();
                            return(true);
                        }

                        return(false);
                    }
                }
예제 #21
0
            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                AsyncSemaphore[] semaphores = Interlocked.Exchange(ref _semaphores, null);
                if (semaphores == null)
                {
                    return;
                }

                List <Exception> ex = null;

                for (int i = 0; i < semaphores.Length; i++)
                {
                    try
                    {
                        AsyncSemaphore sem = Interlocked.Exchange(ref semaphores[i], null);
                        sem?.Release();
                    }
                    catch (Exception e)
                    {
                        if (ex == null)
                        {
                            ex = new List <Exception>();
                        }
                        ex.Add(e);
                    }
                }

                if (ex == null ||
                    ex.Count < 1)
                {
                    return;
                }
                // ReSharper disable once AssignNullToNotNullAttribute
                if (ex.Count < 2)
                {
                    ex[0].ReThrow();
                }
                throw new AggregateException(ex);
            }
예제 #22
0
        public async Task <SteamCdnClient> GetClientAsync(CancellationToken cancellationToken = default)
        {
            await _getClientSemaphore.WaitAsync(cancellationToken);

            if (_cdnClients.Count == 0)
            {
                await FillPoolAsync();
            }

            // Select client that has been handed out the least yet
            var cdnClient = _cdnClients
                            .OrderBy(x => x.Key.Errors)
                            .ThenBy(x => x.Value)
                            .First()
                            .Key;

            _cdnClients[cdnClient]++;

            _getClientSemaphore.Release();

            return(cdnClient);
        }
        /// <summary>
        /// Asynchronously downloads meta-information for one file.
        /// </summary>
        /// <param name="service">Google Drive service that is used to download files metainformation.</param>
        /// <param name="file">File with filled Id field.</param>
        /// <param name="semaphore">Semaphore used to throttle downloading process.</param>
        /// <returns>Asynchronous task that downloads meta-information.</returns>
        private async Task <File> DownloadFileMetaInformation(DriveService service, File file, AsyncSemaphore semaphore)
        {
            var fileInfoRequest = service.Files.Get(file.Id);

            fileInfoRequest.Fields = "*";

            await semaphore.WaitAsync();

            try
            {
                var fileInfo = await GoogleRequestHelper.Execute(
                    fileInfoRequest.ExecuteAsync,
                    this.CancellationToken,
                    GoogleRequestHelper.Cooldown *Utils.Constants.DownloadThreadsCount);

                ++this.Progress;
                this.StatusAccumulator.SuccessItem();
                return(fileInfo);
            }
            catch (Exception e)
            {
                if (this.CancellationToken.IsCancellationRequested)
                {
                    throw;
                }

                this.Log.LogError($"Downloading meta-information for file {file.Name} failed.", e);
                this.StatusAccumulator.FailureOccurred();

                throw;
            }
            finally
            {
                semaphore.Release();
            }
        }
예제 #24
0
        private async Task SendHelperAsync(Guid destination, MessageDto message)
        {
            Debug(
                $"Sending to {destination.ToString("n").Substring(0, 6)} message {message}. " + Environment.NewLine +
                $"clientIdentity matches destination: {remoteIdentity.Matches(destination, IdentityMatchingScope.Broadcast)}");
            if (!isHandshakeComplete || !remoteIdentity.Matches(destination, IdentityMatchingScope.Broadcast))
            {
                return;
            }

            var completionLatch = new AsyncLatch();

            sendCompletionLatchByMessage.AddOrThrow(message, completionLatch);

            outboundMessageQueue.Enqueue(message);
            outboundMessageSignal.Release();

            Debug($"Awaiting completion for send to {destination.ToString("n").Substring(0, 6)} message {message}.");
            await completionLatch.WaitAsync().ConfigureAwait(false);

            sendCompletionLatchByMessage.RemoveOrThrow(message, completionLatch);

            Debug($"Completed send to {destination.ToString("n").Substring(0, 6)} message {message}.");
        }
예제 #25
0
 /// <summary>
 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 /// </summary>
 public void Dispose()
 {
     _semaphore?.Release();
 }
예제 #26
0
        async Task SendBatch()
        {
            batchId++;
            if (batchId >= long.MaxValue)
            {
                batchId = 1;
            }

            // Pause the timer
            timerBatchWait.Change(Timeout.Infinite, Timeout.Infinite);

            if (notifications.Count <= 0)
            {
                return;
            }

            // Let's store the batch items to send internally
            var toSend = new List <CompletableApnsNotification> ();

            while (notifications.Count > 0 && toSend.Count < Configuration.InternalBatchSize)
            {
                var n = notifications.Dequeue();
                toSend.Add(n);
            }


            Log.Info("APNS-Client[{0}]: Sending Batch ID={1}, Count={2}", id, batchId, toSend.Count);

            try {
                var data = createBatch(toSend);

                if (data != null && data.Length > 0)
                {
                    for (var i = 0; i <= Configuration.InternalBatchFailureRetryCount; i++)
                    {
                        await connectingSemaphore.WaitAsync();

                        try {
                            // See if we need to connect
                            if (!socketCanWrite() || i > 0)
                            {
                                await connect();
                            }
                        } finally {
                            connectingSemaphore.Release();
                        }

                        try {
                            await networkStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);

                            break;
                        } catch (Exception ex) when(i != Configuration.InternalBatchFailureRetryCount)
                        {
                            Log.Info("APNS-CLIENT[{0}]: Retrying Batch: Batch ID={1}, Error={2}", id, batchId, ex);
                        }
                    }

                    foreach (var n in toSend)
                    {
                        sent.Add(new SentNotification(n));
                    }
                }
            } catch (Exception ex) {
                Log.Error("APNS-CLIENT[{0}]: Send Batch Error: Batch ID={1}, Error={2}", id, batchId, ex);
                foreach (var n in toSend)
                {
                    n.CompleteFailed(new ApnsNotificationException(ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex));
                }
            }

            Log.Info("APNS-Client[{0}]: Sent Batch, waiting for possible response...", id);

            try {
                await Reader();
            } catch (Exception ex) {
                Log.Error("APNS-Client[{0}]: Reader Exception: {1}", id, ex);
            }

            Log.Info("APNS-Client[{0}]: Done Reading for Batch ID={1}, reseting batch timer...", id, batchId);

            // Restart the timer for the next batch
            timerBatchWait.Change(Configuration.InternalBatchingWaitPeriod, new TimeSpan(0, 0, 0, 0, -1));
        }
예제 #27
0
        /// <summary>
        /// Downloads binary (non-"Google Docs") file.
        /// </summary>
        /// <param name="service">Google Drive service used to download a file.</param>
        /// <param name="file">Meta-information about file to download.</param>
        /// <param name="semaphore">Semaphore used to throttle downloading process.</param>
        /// <param name="unitsOfWork">Abstract units of work assigned to download this file. Used in progress reporting.</param>
        /// <param name="directories">Dictionary that contains possible directories to save a file.</param>
        /// <returns>Information about downloaded file, null if downloading failed.</returns>
        /// <exception cref="InternalErrorException">Something is wrong with downloader code itself.</exception>
        public async Task <DownloadedFile> DownloadBinaryFile(
            DriveService service,
            File file,
            AsyncSemaphore semaphore,
            int unitsOfWork,
            IDictionary <File, IEnumerable <DirectoryInfo> > directories)
        {
            this.Info       = file.Name;
            this.Estimation = unitsOfWork;

            if (file.Size.HasValue && (file.Size.Value == 0))
            {
                try
                {
                    var fileName = FileDownloadUtils.GetFileName(file, directories);
                    System.IO.File.Create(fileName);
                    this.Done();
                    return(new DownloadedFile(file, fileName));
                }
                catch (Exception e)
                {
                    this.Log.LogError($"Saving zero-length file '{file.Name}' error.", e);
                    this.StatusAccumulator.FailureOccurred();
                    throw;
                }
            }

            var request = service.Files.Get(file.Id);

            request.MediaDownloader.ProgressChanged += downloadProgress =>
            {
                switch (downloadProgress.Status)
                {
                case DownloadStatus.NotStarted:
                    break;

                case DownloadStatus.Downloading:
                    this.Progress = (int)(downloadProgress.BytesDownloaded * unitsOfWork / (file.Size ?? 1));
                    break;

                case DownloadStatus.Completed:
                    this.Progress = this.Estimation;
                    break;

                case DownloadStatus.Failed:
                    this.Progress = this.Estimation;
                    if (!this.CancellationToken.IsCancellationRequested)
                    {
                        this.Status = Status.Error;
                        this.Log.LogError($"Downloading file '{file.Name}' error.", downloadProgress.Exception);
                        this.StatusAccumulator.FailureOccurred();
                    }

                    break;

                default:
                    throw new InternalErrorException("DownloadStatus enum contains unknown value.");
                }
            };

            await semaphore.WaitAsync();

            this.Log.LogDebugMessage($"Starting to download '{file.Name}' binary file.");

            try
            {
                var fileName = FileDownloadUtils.GetFileName(file, directories);
                using (var fileStream = new FileStream(fileName, FileMode.Create))
                {
                    await GoogleRequestHelper.Execute(
                        ct => request.DownloadAsync(fileStream, ct),
                        this.CancellationToken,
                        GoogleRequestHelper.Cooldown *Utils.Constants.DownloadThreadsCount);
                }

                var fileInfo = FileDownloadUtils.CorrectFileTimes(file, fileName);
                this.Log.LogDebugMessage($"File '{file.Name}' downloading finished, saved as {fileInfo.FullName}.");
                this.StatusAccumulator.SuccessItem();
                this.Done();
                return(new DownloadedFile(file, fileInfo.FullName));
            }
            catch (Exception e)
            {
                if (this.CancellationToken.IsCancellationRequested)
                {
                    throw;
                }

                this.Log.LogError($"Downloading file '{file.Name}' error.", e);
                this.StatusAccumulator.FailureOccurred();

                throw;
            }
            finally
            {
                semaphore.Release();
            }
        }
예제 #28
0
        /// <summary>
        /// A probe attempt to one destination.
        /// </summary>
        private async Task ProbeDestinationAsync(DestinationInfo destination, AsyncSemaphore semaphore, CancellationToken cancellationToken)
        {
            // Conduct a dither for every endpoint probe to optimize concurrency.
            var randomDither = _randomFactory.CreateRandomInstance();
            await _timer.Delay(TimeSpan.FromMilliseconds(randomDither.Next(_ditheringIntervalInMilliseconds)), cancellationToken);

            var    outcome   = HealthProbeOutcome.Unknown;
            string logDetail = null;

            // Enforce max concurrency.
            await semaphore.WaitAsync();

            Log.ProberStarted(_logger, BackendId, destination.DestinationId);
            try
            {
                using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token))
                {
                    // Set up timeout and start probing.
                    timeoutCts.CancelAfter(_httpTimeoutInterval, _timer);
                    var response = await _operationLogger.ExecuteAsync(
                        "ReverseProxy.Service.HealthProbe",
                        () => _backendProbeHttpClient.GetAsync(new Uri(new Uri(destination.Config.Value.Address, UriKind.Absolute), _healthControllerUrl), timeoutCts.Token));

                    // Collect response status.
                    outcome   = response.IsSuccessStatusCode ? HealthProbeOutcome.Success : HealthProbeOutcome.HttpFailure;
                    logDetail = $"Received status code {(int)response.StatusCode}";
                }
            }
            catch (HttpRequestException ex)
            {
                // If there is a error during the http request process. Swallow the error and log error message.
                outcome   = HealthProbeOutcome.TransportFailure;
                logDetail = ex.Message;
            }
            catch (OperationCanceledException)
            {
                // If the cancel requested by our StopAsync method. It is a expected graceful shut down.
                if (_cts.IsCancellationRequested)
                {
                    outcome   = HealthProbeOutcome.Canceled;
                    logDetail = "Operation deliberately canceled";
                    throw;
                }
                else
                {
                    outcome   = HealthProbeOutcome.Timeout;
                    logDetail = $"Health probe timed out after {Config.HealthCheckOptions.Interval.TotalSeconds} second";
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"Prober for '{destination.DestinationId}' encounters unexpected exception.", ex);
            }
            finally
            {
                if (outcome != HealthProbeOutcome.Canceled)
                {
                    // Update the health state base on the response.
                    var healthState = outcome == HealthProbeOutcome.Success ? DestinationHealth.Healthy : DestinationHealth.Unhealthy;
                    destination.DynamicState.Value = new DestinationDynamicState(healthState);
                    Log.ProberResult(_logger, destination.DestinationId, outcome, logDetail);
                }

                // The probe operation is done, release the semaphore to allow other probes to proceed.
                semaphore.Release();
            }
        }
예제 #29
0
        public async Task Update()
        {
            // TODO: Ensure only one Update is running across all instances of SMA (in case SMA is closed during the update process)
            // TODO: Offer manual updates
            // TODO: Add option to wait for user confirmation to update

            if (UpdateEnabled == false)
            {
                return;
            }

            try
            {
                if (Wininet.HasNetworking() == false)
                {
                    return;
                }

                CancellationTokenSource cts = new CancellationTokenSource(0);
                cts.Cancel();

                using (await _semaphore.LockAsync(cts.Token))
                    using (var updateMgr = CreateUpdateMgr())
                    {
                        State = SMAUpdateState.Fetching;

                        var updateInfo = await updateMgr.CheckForUpdate(false, progress => ProgressPct = progress);

                        if (updateInfo?.ReleasesToApply == null)
                        {
                            State = SMAUpdateState.Error;
                            return;
                        }

                        if (updateInfo.ReleasesToApply.None())
                        {
                            State = SMAUpdateState.UpToDate;
                            return;
                        }

                        State = SMAUpdateState.Downloading;

                        await updateMgr.DownloadReleases(updateInfo.ReleasesToApply, progress => ProgressPct = progress);

                        State = SMAUpdateState.Applying;

                        await updateMgr.ApplyReleases(updateInfo, progress => ProgressPct = progress);

                        State = SMAUpdateState.CreatingUninstaller;

                        await updateMgr.CreateUninstallerRegistryEntry();

                        State = SMAUpdateState.Updated;
                    }
            }
            catch (TaskCanceledException) {}
            catch (Exception ex) // TODO: Update Squirrel UpdateManager to send sub-classed Exceptions
            {
                LogTo.Warning(ex, $"An exception was caught while {State.Name().ToLower()} update");
                State = SMAUpdateState.Error;
            }
            finally
            {
                _semaphore.Release();
            }
        }