Changed various locking mechanisms.

This commit is contained in:
Tom
2025-03-29 20:28:36 +00:00
parent eddd9e6403
commit fb04f4003f
11 changed files with 272 additions and 155 deletions

View File

@ -102,7 +102,7 @@ namespace TwitchChatTTS.Chat.Commands.Limits
private IDictionary<T, UserUsageData> _usages { get; }
private IList<UsagePolicyNode<T>> _children { get; }
private ILogger _logger;
private object _lock { get; }
private ReaderWriterLockSlim _rwls { get; }
public UsagePolicyNode(string name, UsagePolicyLimit? data, UsagePolicyNode<T>? parent, ILogger logger, bool root = false)
{
@ -114,100 +114,149 @@ namespace TwitchChatTTS.Chat.Commands.Limits
_usages = new Dictionary<T, UserUsageData>();
_children = new List<UsagePolicyNode<T>>();
_logger = logger;
_lock = new object();
_rwls = new ReaderWriterLockSlim();
}
public UsagePolicyNode<T>? Get(IEnumerable<string> 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<T>? Remove(IEnumerable<string> 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<string> 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<T>(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<T>(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;

View File

@ -3,7 +3,6 @@ namespace TwitchChatTTS.Chat.Emotes
public class EmoteDatabase : IEmoteDatabase
{
private readonly IDictionary<string, string> _emotes;
public IDictionary<string, string> Emotes { get => _emotes.AsReadOnly(); }
public EmoteDatabase()
{