mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 01:46:11 +00:00
Rework LockGuard to avoid allocations when the lock isn't contested
This commit is contained in:
4 files changed
+79
-69
No files matched your search
@@ -6,6 +6,7 @@ using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.GameOptions;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Api.Net.Inner;
|
||||
using Impostor.Api.Utils;
|
||||
|
||||
namespace Impostor.Api.Games
|
||||
{
|
||||
@@ -53,10 +54,10 @@ namespace Impostor.Api.Games
|
||||
T? FindObjectByNetId<T>(uint netId)
|
||||
where T : IInnerNetObject;
|
||||
|
||||
/// <summary>Lock an IGame to prevent concurrent modification.</summary>
|
||||
/// <returns>A <see cref="LockGuard" /> that should be awaited before and disposed after modifying the game.</returns>
|
||||
/// <summary>Lock an <see cref="IGame"/> to prevent concurrent modification.</summary>
|
||||
/// <returns>A <see cref="AsyncLock.Releaser" /> that should be disposed after modifying the game.</returns>
|
||||
/// <remarks>This method should be called when modifying a game outside of an <see cref="IGameEvent" /> handler.</remarks>
|
||||
Task<LockGuard> LockAsync();
|
||||
ValueTask<AsyncLock.Releaser> LockAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="IPAddress" /> to the ban list of this game.
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Impostor.Api.Net
|
||||
{
|
||||
/// <summary>RAII-style lock guard for use in Impostor.</summary>
|
||||
/// <remarks>
|
||||
/// To lock a semaphore, use the LockAsync method and await the result.
|
||||
/// To unlock the semaphore, dispose of the LockGuard.
|
||||
/// </remarks>
|
||||
public sealed class LockGuard : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
private LockGuard(SemaphoreSlim semaphore)
|
||||
{
|
||||
_semaphore = semaphore;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Create a RAII-style lock guard for an instance of <see cref="SemaphoreSlim"/>.</summary>
|
||||
/// <param name="semaphore">The semaphore to Wait and Release on.</param>
|
||||
/// <param name="timeout">The amount of milliseconds to wait for the lock becoming available.</param>
|
||||
/// <exception cref="ImpostorException">When the timeout expires.</exception>
|
||||
/// <returns>The lock guard if the semaphore could be locked succesfully.</returns>
|
||||
public static async Task<LockGuard> LockAsync(SemaphoreSlim semaphore, int timeout = 1000)
|
||||
{
|
||||
var success = await semaphore.WaitAsync(timeout);
|
||||
if (success)
|
||||
{
|
||||
return new LockGuard(semaphore);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ImpostorException("Could not lock semaphore");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Impostor.Api.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an asynchronous lock mechanism.
|
||||
/// </summary>
|
||||
public sealed class AsyncLock
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously acquires a lock.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="ValueTask{Releaser}"/> that represents the asynchronous lock operation.
|
||||
/// The result of the task is a <see cref="Releaser"/> that should be disposed to release the lock.
|
||||
/// </returns>
|
||||
public ValueTask<Releaser> LockAsync()
|
||||
{
|
||||
return LockAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="LockAsync()"/>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe while waiting for the lock.</param>
|
||||
/// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken" /> was canceled.</exception>
|
||||
public ValueTask<Releaser> LockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var wait = _semaphore.WaitAsync(cancellationToken);
|
||||
|
||||
if (wait.IsCompleted)
|
||||
{
|
||||
return ValueTask.FromResult(new Releaser(this));
|
||||
}
|
||||
|
||||
return new ValueTask<Releaser>(wait.ContinueWith(
|
||||
static (_, state) => new Releaser((AsyncLock)state!),
|
||||
this,
|
||||
cancellationToken,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a disposable object that, when disposed, releases the associated lock.
|
||||
/// </summary>
|
||||
public struct Releaser : IDisposable
|
||||
{
|
||||
private AsyncLock? _lock;
|
||||
|
||||
internal Releaser(AsyncLock @lock)
|
||||
{
|
||||
_lock = @lock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the lock held by this <see cref="Releaser"/> instance.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_lock != null)
|
||||
{
|
||||
_lock._semaphore.Release();
|
||||
_lock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api.Config;
|
||||
using Impostor.Api.Events.Managers;
|
||||
@@ -14,6 +13,7 @@ using Impostor.Api.Innersloth.GameOptions;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Api.Net.Manager;
|
||||
using Impostor.Api.Net.Messages.S2C;
|
||||
using Impostor.Api.Utils;
|
||||
using Impostor.Server.Events;
|
||||
using Impostor.Server.Net.Manager;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -33,7 +33,7 @@ namespace Impostor.Server.Net.State
|
||||
private readonly ICompatibilityManager _compatibilityManager;
|
||||
private readonly CompatibilityConfig _compatibilityConfig;
|
||||
private readonly TimeoutConfig _timeoutConfig;
|
||||
private readonly SemaphoreSlim _lock;
|
||||
private readonly AsyncLock _lock = new();
|
||||
|
||||
public Game(
|
||||
ILogger<Game> logger,
|
||||
@@ -68,7 +68,6 @@ namespace Impostor.Server.Net.State
|
||||
_compatibilityConfig = compatibilityConfig.Value;
|
||||
_timeoutConfig = timeoutConfig.Value;
|
||||
Items = new ConcurrentDictionary<object, object>();
|
||||
_lock = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
public IPEndPoint PublicIp { get; }
|
||||
@@ -116,9 +115,9 @@ namespace Impostor.Server.Net.State
|
||||
return _players.TryGetValue(clientId, out var clientPlayer) ? clientPlayer : null;
|
||||
}
|
||||
|
||||
public async Task<LockGuard> LockAsync()
|
||||
public ValueTask<AsyncLock.Releaser> LockAsync()
|
||||
{
|
||||
return await LockGuard.LockAsync(_lock);
|
||||
return _lock.LockAsync();
|
||||
}
|
||||
|
||||
internal async ValueTask StartedAsync()
|
||||
|
||||
Reference in new issue
Block a user