835c6c2001-06-17Martin Nilsson // This file is part of Roxen WebServer.
f41b982009-05-07Martin Stjernholm // Copyright © 1999 - 2009, Roxen IS.
835c6c2001-06-17Martin Nilsson // // A throttling co-ordinator. Will share bandiwdth among // many pending requests. // // By Francesco Chemolli // // Notice: this works under the hypothesis that there's only one thread // shuffling data (so no locking is done). This might be a wrong // assumption. Per? Grubba? //
199d031999-09-05Francesco Chemolli 
0917d32013-03-04Anders Johansson constant cvs_version="$Id$";
199d031999-09-05Francesco Chemolli  #define DEFAULT_MINGRANT 1300 #define DEFAULT_MAXGRANT 65000 #ifdef THROTTLING_DEBUG
10c7e11999-12-28Martin Nilsson # undef THROTTLING_DEBUG
91d3c32001-03-12Martin Nilsson # define THROTTLING_DEBUG(X) report_debug("throttler: "+X+"\n")
199d031999-09-05Francesco Chemolli #else
10c7e11999-12-28Martin Nilsson # define THROTTLING_DEBUG(X)
199d031999-09-05Francesco Chemolli #endif private int bucket=0; private int fill_rate=0; //if 0, no throttling is done private int depth; //the max bucket depth private int last_fill=0; private int min_grant=0; //if we'd grant less than this, don't grant at all. private int max_grant=0; //maximum granted size for a single request
a00f871999-11-29Per Hedbor ADT.Queue requests_queue; //lazily instantiated.
199d031999-09-05Francesco Chemolli 
1128492002-03-28Per Hedbor //request format: ({ int howmuch, function callback, string host, // array(mixed) extra_args })
199d031999-09-05Francesco Chemolli  //start throttling, given rate, depth, initial fillup, and min grant //if not supplied, mingrant is set to DEFAULT_MINGRANT by default //same for maxgrant
10c7e11999-12-28Martin Nilsson void throttle (int r, int d, int|void initial,
199d031999-09-05Francesco Chemolli  int|void mingrant, int|void maxgrant) {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("throttle(rate="+r+", depth="+d+
199d031999-09-05Francesco Chemolli  ",\n\tinitial="+initial+", mingrant="+mingrant+ ", maxgrant="+maxgrant); fill_rate=r; depth=max(d,r);
1128492002-03-28Per Hedbor  if( initial ) bucket=initial;
199d031999-09-05Francesco Chemolli  min_grant=(zero_type(mingrant)?DEFAULT_MINGRANT:mingrant); max_grant=(zero_type(maxgrant)?DEFAULT_MAXGRANT:maxgrant);
1128492002-03-28Per Hedbor  if( !requests_queue ) // First time. { last_fill=time(1); requests_queue=ADT.Queue(); }
199d031999-09-05Francesco Chemolli  remove_call_out(safety_net); call_out(safety_net,1); } //fills the bucket up, and tries to wake up some requests if possible. private void fill_bucket() { int toadd; if (!fill_rate) //nothing to do. return; toadd=((time(1)-last_fill)*fill_rate); bucket+=toadd;
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("adding "+toadd+" tokens");
199d031999-09-05Francesco Chemolli  last_fill=time(1); if (bucket>depth)
0d79b42001-01-28Per Hedbor  {
199d031999-09-05Francesco Chemolli  bucket=depth;
0d79b42001-01-28Per Hedbor  if( min_grant >= depth ) { THROTTLING_DEBUG("Adjusting min_grant to depth ("+depth+") was "+ min_grant); min_grant = depth; } }
199d031999-09-05Francesco Chemolli  wake_up_some(); } //handles as many pending requests as possible private void wake_up_some () {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("wake_up_some");
199d031999-09-05Francesco Chemolli  array request;
4da80c2002-03-27Per Hedbor  multiset seen = (<>); multiset have = (<>);
199d031999-09-05Francesco Chemolli  while ((!requests_queue->is_empty()) && (bucket >= min_grant)) { request=requests_queue->get();
4da80c2002-03-27Per Hedbor  if( seen[request] ) { have = (<>); seen = (<>); } seen[request] = 1; if( have[request[2]] ) { requests_queue->put( request ); continue; } have[request[2]]=1;
199d031999-09-05Francesco Chemolli  grant(@request); }
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("Done waking up requests");
199d031999-09-05Francesco Chemolli } //handles a single request. It assumes it has been granted, otherwise //it will allow going over quota.
4da80c2002-03-27Per Hedbor private void grant (int howmuch, function callback, string host, array(mixed) cb_args ) {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("grant("+howmuch+"). bucket="+bucket); if (!callback) { THROTTLING_DEBUG("no callback. Exiting"); return; }
199d031999-09-05Francesco Chemolli  if (howmuch >= bucket) {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("limiting granted bandwidth");
199d031999-09-05Francesco Chemolli  howmuch=bucket; } bucket-=howmuch; callback(howmuch,@cb_args); } //request for permission to send this much data. //when granted, callback will be called. First arg is the number //of allowed bytes. Then the hereby supplied args. void request (int howmuch, function(int,mixed ...:void) callback,
4da80c2002-03-27Per Hedbor  string host, mixed ... cb_args) {
199d031999-09-05Francesco Chemolli  if (!fill_rate) { //no throttling is actually done
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("auto-grant (not throttling)");
199d031999-09-05Francesco Chemolli  callback(howmuch,@cb_args); return; }
10c7e11999-12-28Martin Nilsson 
199d031999-09-05Francesco Chemolli  if (howmuch > max_grant) {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("request too big, limiting");
199d031999-09-05Francesco Chemolli  howmuch=max_grant; }
10c7e11999-12-28Martin Nilsson 
4da80c2002-03-27Per Hedbor // fill_bucket(); //maybe we can squeeze some more bandwidth.
10c7e11999-12-28Martin Nilsson 
199d031999-09-05Francesco Chemolli  if (bucket <= min_grant ) { //bad luck. Nothing to allow. Enqueue
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("no tokens, enqueueing");
4da80c2002-03-27Per Hedbor  requests_queue->put( ({howmuch,callback,host,cb_args}) );
199d031999-09-05Francesco Chemolli  return; }
10c7e11999-12-28Martin Nilsson 
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("granting");
4da80c2002-03-27Per Hedbor  grant (howmuch, callback, host, cb_args);
199d031999-09-05Francesco Chemolli } //after a request has been granted, if the request doesn't use all of the //assigned bandwidth, it can return the unused amount. void report_unused (int howmuch) {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("got an unused bandwidth report ("+howmuch+")");
199d031999-09-05Francesco Chemolli  bucket+=howmuch; } //a call_out cycle, in order to make sure that we fill the bucket at least //once per second. Otherwise we might enqueue all the pending requests, and //get stuck until a new one is done. private void safety_net () {
53865e1999-10-10Francesco Chemolli  /* THROTTLING_DEBUG("throttler: safety net"); */
199d031999-09-05Francesco Chemolli  call_out(safety_net,1); if (requests_queue->is_empty()) return; fill_bucket(); //might wake a request up there. } void destruct () {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("destroying");
199d031999-09-05Francesco Chemolli  remove_call_out(safety_net); } #ifdef THROTTLING_DEBUG void create() {
53865e1999-10-10Francesco Chemolli  THROTTLING_DEBUG("creating");
199d031999-09-05Francesco Chemolli } #endif