/*
* Copyright 2010-2016 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
{
//Dispose Method
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_rwlock.Dispose();
_disposed = true;
}
}
public CapacityManager(int throttleRetryCount, int throttleRetryCost, int throttleCost)
{
THROTTLE_RETRY_REQUEST_COST = throttleRetryCost;
THROTTLED_RETRIES = throttleRetryCount;
THROTTLE_REQUEST_COST = throttleCost;
}
///
/// 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)
{
if (THROTTLE_RETRY_REQUEST_COST < 0)
{
return false;
}
lock (retryCapacity)
{
if (retryCapacity.AvailableCapacity - THROTTLE_RETRY_REQUEST_COST >= 0)
{
retryCapacity.AvailableCapacity -= THROTTLE_RETRY_REQUEST_COST;
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.
public void TryReleaseCapacity(bool isRetryRequest,RetryCapacity retryCapacity)
{
if(isRetryRequest)
{
ReleaseCapacity(THROTTLE_RETRY_REQUEST_COST,retryCapacity);
}
else
{
ReleaseCapacity(THROTTLE_REQUEST_COST,retryCapacity);
}
}
///
/// 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 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 THROTTLE_RETRY_REQUEST_COST;
//maximum capacity in a bucket set to 100.
private readonly int THROTTLED_RETRIES;
// 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 THROTTLE_REQUEST_COST;
private 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(THROTTLE_RETRY_REQUEST_COST * THROTTLED_RETRIES);
_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;
}
}
}