#include "FileZilla.h"
#include "ratelimiter.h"
static const int tickDelay = 250;
CRateLimiter* CRateLimiter::m_pTheRateLimiter = 0;
BEGIN_EVENT_TABLE(CRateLimiter, wxEvtHandler)
EVT_TIMER(wxID_ANY, CRateLimiter::OnTimer)
END_EVENT_TABLE()
CRateLimiter::CRateLimiter(COptionsBase* pOptions) :
m_usageCount(1),
m_pOptions(pOptions)
{
m_timer.SetOwner(this);
m_tokenDebt[0] = 0;
m_tokenDebt[1] = 0;
}
CRateLimiter::~CRateLimiter()
{
}
CRateLimiter* CRateLimiter::Create(COptionsBase* pOptions)
{
if (!m_pTheRateLimiter)
m_pTheRateLimiter = new CRateLimiter(pOptions);
else
m_pTheRateLimiter->m_usageCount++;
return m_pTheRateLimiter;
}
CRateLimiter* CRateLimiter::Get()
{
return m_pTheRateLimiter;
}
void CRateLimiter::Free()
{
wxASSERT(m_pTheRateLimiter);
if (!m_pTheRateLimiter)
return;
wxASSERT(this == m_pTheRateLimiter);
if (m_usageCount <= 1)
{
delete this;
m_pTheRateLimiter = 0;
}
else
m_usageCount--;
}
void CRateLimiter::AddObject(CRateLimiterObject* pObject)
{
m_objectList.push_back(pObject);
for (int i = 0; i < 2; i++)
{
int limit = m_pOptions->GetOptionVal(OPTION_SPEEDLIMIT_INBOUND + i) * 1024;
if (limit > 0)
{
int tokens = limit * tickDelay / 1000;
tokens /= m_objectList.size();
if (m_tokenDebt[i] > 0)
{
if (tokens >= m_tokenDebt[i])
{
tokens -= m_tokenDebt[i];
m_tokenDebt[i] = 0;
}
else
{
tokens = 0;
m_tokenDebt[i] -= tokens;
}
}
pObject->m_bytesAvailable[i] = tokens;
}
else
pObject->m_bytesAvailable[i] = -1;
if (!m_timer.IsRunning())
m_timer.Start(tickDelay, false);
}
}
void CRateLimiter::RemoveObject(CRateLimiterObject* pObject)
{
for (std::list<CRateLimiterObject*>::iterator iter = m_objectList.begin(); iter != m_objectList.end(); iter++)
{
if (*iter == pObject)
{
for (int i = 0; i < 2; i++)
{
// If an object already used up some of its assigned tokens, add them to m_tokenDebt,
// so that newly created objects get less initial tokens.
// That ensures that rapidly adding and removing objects does not exceed the rate
int limit = m_pOptions->GetOptionVal(OPTION_SPEEDLIMIT_INBOUND + i) * 1024;
int tokens = limit * tickDelay / 1000;
tokens /= m_objectList.size();
if ((*iter)->m_bytesAvailable[i] < tokens)
m_tokenDebt[i] += tokens - (*iter)->m_bytesAvailable[i];
}
m_objectList.erase(iter);
break;
}
}
for (int i = 0; i < 2; i++)
{
for (std::list<CRateLimiterObject*>::iterator iter = m_wakeupList[i].begin(); iter != m_wakeupList[i].end(); iter++)
{
if (*iter == pObject)
{
m_wakeupList[i].erase(iter);
break;
}
}
}
}
void CRateLimiter::OnTimer(wxTimerEvent& event)
{
std::list<CRateLimiterObject*> objectsToUnwait;
for (int i = 0; i < 2; i++)
{
m_tokenDebt[i] = 0;
if (!m_objectList.size())
continue;
int limit = m_pOptions->GetOptionVal(OPTION_SPEEDLIMIT_INBOUND + i) * 1024;
if (!limit)
{
for (std::list<CRateLimiterObject*>::iterator iter = m_objectList.begin(); iter != m_objectList.end(); iter++)
{
(*iter)->m_bytesAvailable[i] = -1;
if ((*iter)->m_waiting[i])
m_wakeupList[i].push_back(*iter);
}
continue;
}
int tokens = limit * tickDelay / 1000;
if (!tokens)
tokens = 1;
int maxTokens = tokens * GetBucketSize();
// Get amount of tokens for each object
int tokensPerObject = tokens / m_objectList.size();
if (!tokensPerObject)
tokensPerObject = 1;
tokens = 0;
// This list will hold all objects which didn't reach maxTokens
std::list<CRateLimiterObject*> unsaturatedObjects;
for (std::list<CRateLimiterObject*>::iterator iter = m_objectList.begin(); iter != m_objectList.end(); iter++)
{
if ((*iter)->m_bytesAvailable[i] == -1)
{
wxASSERT(!(*iter)->m_waiting[i]);
(*iter)->m_bytesAvailable[i] = tokensPerObject;
unsaturatedObjects.push_back(*iter);
}
else
{
(*iter)->m_bytesAvailable[i] += tokensPerObject;
if ((*iter)->m_bytesAvailable[i] > maxTokens)
{
tokens += (*iter)->m_bytesAvailable[i] - maxTokens;
(*iter)->m_bytesAvailable[i] = maxTokens;
}
else
unsaturatedObjects.push_back(*iter);
if ((*iter)->m_waiting[i])
m_wakeupList[i].push_back(*iter);
}
}
// If there are any left-over tokens (in case of objects with a rate below the limit)
// assign to the unsaturated sources
while (tokens && !unsaturatedObjects.empty())
{
tokensPerObject = tokens / unsaturatedObjects.size();
if (!tokensPerObject)
break;
tokens = 0;
std::list<CRateLimiterObject*> objects;
objects.swap(unsaturatedObjects);
for (std::list<CRateLimiterObject*>::iterator iter = objects.begin(); iter != objects.end(); iter++)
{
(*iter)->m_bytesAvailable[i] += tokensPerObject;
if ((*iter)->m_bytesAvailable[i] > maxTokens)
{
tokens += (*iter)->m_bytesAvailable[i] - maxTokens;
(*iter)->m_bytesAvailable[i] = maxTokens;
}
else
unsaturatedObjects.push_back(*iter);
}
}
}
WakeupWaitingObjects();
if (m_objectList.empty())
m_timer.Stop();
}
void CRateLimiter::WakeupWaitingObjects()
{
for (int i = 0; i < 2; i++)
{
while (!m_wakeupList[i].empty())
{
CRateLimiterObject* pObject = m_wakeupList[i].front();
m_wakeupList[i].pop_front();
if (!pObject->m_waiting[i])
continue;
wxASSERT(pObject->m_bytesAvailable != 0);
pObject->m_waiting[i] = false;
pObject->OnRateAvailable((rate_direction)i);
}
}
}
int CRateLimiter::GetBucketSize() const
{
const int burst_tolerance = m_pOptions->GetOptionVal(OPTION_SPEEDLIMIT_BURSTTOLERANCE);
int bucket_size = 1000 / tickDelay;
switch (burst_tolerance)
{
case 1:
bucket_size *= 2;
break;
case 2:
bucket_size *= 5;
break;
default:
break;
}
return bucket_size;
}
CRateLimiterObject::CRateLimiterObject()
{
for (int i = 0; i < 2; i++)
{
m_waiting[i] = false;
m_bytesAvailable[i] = -1;
}
}
void CRateLimiterObject::UpdateUsage(enum CRateLimiter::rate_direction direction, int usedBytes)
{
wxASSERT(usedBytes <= m_bytesAvailable[direction]);
if (usedBytes > m_bytesAvailable[direction])
m_bytesAvailable[direction] = 0;
else
m_bytesAvailable[direction] -= usedBytes;
}
void CRateLimiterObject::Wait(enum CRateLimiter::rate_direction direction)
{
wxASSERT(m_bytesAvailable[direction] == 0);
m_waiting[direction] = true;
}
bool CRateLimiterObject::IsWaiting(enum CRateLimiter::rate_direction direction) const
{
return m_waiting[direction];
}
syntax highlighted by Code2HTML, v. 0.9.1