/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Amazon.Runtime.Internal
{
///
/// This class is responsible for keeping track of Retry capacity across different ServiceURLs.
///
public class CapacityManager:IDisposable
{
///
/// CapacityType determines the type of capacity to obtain or use.
///
public enum CapacityType
{
///
/// The increment capacity type adds capacity.
///
Increment,
///
/// The default retry capacity type uses the default capacity amount.
///
Retry,
///
/// The timeout capacity type uses the timeout capacity amount.
///
Timeout
}
//Dispose Method
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_disposed = true;
}
}
public CapacityManager(int throttleRetryCount, int throttleRetryCost, int throttleCost)
: this(throttleRetryCount, throttleRetryCost, throttleCost, throttleRetryCost)
{
}
public CapacityManager(int throttleRetryCount, int throttleRetryCost, int throttleCost, int timeoutRetryCost)
{
retryCost = throttleRetryCost;
initialRetryTokens = throttleRetryCount;
noRetryIncrement = throttleCost;
this.timeoutRetryCost = timeoutRetryCost;
}
///
/// This method acquires a said retry capacity if the container has the capacity.
///
/// Contains the RetryCapacity object for the said ServiceURL.
public bool TryAcquireCapacity(RetryCapacity retryCapacity)
{
return TryAcquireCapacity(retryCapacity, CapacityType.Retry);
}
///
/// This method acquires a said retry capacity if the container has the capacity.
///
/// Contains the RetryCapacity object for the said ServiceURL.
/// Specifies what capacity type cost to use for obtaining capacity
public bool TryAcquireCapacity(RetryCapacity retryCapacity, CapacityType capacityType)
{
var capacityCost = capacityType == CapacityType.Timeout ? timeoutRetryCost : retryCost;
if (capacityCost < 0)
{
return false;
}
lock (retryCapacity)
{
if (retryCapacity.AvailableCapacity - capacityCost >= 0)
{
retryCapacity.AvailableCapacity -= capacityCost;
return true;
}
else
{
return false;
}
}
}
///
/// This method calls a method to release capacity back
/// based on whether it was a successful response or a successful retry response. This is invoked by a retry request response.
///
/// if this request is a retry, use a different capacity cost
/// Contains the RetryCapacity object for the said ServiceURL.
[Obsolete("This method is no longer used in favor of allowing the caller to specify the type of capacity to release.")]
public void TryReleaseCapacity(bool isRetryRequest, RetryCapacity retryCapacity)
{
ReleaseCapacity(isRetryRequest ? CapacityType.Retry : CapacityType.Increment, retryCapacity);
}
///
/// This method calls a method to release capacity back
/// based on whether it was a successful response or a successful retry response. This is invoked by a retry request response.
///
/// Specifies what capacity type cost to use for adding capacity
/// Contains the RetryCapacity object for the said ServiceURL.
public void ReleaseCapacity(CapacityType capacityType, RetryCapacity retryCapacity)
{
switch (capacityType)
{
case CapacityType.Retry:
ReleaseCapacity(retryCost, retryCapacity);
break;
case CapacityType.Timeout:
ReleaseCapacity(timeoutRetryCost, retryCapacity);
break;
case CapacityType.Increment:
ReleaseCapacity(noRetryIncrement, retryCapacity);
break;
default:
throw new NotSupportedException($"Unsupported CapacityType {capacityType}");
}
}
///
/// Ths method fetches the RetryCapacity for the given ServiceURL from CapacityManager.CapacityContainer
///
public RetryCapacity GetRetryCapacity(string serviceURL)
{
RetryCapacity retryCapacity;
if (!(TryGetRetryCapacity(serviceURL, out retryCapacity)))
{
retryCapacity = AddNewRetryCapacity(serviceURL);
}
return retryCapacity;
}
private bool _disposed;
//Dictionary that keeps track of the available capacity by ServiceURLs
private static Dictionary _serviceUrlToCapacityMap = new Dictionary();
//Read write slim lock for performing said operations on CapacityManager._serviceUrlToCapacityMap.
private static ReaderWriterLockSlim _rwlock = new ReaderWriterLockSlim();
// This parameter sets the cost of making a retry call on a request.The default value is set at 5.
private readonly int retryCost;
// This parameter sets the cost of making a retry call when the request was a timeout. The default value is 5 for
// legacy retry modes and 10 for all other retry modes.
private readonly int timeoutRetryCost;
// Maximum capacity in a bucket set to 100 for legacy retry mode and 500 for all other retry modes.
private readonly int initialRetryTokens;
// For every successful request, lesser value capacity would be released. This
// is done to ensure that the bucket has a strategy for filling up if an explosion of bad retry requests
// were to deplete the entire capacity.The default value is set at 1.
private readonly int noRetryIncrement;
private static bool TryGetRetryCapacity(string key, out RetryCapacity value)
{
_rwlock.EnterReadLock();
try
{
if (_serviceUrlToCapacityMap.TryGetValue(key, out value))
{
return true;
}
return false;
}
finally
{
_rwlock.ExitReadLock();
}
}
private RetryCapacity AddNewRetryCapacity(string serviceURL)
{
RetryCapacity retryCapacity;
_rwlock.EnterUpgradeableReadLock();
try
{
if (!(_serviceUrlToCapacityMap.TryGetValue(serviceURL, out retryCapacity)))
{
_rwlock.EnterWriteLock();
try
{
retryCapacity = new RetryCapacity(retryCost * initialRetryTokens);
_serviceUrlToCapacityMap.Add(serviceURL, retryCapacity);
return retryCapacity;
}
finally
{
_rwlock.ExitWriteLock();
}
}
else
{
return retryCapacity;
}
}
finally
{
_rwlock.ExitUpgradeableReadLock();
}
}
///
/// This method releases capacity back. This is invoked by the TryReleaseCapacity method.
///
/// Contains the RetryCapacity object for the said ServiceURL.
/// The capacity that needs to be released based on whether it was a successful response or a successful retry response.
private static void ReleaseCapacity(int capacity, RetryCapacity retryCapacity)
{
if (retryCapacity.AvailableCapacity >= 0 && retryCapacity.AvailableCapacity < retryCapacity.MaxCapacity)
{
lock (retryCapacity)
{
retryCapacity.AvailableCapacity = Math.Min((retryCapacity.AvailableCapacity + capacity), retryCapacity.MaxCapacity);
}
}
}
}
///
/// This class is the RetryCapacity class for a given ServiceURL.
///
public class RetryCapacity
{
//maximum capacity in a bucket.
private readonly int _maxCapacity;
//available ncapacity in a bucket for a given ServiceURL.
public int AvailableCapacity { get; set; }
//maximum capacity in a bucket.
public int MaxCapacity
{
get
{
return _maxCapacity;
}
}
public RetryCapacity(int maxCapacity)
{
_maxCapacity = maxCapacity;
this.AvailableCapacity = maxCapacity;
}
}
}