From fb04f4003f232cd30cfcc3588c7e724247be22ed Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 29 Mar 2025 20:28:36 +0000 Subject: [PATCH] Changed various locking mechanisms. --- Chat/Commands/Limits/UsagePolicy.cs | 167 +++++++++++------- Chat/Emotes/EmoteDatabase.cs | 1 - Hermes/Socket/Handlers/LoginAckHandler.cs | 4 +- Hermes/Socket/HermesSocketClient.cs | 71 +++++--- .../Socket/Requests/DeleteGroupChatterAck.cs | 2 +- Seven/Socket/Handlers/DispatchHandler.cs | 30 ++-- Seven/Socket/Handlers/EndOfStreamHandler.cs | 1 - Twitch/Redemptions/RedemptionManager.cs | 79 +++++++-- .../Socket/Handlers/SessionWelcomeHandler.cs | 2 +- Twitch/Socket/TwitchConnectionManager.cs | 39 +++- Twitch/Socket/TwitchWebsocketClient.cs | 31 +--- 11 files changed, 272 insertions(+), 155 deletions(-) diff --git a/Chat/Commands/Limits/UsagePolicy.cs b/Chat/Commands/Limits/UsagePolicy.cs index 95486c5..309890e 100644 --- a/Chat/Commands/Limits/UsagePolicy.cs +++ b/Chat/Commands/Limits/UsagePolicy.cs @@ -102,7 +102,7 @@ namespace TwitchChatTTS.Chat.Commands.Limits private IDictionary _usages { get; } private IList> _children { get; } private ILogger _logger; - private object _lock { get; } + private ReaderWriterLockSlim _rwls { get; } public UsagePolicyNode(string name, UsagePolicyLimit? data, UsagePolicyNode? parent, ILogger logger, bool root = false) { @@ -114,100 +114,149 @@ namespace TwitchChatTTS.Chat.Commands.Limits _usages = new Dictionary(); _children = new List>(); _logger = logger; - _lock = new object(); + _rwls = new ReaderWriterLockSlim(); } public UsagePolicyNode? Get(IEnumerable path) { - if (!path.Any()) - return this; + _rwls.EnterReadLock(); + try + { + if (!path.Any()) + return this; - var nextName = path.First(); - var next = _children.FirstOrDefault(c => c.Name == nextName); - if (next == null) - return this; - return next.Get(path.Skip(1)); + var nextName = path.First(); + var next = _children.FirstOrDefault(c => c.Name == nextName); + if (next == null) + return this; + return next.Get(path.Skip(1)); + } + finally + { + _rwls.ExitReadLock(); + } } public UsagePolicyNode? Remove(IEnumerable path) { - if (!path.Any()) + _rwls.EnterWriteLock(); + try { - if (_parent == null) - throw new InvalidOperationException("Cannot remove root node"); + if (!path.Any()) + { + if (_parent == null) + throw new InvalidOperationException("Cannot remove root node"); - _parent._children.Remove(this); - return this; + _parent._children.Remove(this); + return this; + } + + var nextName = path.First(); + var next = _children.FirstOrDefault(c => c.Name == nextName); + _logger.Debug($"internal remove node [is null: {next == null}][path: {string.Join('.', path)}]"); + if (next == null) + return null; + return next.Remove(path.Skip(1)); + } + finally + { + _rwls.ExitWriteLock(); } - - var nextName = path.First(); - var next = _children.FirstOrDefault(c => c.Name == nextName); - _logger.Debug($"internal remove node [is null: {next == null}][path: {string.Join('.', path)}]"); - if (next == null) - return null; - return next.Remove(path.Skip(1)); } public void Set(IEnumerable path, int count, TimeSpan span) { - if (!path.Any()) + _rwls.EnterWriteLock(); + try { - Limit = new UsagePolicyLimit(count, span); - return; - } + if (!path.Any()) + { + Limit = new UsagePolicyLimit(count, span); + return; + } - var nextName = path.First(); - var next = _children.FirstOrDefault(c => c.Name == nextName); - _logger.Debug($"internal set node [is null: {next == null}][path: {string.Join('.', path)}]"); - if (next == null) - { - next = new UsagePolicyNode(nextName, null, this, _logger); - _children.Add(next); + var nextName = path.First(); + var next = _children.FirstOrDefault(c => c.Name == nextName); + _logger.Debug($"internal set node [is null: {next == null}][path: {string.Join('.', path)}]"); + if (next == null) + { + next = new UsagePolicyNode(nextName, null, this, _logger); + _children.Add(next); + } + next.Set(path.Skip(1), count, span); + } + finally + { + _rwls.ExitWriteLock(); } - next.Set(path.Skip(1), count, span); } public bool TryUse(T key, DateTime timestamp) { - if (_parent == null) - return false; - if (Limit == null || Limit.Count <= 0) - return _parent.TryUse(key, timestamp); - - UserUsageData? usage; - lock (_lock) + _rwls.EnterUpgradeableReadLock(); + try { + if (_parent == null) + return false; + if (Limit == null || Limit.Count <= 0) + return _parent.TryUse(key, timestamp); + + UserUsageData? usage; if (!_usages.TryGetValue(key, out usage)) { - usage = new UserUsageData(Limit.Count, 1 % Limit.Count); - usage.Uses[0] = timestamp; - _usages.Add(key, usage); + _rwls.EnterWriteLock(); + try + { + usage = new UserUsageData(Limit.Count, 1 % Limit.Count); + usage.Uses[0] = timestamp; + _usages.Add(key, usage); + } + finally + { + _rwls.ExitWriteLock(); + } _logger.Debug($"internal use node create"); return true; } if (usage.Uses.Length != Limit.Count) { - var sizeDiff = Math.Max(0, usage.Uses.Length - Limit.Count); - var temp = usage.Uses.Skip(sizeDiff); - var tempSize = usage.Uses.Length - sizeDiff; - usage.Uses = temp.Union(new DateTime[Math.Max(0, Limit.Count - tempSize)]).ToArray(); + _rwls.EnterWriteLock(); + try + { + var sizeDiff = Math.Max(0, usage.Uses.Length - Limit.Count); + var temp = usage.Uses.Skip(sizeDiff); + var tempSize = usage.Uses.Length - sizeDiff; + usage.Uses = temp.Union(new DateTime[Math.Max(0, Limit.Count - tempSize)]).ToArray(); + finally + { + _rwls.ExitWriteLock(); + } + } + + // Attempt on parent node if policy has been abused. + if (timestamp - usage.Uses[usage.Index] < Limit.Span) + { + _logger.Debug($"internal use node spam [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]"); + return _parent.TryUse(key, timestamp); + } + + _logger.Debug($"internal use node normal [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]"); + _rwls.EnterWriteLock(); + try + { + usage.Uses[usage.Index] = timestamp; + usage.Index = (usage.Index + 1) % Limit.Count; + } + finally + { + _rwls.ExitWriteLock(); } } - - // Attempt on parent node if policy has been abused. - if (timestamp - usage.Uses[usage.Index] < Limit.Span) + finally { - _logger.Debug($"internal use node spam [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]"); - return _parent.TryUse(key, timestamp); - } - - _logger.Debug($"internal use node normal [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]"); - lock (_lock) - { - usage.Uses[usage.Index] = timestamp; - usage.Index = (usage.Index + 1) % Limit.Count; + _rwls.ExitWriteLock(); } return true; diff --git a/Chat/Emotes/EmoteDatabase.cs b/Chat/Emotes/EmoteDatabase.cs index 4752946..cdd2332 100644 --- a/Chat/Emotes/EmoteDatabase.cs +++ b/Chat/Emotes/EmoteDatabase.cs @@ -3,7 +3,6 @@ namespace TwitchChatTTS.Chat.Emotes public class EmoteDatabase : IEmoteDatabase { private readonly IDictionary _emotes; - public IDictionary Emotes { get => _emotes.AsReadOnly(); } public EmoteDatabase() { diff --git a/Hermes/Socket/Handlers/LoginAckHandler.cs b/Hermes/Socket/Handlers/LoginAckHandler.cs index 34f55c6..cd294b4 100644 --- a/Hermes/Socket/Handlers/LoginAckHandler.cs +++ b/Hermes/Socket/Handlers/LoginAckHandler.cs @@ -54,8 +54,8 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers _user.DefaultTTSVoice = message.DefaultTTSVoice; _user.VoicesAvailable = new ConcurrentDictionary(message.TTSVoicesAvailable); _user.VoicesEnabled = new HashSet(message.EnabledTTSVoices); - _user.TwitchConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "twitch"); - _user.NightbotConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "nightbot"); + _user.TwitchConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "twitch") ?? message.Connections.FirstOrDefault(c => c.Type == "twitch"); + _user.NightbotConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "nightbot") ?? message.Connections.FirstOrDefault(c => c.Type == "nightbot"); if (_user.TwitchConnection != null) { _logger.Information("Twitch connection: " + _user.TwitchConnection.Name + " / " + _user.TwitchConnection.AccessToken); diff --git a/Hermes/Socket/HermesSocketClient.cs b/Hermes/Socket/HermesSocketClient.cs index 1922949..3b27e8f 100644 --- a/Hermes/Socket/HermesSocketClient.cs +++ b/Hermes/Socket/HermesSocketClient.cs @@ -28,7 +28,7 @@ namespace TwitchChatTTS.Hermes.Socket public string? UserId { get; set; } private readonly System.Timers.Timer _heartbeatTimer; private readonly IBackoff _backoff; - private readonly object _lock; + private readonly ReaderWriterLockSlim _rwls; public bool Connected { get; set; } public bool LoggedIn { get; set; } @@ -62,7 +62,7 @@ namespace TwitchChatTTS.Hermes.Socket LastHeartbeatReceived = LastHeartbeatSent = DateTime.UtcNow; URL = $"wss://{BASE_URL}"; - _lock = new object(); + _rwls = new ReaderWriterLockSlim(); var ttsCreateUserVoice = _bus.GetTopic("tts.user.voice.create"); ttsCreateUserVoice.Subscribe(async data => await Send(3, new RequestMessage() @@ -82,25 +82,35 @@ namespace TwitchChatTTS.Hermes.Socket public override async Task Connect() { - lock (_lock) + _rwls.EnterWriteLock(); + try { if (Connected) return; - } - _logger.Debug($"Attempting to connect to {URL}"); - await ConnectAsync(URL); + _logger.Debug($"Attempting to connect to {URL}"); + await ConnectAsync(URL); + } + finally + { + _rwls.ExitWriteLock(); + } } private async Task Disconnect() { - lock (_lock) + _rwls.EnterWriteLock(); + try { if (!Connected) return; - } - await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed")); + await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed")); + } + finally + { + _rwls.ExitWriteLock(); + } } public async Task CreateTTSVoice(string voiceName) @@ -251,12 +261,10 @@ namespace TwitchChatTTS.Hermes.Socket OnConnected += async (sender, e) => { - lock (_lock) - { - if (Connected) - return; - Connected = true; - } + if (Connected) + return; + Connected = true; + _logger.Information("Tom to Speech websocket client connected."); _heartbeatTimer.Enabled = true; @@ -273,16 +281,13 @@ namespace TwitchChatTTS.Hermes.Socket OnDisconnected += async (sender, e) => { - lock (_lock) - { - if (!Connected) - return; + if (!Connected) + return; - Connected = false; - LoggedIn = false; - Ready = false; - _user.Slave = true; - } + Connected = false; + LoggedIn = false; + Ready = false; + _user.Slave = true; _logger.Warning("Tom to Speech websocket client disconnected."); @@ -424,13 +429,21 @@ namespace TwitchChatTTS.Hermes.Socket public new async Task Send(int opcode, T message) { - if (!Connected) + _rwls.EnterReadLock(); + try { - _logger.Warning("Tom to Speech websocket client is not connected. Not sending a message."); - return; - } + if (!Connected) + { + _logger.Warning("Tom to Speech websocket client is not connected. Not sending a message."); + return; + } - await base.Send(opcode, message); + await base.Send(opcode, message); + } + finally + { + _rwls.ExitReadLock(); + } } } } \ No newline at end of file diff --git a/Hermes/Socket/Requests/DeleteGroupChatterAck.cs b/Hermes/Socket/Requests/DeleteGroupChatterAck.cs index 4a015ee..b80dbe7 100644 --- a/Hermes/Socket/Requests/DeleteGroupChatterAck.cs +++ b/Hermes/Socket/Requests/DeleteGroupChatterAck.cs @@ -23,7 +23,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests return; } - if (long.TryParse(requestData["chatter"].ToString(), out var chatterId)) + if (!long.TryParse(requestData["chatter"].ToString(), out var chatterId)) { _logger.Warning($"Chatter Id is invalid [chatter id: {chatterId}]"); return; diff --git a/Seven/Socket/Handlers/DispatchHandler.cs b/Seven/Socket/Handlers/DispatchHandler.cs index f0e077b..618f03c 100644 --- a/Seven/Socket/Handlers/DispatchHandler.cs +++ b/Seven/Socket/Handlers/DispatchHandler.cs @@ -9,25 +9,28 @@ namespace TwitchChatTTS.Seven.Socket.Handlers { public class DispatchHandler : IWebSocketHandler { + public int OperationCode { get; } = 0; + private readonly ILogger _logger; private readonly IEmoteDatabase _emotes; - private readonly object _lock = new object(); - public int OperationCode { get; } = 0; + private readonly Mutex _lock; public DispatchHandler(IEmoteDatabase emotes, ILogger logger) { _emotes = emotes; _logger = logger; + _lock = new Mutex(); } public Task Execute(SocketClient sender, Data data) { - if (data is not DispatchMessage message || message == null) + if (data is not DispatchMessage message || message == null || message.Body == null) return Task.CompletedTask; - ApplyChanges(message?.Body?.Pulled, cf => cf.OldValue, true); - ApplyChanges(message?.Body?.Pushed, cf => cf.Value, false); - ApplyChanges(message?.Body?.Removed, cf => cf.OldValue, true); - ApplyChanges(message?.Body?.Updated, cf => cf.OldValue, false, cf => cf.Value); + + ApplyChanges(message.Body.Pulled, cf => cf.OldValue, true); + ApplyChanges(message.Body.Pushed, cf => cf.Value, false); + ApplyChanges(message.Body.Removed, cf => cf.OldValue, true); + ApplyChanges(message.Body.Updated, cf => cf.OldValue, false, cf => cf.Value); return Task.CompletedTask; } @@ -42,7 +45,7 @@ namespace TwitchChatTTS.Seven.Socket.Handlers if (value == null) continue; - var o = JsonSerializer.Deserialize(value.ToString(), new JsonSerializerOptions() + var o = JsonSerializer.Deserialize(value.ToString()!, new JsonSerializerOptions() { PropertyNameCaseInsensitive = false, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower @@ -50,8 +53,9 @@ namespace TwitchChatTTS.Seven.Socket.Handlers if (o == null) continue; - lock (_lock) + try { + _lock.WaitOne(); if (removing) { if (_emotes.Get(o.Name) != o.Id) @@ -71,8 +75,10 @@ namespace TwitchChatTTS.Seven.Socket.Handlers } _emotes.Remove(o.Name); var update = updater(val); + if (update == null) + continue; - var u = JsonSerializer.Deserialize(update.ToString(), new JsonSerializerOptions() + var u = JsonSerializer.Deserialize(update.ToString()!, new JsonSerializerOptions() { PropertyNameCaseInsensitive = false, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower @@ -94,6 +100,10 @@ namespace TwitchChatTTS.Seven.Socket.Handlers _logger.Information($"Added 7tv emote [name: {o.Name}][id: {o.Id}]"); } } + finally + { + _lock.ReleaseMutex(); + } } } } diff --git a/Seven/Socket/Handlers/EndOfStreamHandler.cs b/Seven/Socket/Handlers/EndOfStreamHandler.cs index 9f8d4de..9e16f69 100644 --- a/Seven/Socket/Handlers/EndOfStreamHandler.cs +++ b/Seven/Socket/Handlers/EndOfStreamHandler.cs @@ -1,4 +1,3 @@ -using System.Net.WebSockets; using CommonSocketLibrary.Abstract; using CommonSocketLibrary.Common; using TwitchChatTTS.Seven.Socket.Data; diff --git a/Twitch/Redemptions/RedemptionManager.cs b/Twitch/Redemptions/RedemptionManager.cs index 3a06c67..575af70 100644 --- a/Twitch/Redemptions/RedemptionManager.cs +++ b/Twitch/Redemptions/RedemptionManager.cs @@ -27,7 +27,7 @@ namespace TwitchChatTTS.Twitch.Redemptions private readonly AudioPlaybackEngine _playback; private readonly ILogger _logger; private readonly Random _random; - private readonly object _lock; + private readonly ReaderWriterLockSlim _rwls; public RedemptionManager( @@ -50,7 +50,7 @@ namespace TwitchChatTTS.Twitch.Redemptions _playback = playback; _logger = logger; _random = new Random(); - _lock = new object(); + _rwls = new ReaderWriterLockSlim(); var topic = _bus.GetTopic("redemptions_initiation"); topic.Subscribe(data => @@ -110,16 +110,15 @@ namespace TwitchChatTTS.Twitch.Redemptions private void Add(string twitchRedemptionId, string redemptionId) { - lock (_lock) + _rwls.EnterWriteLock(); + try { if (!_redeems.TryGetValue(twitchRedemptionId, out var redeems)) _redeems.Add(twitchRedemptionId, redeems = new List()); var item = _redemptions.TryGetValue(redemptionId, out var r) ? r : null; if (item == null) - { return; - } var redemptions = redeems.Select(r => _redemptions.TryGetValue(r, out var rr) ? rr : null); bool added = false; @@ -138,12 +137,17 @@ namespace TwitchChatTTS.Twitch.Redemptions if (!added) redeems.Add(redemptionId); } + finally + { + _rwls.ExitWriteLock(); + } _logger.Debug($"Added redemption action [redemption id: {redemptionId}][twitch redemption id: {twitchRedemptionId}]"); } private void Add(string twitchRedemptionId, Redemption item) { - lock (_lock) + _rwls.EnterWriteLock(); + try { if (!_redeems.TryGetValue(twitchRedemptionId, out var redemptionNames)) _redeems.Add(twitchRedemptionId, redemptionNames = new List()); @@ -165,6 +169,10 @@ namespace TwitchChatTTS.Twitch.Redemptions if (!added) redemptionNames.Add(item.Id); } + finally + { + _rwls.ExitWriteLock(); + } _logger.Debug($"Added redemption action [redemption id: {item.Id}][twitch redemption id: {twitchRedemptionId}]"); } @@ -419,7 +427,8 @@ namespace TwitchChatTTS.Twitch.Redemptions public IEnumerable Get(string twitchRedemptionId) { - lock (_lock) + _rwls.EnterReadLock(); + try { if (_redeems.TryGetValue(twitchRedemptionId, out var redemptionIds)) return redemptionIds.Select(r => _redemptions.TryGetValue(r, out var redemption) ? redemption : null) @@ -427,15 +436,19 @@ namespace TwitchChatTTS.Twitch.Redemptions .Select(r => _actions.TryGetValue(r!.ActionName, out var action) ? action : null) .Where(a => a != null)!; } + finally + { + _rwls.ExitReadLock(); + } return []; } public void Initialize() { - _logger.Debug($"Redemption manager is about to initialize [redemption count: {_redemptions.Count()}][action count: {_actions.Count}]"); - - lock (_lock) + _rwls.EnterWriteLock(); + try { + _logger.Debug($"Redemption manager is about to initialize [redemption count: {_redemptions.Count()}][action count: {_actions.Count}]"); _redeems.Clear(); var ordered = _redemptions.Select(r => r.Value).Where(r => r != null).OrderBy(r => r.Order); @@ -463,18 +476,31 @@ namespace TwitchChatTTS.Twitch.Redemptions } } } + finally + { + _rwls.ExitWriteLock(); + } _logger.Debug("All redemptions added. Redemption Manager is ready."); } public bool RemoveAction(string actionName) { - return _actions.Remove(actionName); + _rwls.EnterWriteLock(); + try + { + return _actions.Remove(actionName); + } + finally + { + _rwls.ExitWriteLock(); + } } public bool RemoveRedemption(string redemptionId) { - lock (_lock) + _rwls.EnterWriteLock(); + try { if (!_redemptions.TryGetValue(redemptionId, out var redemption)) { @@ -490,6 +516,10 @@ namespace TwitchChatTTS.Twitch.Redemptions return true; } } + finally + { + _rwls.ExitWriteLock(); + } return false; } @@ -507,7 +537,8 @@ namespace TwitchChatTTS.Twitch.Redemptions public bool Update(Redemption redemption) { - lock (_lock) + _rwls.EnterWriteLock(); + try { if (_redemptions.TryGetValue(redemption.Id, out var r)) { @@ -548,6 +579,10 @@ namespace TwitchChatTTS.Twitch.Redemptions return true; } } + finally + { + _rwls.ExitWriteLock(); + } _logger.Warning($"Cannot find redemption by name [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}]"); return false; @@ -555,12 +590,20 @@ namespace TwitchChatTTS.Twitch.Redemptions public bool Update(RedeemableAction action) { - if (_actions.TryGetValue(action.Name, out var a)) + _rwls.EnterWriteLock(); + try { - a.Type = action.Type; - a.Data = action.Data; - _logger.Debug($"Updated redeemable action in redemption manager [action name: {action.Name}]"); - return true; + if (_actions.TryGetValue(action.Name, out var a)) + { + a.Type = action.Type; + a.Data = action.Data; + _logger.Debug($"Updated redeemable action in redemption manager [action name: {action.Name}]"); + return true; + } + } + finally + { + _rwls.ExitWriteLock(); } _logger.Warning($"Cannot find redeemable action by name [action name: {action.Name}]"); diff --git a/Twitch/Socket/Handlers/SessionWelcomeHandler.cs b/Twitch/Socket/Handlers/SessionWelcomeHandler.cs index 9318cc9..aee22d5 100644 --- a/Twitch/Socket/Handlers/SessionWelcomeHandler.cs +++ b/Twitch/Socket/Handlers/SessionWelcomeHandler.cs @@ -39,7 +39,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers _logger.Warning($"Twitch connection has {timeLeft} before it is revoked. Refreshing the token is soon required."); else { - _logger.Error("Twitch connection has its permissions revoked. Refresh the token. Twitch client will not be connecting."); + _logger.Error($"Twitch connection has its permissions revoked. Refresh the token. Twitch client will not be connecting. [expired at: {twitchConnection.ExpiresAt}]"); return; } diff --git a/Twitch/Socket/TwitchConnectionManager.cs b/Twitch/Socket/TwitchConnectionManager.cs index 35bea8a..848d82b 100644 --- a/Twitch/Socket/TwitchConnectionManager.cs +++ b/Twitch/Socket/TwitchConnectionManager.cs @@ -18,21 +18,22 @@ namespace TwitchChatTTS.Twitch.Socket private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; - private readonly object _lock; + private readonly Mutex _mutex; public TwitchConnectionManager(IServiceProvider serviceProvider, ILogger logger) { _serviceProvider = serviceProvider; _logger = logger; - _lock = new object(); + _mutex = new Mutex(); } public TwitchWebsocketClient GetBackupClient() { - lock (_lock) + try { + _mutex.WaitOne(); if (_identified == null) throw new InvalidOperationException("Cannot get backup Twitch client yet. Waiting for identification."); if (_backup != null) @@ -40,12 +41,17 @@ namespace TwitchChatTTS.Twitch.Socket return CreateNewClient(); } + finally + { + _mutex.ReleaseMutex(); + } } public TwitchWebsocketClient GetWorkingClient() { - lock (_lock) + try { + _mutex.WaitOne(); if (_identified == null) { return CreateNewClient(); @@ -53,6 +59,10 @@ namespace TwitchChatTTS.Twitch.Socket return _identified; } + finally + { + _mutex.ReleaseMutex(); + } } private TwitchWebsocketClient CreateNewClient() @@ -74,8 +84,9 @@ namespace TwitchChatTTS.Twitch.Socket private async Task OnDisconnection(TwitchWebsocketClient client) { bool reconnecting = false; - lock (_lock) + try { + _mutex.WaitOne(); if (_identified?.UID == client.UID) { _logger.Debug($"Identified Twitch client has disconnected [client: {client.UID}][main: {_identified.UID}][backup: {_backup?.UID}]"); @@ -92,19 +103,25 @@ namespace TwitchChatTTS.Twitch.Socket else _logger.Warning($"Twitch client disconnected from unknown source [client: {client.UID}][main: {_identified?.UID}][backup: {_backup?.UID}]"); } + finally + { + _mutex.ReleaseMutex(); + } if (reconnecting) { var newClient = GetWorkingClient(); await newClient.Reconnect(); } + } private async Task OnIdentified(TwitchWebsocketClient client) { bool clientDisconnect = false; - lock (_lock) + try { + _mutex.WaitOne(); if (_identified == null || _identified.ReceivedReconnecting) { if (_backup != null && _backup.UID == client.UID) @@ -125,10 +142,14 @@ namespace TwitchChatTTS.Twitch.Socket _logger.Warning($"Twitch client has been identified, but isn't main or backup [client: {client.UID}][main: {_identified.UID}][backup: {_backup?.UID}]"); clientDisconnect = true; } - } - if (clientDisconnect) - await client.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "No need for a tertiary client.")); + if (clientDisconnect) + client.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "No need for a tertiary client.")).Wait(); + } + finally + { + _mutex.ReleaseMutex(); + } } } } \ No newline at end of file diff --git a/Twitch/Socket/TwitchWebsocketClient.cs b/Twitch/Socket/TwitchWebsocketClient.cs index e439fdd..ebb768f 100644 --- a/Twitch/Socket/TwitchWebsocketClient.cs +++ b/Twitch/Socket/TwitchWebsocketClient.cs @@ -17,15 +17,11 @@ namespace TwitchChatTTS.Twitch.Socket private readonly IDictionary _subscriptions; private readonly IBackoff _backoff; private readonly Configuration _configuration; - private bool _disconnected; - private readonly object _lock; public event EventHandler? OnIdentified; public string UID { get; } public string URL; - public bool Connected { get; private set; } - public bool Identified { get; private set; } public string? SessionId { get; private set; } public bool ReceivedReconnecting { get; set; } public bool TwitchReconnected { get; set; } @@ -46,13 +42,14 @@ namespace TwitchChatTTS.Twitch.Socket _backoff = backoff; _configuration = configuration; _subscriptions = new Dictionary(); - _lock = new object(); - _messageTypes = new Dictionary(); - _messageTypes.Add("session_keepalive", typeof(object)); - _messageTypes.Add("session_welcome", typeof(SessionWelcomeMessage)); - _messageTypes.Add("session_reconnect", typeof(SessionWelcomeMessage)); - _messageTypes.Add("notification", typeof(NotificationMessage)); + _messageTypes = new Dictionary + { + { "session_keepalive", typeof(object) }, + { "session_welcome", typeof(SessionWelcomeMessage) }, + { "session_reconnect", typeof(SessionWelcomeMessage) }, + { "notification", typeof(NotificationMessage) } + }; UID = Guid.NewGuid().ToString("D"); @@ -88,25 +85,12 @@ namespace TwitchChatTTS.Twitch.Socket _logger.Information($"Initializing Twitch websocket client."); OnConnected += (sender, e) => { - Connected = true; _logger.Information("Twitch websocket client connected."); - _disconnected = false; }; OnDisconnected += (sender, e) => { - lock (_lock) - { - if (_disconnected) - return; - - _disconnected = true; - } - _logger.Information($"Twitch websocket client disconnected [status: {e.Status}][reason: {e.Reason}][client: {UID}]"); - - Connected = false; - Identified = false; }; } @@ -126,7 +110,6 @@ namespace TwitchChatTTS.Twitch.Socket public void Identify(string sessionId) { - Identified = true; SessionId = sessionId; OnIdentified?.Invoke(this, EventArgs.Empty); }