Sunday, February 10, 2008

Throttling requests - STL/Windows

In a post a few days ago I posted a problem - ensure that a service/API/resource is not called more than N times in any fixed interval. Here's the solution in STL-ish C++.

For more discussion (and for perl implementation - I don't read perl so I can't vouch for its correctness though), see this post and comments: http://1-800-magic.blogspot.com/2008/02/throttling-service-requests-puzzle.html.


#include <windows.h>
#include <stdio.h>
#include <assert.h>

#include <queue>

using namespace std;

// Class that throttles requests. Ensures that
// the StartRequest cannot be called more than
// a given amount of time in any given interval.
//
// StartRequest blocks until this requirement
// is satisfied.
//
// TryStartRequest returns 0 if the request was
// cleared, or a non-0 number of milliseconds to
// sleep before the next attempt.
//
// Simple usage, 10 requests per second:
//
// Throttler *t = new Throttler(10, 1000);
// ...
// ServiceRequest(Throtter *t, ...) {
// t->StartRequest();
// .. do work ..
//
class Throttler {
private:
queue<DWORD> ticks;
queue<DWORD>::size_type max_requests;
DWORD interval;

CRITICAL_SECTION cs;

// Removes all expired entried from the queue.
// cs should be held.
void PopExpired(DWORD tick_now) {
assert(cs.OwningThread ==
(HANDLE)GetCurrentThreadId());

while (!ticks.empty()) {
if (tick_now - ticks.front() < interval)
break;
ticks.pop();
}
}

public:
Throttler(int number_of_requests,
DWORD interval_ms) {
// leave one extra space for sentinel
max_requests = number_of_requests;
interval = interval_ms;
InitializeCriticalSection(&cs);
}

~Throttler() {
DeleteCriticalSection(&cs);
}

DWORD TryStartRequest(void) {
DWORD result = 0;
EnterCriticalSection(&cs);

DWORD now = GetTickCount();
PopExpired(now);

if (ticks.size() < max_requests) {
// We have space.
ticks.push(now);
} else {
// We've filled the queue. Calculate the
// wait time.
result = interval - (now - ticks.front());
assert(result <= interval); // no overflow
}
LeaveCriticalSection(&cs);
return result;
}

void StartRequest(void) {
while (DWORD sleep = TryStartRequest()) {
Sleep(sleep);
}
}
};

No comments: