Bug fixes and stability improvements:

- Fix crash on improper websocket disconnect
- Fix some race conditions by moving to synchronized Queue and List
- Fix allowing invalid usernames
This commit is contained in:
Elijah R 2024-01-03 19:07:47 -05:00
parent 563883a275
commit 48e2ece306
4 changed files with 41 additions and 19 deletions

View file

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Timers;
using Timer = System.Timers.Timer;
@ -9,7 +10,7 @@ public class TurnQueue
public event EventHandler<TurnStatus> Turn;
// Private fields
private readonly Queue<User> queue;
private readonly ConcurrentQueue<User> queue;
private readonly uint turnTime;
private readonly Timer timer;
private uint currentTurnRemainingTime;
@ -32,7 +33,8 @@ public class TurnQueue
if (indefiniteTurn != null) return;
if (queue.Count == 0) return; // This shouldn't happen, but just in case
currentTurnRemainingTime--;
Utilities.Log(LogLevel.DEBUG, $"Turn tick, {currentTurnRemainingTime} seconds remaining on {queue.Peek().Username}'s turn");
if (!queue.TryPeek(out var u)) return;
Utilities.Log(LogLevel.DEBUG, $"Turn tick, {currentTurnRemainingTime} seconds remaining on {u.Username}'s turn");
if (currentTurnRemainingTime == 0)
{
NextTurn();
@ -48,10 +50,11 @@ public class TurnQueue
SendTurnUpdate();
return;
}
queue.Dequeue();
queue.TryDequeue(out _);
if (queue.Count > 0)
{
Utilities.Log(LogLevel.DEBUG, $"It is now {queue.Peek().Username}'s turn");
if (!queue.TryPeek(out var u)) return;
Utilities.Log(LogLevel.DEBUG, $"It is now {u.Username}'s turn");
currentTurnRemainingTime = turnTime;
}
SendTurnUpdate();
@ -100,7 +103,8 @@ public class TurnQueue
}
if (!queue.Contains(user)) return;
Utilities.Log(LogLevel.DEBUG, $"Removing user {user.Username} to turn queue");
if (queue.Peek() == user && indefiniteTurn == null)
if (!queue.TryPeek(out var _u)) return;
if (_u == user && indefiniteTurn == null)
{
NextTurn();
return;
@ -128,7 +132,8 @@ public class TurnQueue
{
if (indefiniteTurn != null) return indefiniteTurn;
if (queue.Count == 0) return null;
return queue.Peek();
if (!queue.TryPeek(out var u)) return null;
return u;
}
public void GiveTurn(User user)
@ -145,7 +150,8 @@ public class TurnQueue
AddUser(user);
return;
}
if (queue.Peek() == user) return;
if (!queue.TryPeek(out var _u)) return;
if (_u == user) return;
RemoveUser(user);
var _queue = queue.ToArray();
queue.Clear();

View file

@ -79,8 +79,12 @@ public class User
{
return;
}
await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg)), WebSocketMessageType.Text, true,
CancellationToken.None);
try
{
await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg)), WebSocketMessageType.Text, true,
CancellationToken.None);
} catch { /* ignored */ }
}
private async Task ProcessMessage(string[] msgArr)
@ -343,7 +347,7 @@ public class User
{
// Ban
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.Ban) || msgArr.Length < 3 || vm == null) return;
var user = vm.Users.Find(u => u.Username == msgArr[2]);
var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return;
await user.Ban(msgArr.Length == 4 ? msgArr[3] : null);
}
@ -366,7 +370,7 @@ public class User
{
// Mute
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.Mute) || msgArr.Length != 4 || vm == null) return;
var user = vm.Users.Find(u => u.Username == msgArr[2]);
var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return;
bool? permanent = msgArr[3] == "1" ? true : msgArr[3] == "0" ? false : null;
if (permanent == null) return;
@ -377,7 +381,7 @@ public class User
{
// Kick
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.Kick) || msgArr.Length != 3 || vm == null) return;
var user = vm.Users.Find(u => u.Username == msgArr[2]);
var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return;
await user.Close();
}
@ -386,7 +390,7 @@ public class User
{
// End turn
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.BypassTurn) || msgArr.Length != 3 || vm == null) return;
var user = vm.Users.Find(u => u.Username == msgArr[2]);
var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return;
vm.TurnQueue.RemoveUser(user);
}
@ -404,7 +408,7 @@ public class User
{
// Rename user
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.Rename) || msgArr.Length != 4 || vm == null) return;
var user = vm.Users.Find(u => u.Username == msgArr[2]);
var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return;
await user.Rename(msgArr[3]);
}
@ -413,7 +417,7 @@ public class User
{
// Get IP
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.GrabIP) || msgArr.Length != 3 || vm == null) return;
var user = vm.Users.Find(u => u.Username == msgArr[2]);
var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return;
await SendAsync(Guacutils.Encode("admin", "19", msgArr[2], user.IP.ToString()));
}
@ -497,7 +501,15 @@ public class User
using MemoryStream ms = new MemoryStream();
do
{
result = await socket.ReceiveAsync(receivebuffer, token);
try
{
result = await socket.ReceiveAsync(receivebuffer, token);
}
catch (WebSocketException ex)
{
await Close(false);
return;
}
if (result.MessageType == WebSocketMessageType.Close)
{
Disconnected.Invoke(this, new EventArgs());
@ -546,9 +558,9 @@ public class User
{
await SendAsync(Guacutils.Encode("size", "0", size.Width.ToString(), size.Height.ToString()));
}
public async Task Close()
public async Task Close(bool sendDisconnect = true)
{
await SendAsync(Guacutils.Encode("disconnect"));
if (sendDisconnect) SendAsync(Guacutils.Encode("disconnect"));
try
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
@ -608,6 +620,7 @@ public class User
return;
}
await this.SendAsync(Guacutils.Encode("rename", "0", "2", _username, ((int)this._rank).ToString()));
return;
}
this._username = newname;
if (oldname != null) this.Renamed.Invoke(this, oldname);

View file

@ -1,3 +1,5 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Net;
using CollabVM.Server.Config;
using SixLabors.ImageSharp;
@ -14,7 +16,7 @@ public class VM
// Public properties and events
public VMController Controller { get; }
public VMConfig Config { get; }
public List<User> Users { get; } = new();
public SynchronizedCollection<User> Users { get; } = new();
public CircularBuffer.CircularBuffer<ChatMessage> ChatHistory { get; } = new((int)Program.Config.Chat.ChatHistoryLength);
public TurnQueue TurnQueue { get; } = new(Program.Config.Turns.TurnTime);
public ResetVote? Vote { get; private set; }

View file

@ -15,6 +15,7 @@
<PackageReference Include="Samboy063.Tomlet" Version="5.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.ServiceModel.Primitives" Version="8.0.0" />
</ItemGroup>
<ItemGroup>