@@ -16,8 +16,8 @@ namespace Discord.Net.Rest
private readonly ConcurrentQueue<RestRequest> _queue;
private readonly SemaphoreSlim _lock;
private Task _resetTask;
private DateTime? _retryAfter;
private bool _waitingToProcess ;
private bool _waitingToProcess, _destroyed; //TODO: Remove _destroyed
private int _id ;
public int WindowMaxCount { get; }
public int WindowSeconds { get; }
@@ -44,10 +44,12 @@ namespace Discord.Net.Rest
WindowSeconds = windowSeconds;
_queue = new ConcurrentQueue<RestRequest>();
_lock = new SemaphoreSlim(1, 1);
_id = new System.Random().Next(0, int.MaxValue);
}
public void Queue(RestRequest request)
{
if (_destroyed) throw new Exception();
//Assume this obj's parent is under lock
_queue.Enqueue(request);
@@ -75,7 +77,7 @@ namespace Discord.Net.Rest
//If we're waiting to reset (due to a rate limit exception, or preemptive check), abort
if (WindowCount == WindowMaxCount) return;
//Get next request, return if queue is empty
if (!_queue.TryPeek(out request)) return ;
if (!_queue.TryPeek(out request)) break ;
try
{
@@ -88,17 +90,20 @@ namespace Discord.Net.Rest
}
catch (HttpRateLimitException ex) //Preemptive check failed, use Discord's time instead of our own
{
if (_resetTask == null)
WindowCount = WindowMaxCount;
var task = _resetTask;
if (task != null)
{
//No reset has been queued yet, lets create one as if this *was* preemptive
_resetTask = ResetAfter(ex.RetryAfterMilliseconds);
Debug($"External rate limit: Reset in {ex.RetryAfterMilliseconds} ms");
Debug($"External rate limit: Extended to {ex.RetryAfterMilliseconds} ms");
var retryAfter = DateTime.UtcNow.AddMilliseconds(ex.RetryAfterMilliseconds);
await task.ConfigureAwait(false);
int millis = (int)Math.Ceiling((DateTime.UtcNow - retryAfter).TotalMilliseconds);
_resetTask = ResetAfter(millis);
}
else
{
//A preemptive reset is already queued, set RetryAfter to extend it
_retryAfter = DateTime.UtcNow.AddMilliseconds(ex.RetryAfterMilliseconds);
Debug($"External rate limit: Extended to {ex.RetryAfterMilliseconds} ms");
Debug($"External rate limit: Reset in {ex.RetryAfterMilliseconds} ms");
_resetTask = ResetAfter(ex.RetryAfterMilliseconds);
}
return;
}
@@ -132,6 +137,25 @@ namespace Discord.Net.Rest
Debug($"Internal rate limit: Reset in {WindowSeconds * 1000} ms");
}
}
//If queue is empty, non-global, and there is no active rate limit, remove this bucket
if (_resetTask == null && _bucketGroup == BucketGroup.Guild)
{
try
{
await _parent.Lock().ConfigureAwait(false);
if (_queue.IsEmpty) //Double check, in case a request was queued before we got both locks
{
Debug($"Destroy");
_parent.DestroyGuildBucket((GuildBucket)_bucketId, _guildId);
_destroyed = true;
}
}
finally
{
_parent.Unlock();
}
}
}
finally
{
@@ -155,36 +179,14 @@ namespace Discord.Net.Rest
{
await Lock().ConfigureAwait(false);
//If an extension has been planned, start a new wait task
if (_retryAfter != null)
{
_resetTask = ResetAfter((int)(_retryAfter.Value - DateTime.UtcNow).TotalMilliseconds);
_retryAfter = null;
return;
}
Debug($"Reset");
//Reset the current window count and set our state back to normal
WindowCount = 0;
_resetTask = null;
//Wait is over, work through the current queue
await ProcessQueue().ConfigureAwait(false);
//If queue is empty and non-global, remove this bucket
if (_bucketGroup == BucketGroup.Guild && _queue.IsEmpty)
{
try
{
await _parent.Lock().ConfigureAwait(false);
if (_queue.IsEmpty) //Double check, in case a request was queued before we got both locks
_parent.DestroyGuildBucket((GuildBucket)_bucketId, _guildId);
}
finally
{
_parent.Unlock();
}
}
}
finally
{
@@ -217,7 +219,7 @@ namespace Discord.Net.Rest
name = "Unknown";
break;
}
System.Diagnostics.Debug.WriteLine($"[{name}] {text}");
System.Diagnostics.Debug.WriteLine($"[{name} {_id} ] {text}");
}
}
}