scratch-link/scratch-link-common/Extensions/SemaphoreSlimExtensions.cs
Christopher Willis-Ford eed937fd18 fix: fix DisposedException by removing cancellation token
We really use the socket state to control the session, so there's no
need to also pass a CancellationToken around. The exception was being
caused when an unlucky callback happened after a session was disposed
and the callback tried to get a new token to check cancellation. If the
token source is already disposed, it can't provide a new token.

Also, remove some excessive logging and add more exception logging.
2023-01-13 08:21:37 -08:00

83 lines
3.6 KiB
C#

// <copyright file="SemaphoreSlimExtensions.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>
namespace ScratchLink.Extensions;
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Extensions for <see cref="SemaphoreSlim"/>.
/// </summary>
public static class SemaphoreSlimExtensions
{
/// <summary>
/// Like <see cref="SemaphoreSlim.WaitAsync(CancellationToken)"/>, but generates a <see cref="DisposableSemaphoreLock"/> which will release the semaphore on <see cref="DisposableSemaphoreLock.Dispose"/>.
/// </summary>
/// <param name="semaphore">The semaphore on which to call <see cref="SemaphoreSlim.WaitAsync(CancellationToken)"/>.</param>
/// <param name="cancellationToken"><inheritdoc cref="SemaphoreSlim.WaitAsync(CancellationToken)"/></param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
public static async Task<IDisposable> WaitDisposableAsync(
this SemaphoreSlim semaphore,
CancellationToken cancellationToken)
{
var disposableLock = new DisposableSemaphoreLock(semaphore);
await semaphore.WaitAsync(cancellationToken);
return disposableLock;
}
/// <summary>
/// Like <see cref="SemaphoreSlim.WaitAsync(CancellationToken)"/>, but generates a <see cref="DisposableSemaphoreLock"/> which will release the semaphore on <see cref="DisposableSemaphoreLock.Dispose"/>.
/// </summary>
/// <param name="semaphore">The semaphore on which to call <see cref="SemaphoreSlim.WaitAsync(CancellationToken)"/>.</param>
/// <param name="timeout"><inheritdoc cref="SemaphoreSlim.WaitAsync(TimeSpan)"/></param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
public static async Task<IDisposable> WaitDisposableAsync(
this SemaphoreSlim semaphore,
TimeSpan timeout)
{
var disposableLock = new DisposableSemaphoreLock(semaphore);
await semaphore.WaitAsync(timeout);
return disposableLock;
}
/// <summary>
/// Like <see cref="SemaphoreSlim.WaitAsync(CancellationToken)"/>, but generates a <see cref="DisposableSemaphoreLock"/> which will release the semaphore on <see cref="DisposableSemaphoreLock.Dispose"/>.
/// </summary>
/// <param name="semaphore">The semaphore on which to call <see cref="SemaphoreSlim.WaitAsync(CancellationToken)"/>.</param>
/// <param name="timeout"><inheritdoc cref="SemaphoreSlim.WaitAsync(TimeSpan)"/></param>
/// <param name="cancellationToken"><inheritdoc cref="SemaphoreSlim.WaitAsync(CancellationToken)"/></param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
public static async Task<IDisposable> WaitDisposableAsync(
this SemaphoreSlim semaphore,
TimeSpan timeout,
CancellationToken cancellationToken)
{
var disposableLock = new DisposableSemaphoreLock(semaphore);
await semaphore.WaitAsync(timeout, cancellationToken);
return disposableLock;
}
private class DisposableSemaphoreLock : IDisposable
{
private SemaphoreSlim semaphore;
public DisposableSemaphoreLock(SemaphoreSlim semaphore)
{
this.semaphore = semaphore;
}
public void Dispose()
{
if (this.semaphore == null)
{
return;
}
this.semaphore.Release();
this.semaphore = null;
}
}
}