]> jfr.im git - irc/quakenet/qwebirc.git/commitdiff
Change the way timeouts are processed entirely to make the entire system more resilient.
authorChris Porter <redacted>
Tue, 17 Feb 2009 03:06:28 +0000 (03:06 +0000)
committerChris Porter <redacted>
Tue, 17 Feb 2009 03:06:28 +0000 (03:06 +0000)
config.py.example
js/irc/ircconnection.js
qwebirc/engines/ajaxengine.py
run.py
twisted/plugins/webirc.py

index 45f36773cc1bf3df3ce84e92dc52508f4817599f..0b53f34e5b1f7b3fda94b34d430295cdc46dcf0b 100644 (file)
@@ -14,3 +14,5 @@ FEEDBACK_TO = "moo@moo.com"
 FEEDBACK_SMTP_HOST = "127.0.0.1"
 FEEDBACK_SMTP_PORT = 25
 ADMIN_ENGINE_HOSTS = ["127.0.0.1"]
+HTTP_REQUEST_TIMEOUT = 5
+HTTP_AJAX_REQUEST_TIMEOUT = 30
index 8bc4770542ded934eb6e094513066a3ca9119c57..7de787d6ad0cf4bfe276bdd1652f7ea1a3094617 100644 (file)
@@ -4,10 +4,12 @@ qwebirc.irc.IRCConnection = new Class({
   Implements: [Events, Options],
   options: {
     initialNickname: "ircconnX",
-    timeout: 30000,
-    floodInterval: 250,
-    floodMax: 5,
-    errorAlert: true
+    timeout: 45000,
+    floodInterval: 200,
+    floodMax: 10,
+    floodReset: 5000,
+    errorAlert: true,
+    maxRetries: 5
   },
   initialize: function(options) {
     this.setOptions(options);
@@ -17,38 +19,48 @@ qwebirc.irc.IRCConnection = new Class({
     this.counter = 0;
     this.disconnected = false;
     
-    this.lastActiveRequest = 0;
-    this.floodCounter = 0;
+    this.__floodLastRequest = 0;
+    this.__floodCounter = 0;
+    this.__floodLastFlood = 0;
     
-    this.activerequest = null;
-    this.timeoutid = null;
+    this.__retryAttempts = 0;
+    
+    this.__timeoutId = null;
+    this.__lastActiveRequest = null;
+    this.__activeRequest = null;
+    
+    this.haha = 0
   },
   __error: function(text) {
     this.fireEvent("error", text);
     if(this.options.errorAlert)
       alert(text);
   },
-  newRequest: function(url, onComplete, floodProtection) {
+  newRequest: function(url, floodProtection) {
+    if(this.disconnected)
+      return null;
+      
     if(floodProtection) {
       var t = new Date().getTime();
       
-      if(t - this.lastActiveRequest < this.options.floodInterval) {
-        if(this.floodCounter++ >= this.options.floodMax) {
+      if(t - this.__floodLastRequest < this.options.floodInterval) {
+        if(this.__floodLastFlood != 0 && (t - this.__floodLastFlood > this.options.floodReset)) {
+          this.floodCounter = 0;
+        }
+        this.__floodLastFlood = t;
+        if(this.__floodCounter++ >= this.options.floodMax) {
           if(!this.disconnected) {
             this.disconnect();
             this.__error("BUG: uncontrolled flood detected -- disconnected.");
-            return {"send": function() { }, "cancel": function() { }};
           }
+          return null;
         }
-      } else {
-        this.floodCounter = 0;
       }
-      this.lastActiveRequest = t;
+      this.__floodLastRequest = t;
     }
     
     var r = new Request.JSON({
-      url: "/e/" + url + "?r=" + this.cacheAvoidance + "&t=" + this.counter++,
-      onComplete: onComplete
+      url: "/e/" + url + "?r=" + this.cacheAvoidance + "&t=" + this.counter++
     });
     
     /* try to minimise the amount of headers */
@@ -74,7 +86,9 @@ qwebirc.irc.IRCConnection = new Class({
   send: function(data) {
     if(this.disconnected)
       return false;
-    var r = this.newRequest("p", function(o) {
+    var r = this.newRequest("p");
+    
+    r.addEvent("complete", function(o) {
       if(!o || (o[0] == false)) {
         if(!this.disconnected) {
           this.disconnected = true;
@@ -83,87 +97,116 @@ qwebirc.irc.IRCConnection = new Class({
         return false;
       }
     }.bind(this));
+    
     r.send("s=" + this.sessionid + "&c=" + encodeURIComponent(data));
     return true;
   },
-  __timeout: function() {
-    if(this.lastactiverequest) {
-      this.lastactiverequest.cancel();
-      this.lastactiverequest = null;
+  __processData: function(o) {
+    if(o[0] == false) {
+      if(!this.disconnected) {
+        this.disconnected = true;
+        this.__error("An error occured: " + o[1]);
+      }
+      return false;
     }
-    if(this.activerequest) {
-      this.lastactiverequest = this.activerequest;
-      /*this.activerequest.cancel();
-      this.activerequest = null;*/
+    
+    this.__retryAttempts = 0;
+    o.each(function(x) {
+      this.fireEvent("recv", [x]);
+    }, this);
+    
+    return true;
+  },
+  __scheduleTimeout: function() {
+    if(this.options.timeout)
+      this.__timeoutId = this.__timeoutEvent.delay(this.options.timeout, this);
+  },
+  __cancelTimeout: function() {
+    if($defined(this.__timeoutId)) {
+      $clear(this.__timeoutId);
+      this.__timeoutId = null;
+    }
+  },
+  __timeoutEvent: function() {
+    this.__timeoutId = null;
+    
+    if(!$defined(this.__activeRequest))
+      return;
+      
+    if(this.__checkRetries()) {
+      if(this.__lastActiveRequest)
+        this.__lastActiveRequest.cancel();
+        
+      this.__activeRequest.__replaced = true;
+      this.__lastActiveRequest = this.__activeRequest;
+      this.recv();
+    } else {
+      this.__cancelRequests();
     }
-    if($defined(this.timeoutid)) {
-      $clear(this.timeoutid);
-      this.timeoutid = null;
+  },
+  __checkRetries: function() {
+    /* hmm, something went wrong! */
+    if(this.__retryAttempts++ >= this.options.maxRetries) {
+      this.disconnect();
+      
+      this.__error("Error: connection closed after several requests failed.");
+      return false;
     }
-    this.recv();
+    
+    return true;
   },
   recv: function() {
-    var r = this.newRequest("s", function(o) {
-      if(this.lastactiverequest != r) 
-        this.activerequest = null;
+    var r = this.newRequest("s", true);
+    if(!$defined(r))
+      return;
+
+    this.__activeRequest = r;
+    r.__replaced = false;
+    
+    var onComplete = function(o) {
+      /* if we're a replaced requests... */
+      if(r.__replaced) {
+        this.__lastActiveRequest = null;
         
-      if($defined(this.timeoutid)) {
-        $clear(this.timeoutid);
-        this.timeoutid = null;
+        if(o)          
+          this.__processData(o);
+        return;
       }
-
-      if(o) {
-        if(this.lastactiverequest == r)
-          this.lastactiverequest = null;
-        this.lasttry = false;
-        if(o[0] == false) {
-          if(!this.disconnected) {
-            this.disconnected = true;
-
-            this.__error("An error occured: " + o[1]);
-          }
-          return;
-        }
-        o.each(function(x) {
-          this.fireEvent("recv", [x]);
-        }, this);
-      } else {
-        if(this.lastactiverequest == r) {
-          this.lastactiverequest = null;
+    
+      /* ok, we're the main request */
+      this.__activeRequest = null;
+      this.__cancelTimeout();
+      
+      if(!o) {
+        if(this.disconnected)
           return;
-        }
-        if(!this.disconnected) {
-          if(this.lasttry) {
-            this.disconnected = true;
-
-            this.__error("Error: the server closed the connection.");
-            return;
-          } else {
-            this.lasttry = true;
-          }
-        }
+          
+        if(this.__checkRetries())
+          this.recv();
+        return;
       }
       
-      this.recv();
-    }.bind(this), true);
+      if(this.__processData(o))
+        this.recv();
+    };
 
-    if(this.options.timeout)
-      this.timeoutid = this.__timeout.delay(this.options.timeout, this);
-    
-    this.activerequest = r;
+    r.addEvent("complete", onComplete.bind(this));
+
+    this.__scheduleTimeout();
     r.send("s=" + this.sessionid);
   },
   connect: function() {
     this.cacheAvoidance = qwebirc.util.randHexString(16);
     
-    var r = this.newRequest("n", function(o) {
+    var r = this.newRequest("n");
+    r.addEvent("complete", function(o) {
       if(!o) {
         this.disconnected = true;
         this.__error("Couldn't connect to remote server.");
         return;
       }
       if(o[0] == false) {
-        this.disconnected = true;
+        this.disconnect();
         this.__error("An error occured: " + o[1]);
         return;
       }
@@ -171,18 +214,21 @@ qwebirc.irc.IRCConnection = new Class({
       
       this.recv();    
     }.bind(this));
-    
     r.send("nick=" + encodeURIComponent(this.initialNickname));
   },
-  disconnect: function() {
-    this.disconnected = true;
-    if(this.lastactiverequest) {
-      this.lastactiverequest.cancel();
-      this.lastactiverequest = null;
+  __cancelRequests: function() {
+    if($defined(this.__lastActiveRequest)) {
+      this.__lastActiveRequest.cancel();
+      this.__lastActiveRequest = null;
     }
-    if($defined(this.timeoutid)) {
-      $clear(this.timeoutid);
-      this.timeoutid = null;
+    if($defined(this.__activeRequest)) {
+      this.__activeRequest.cancel();
+      this.__activeRequest = null;
     }
+  },
+  disconnect: function() {
+    this.disconnected = true;
+    this.__cancelTimeout();
+    this.__cancelRequests();
   }
 });
index 7390f2ebe017292d4cb08cd566874ee6c1a4f690..2b679600be8f2d4f5bcdd71da8fd6b574f3fb007 100644 (file)
@@ -1,6 +1,6 @@
 from twisted.web import resource, server, static
 from twisted.names import client
-from twisted.internet import reactor
+from twisted.internet import reactor, error
 from authgateengine import login_optional, getSessionData
 import simplejson, md5, sys, os, time, config, weakref, traceback
 import qwebirc.ircclient as ircclient
@@ -52,13 +52,31 @@ class IRCSession:
     self.closed = False
     self.cleanupschedule = None
 
-  def subscribe(self, channel):
+  def subscribe(self, channel, notifier):
+    timeout_entry = reactor.callLater(config.HTTP_AJAX_REQUEST_TIMEOUT, self.timeout, channel)
+    def cancel_timeout(result):
+      if channel in self.subscriptions:
+        self.subscriptions.remove(channel)
+      try:
+        timeout_entry.cancel()
+      except error.AlreadyCalled:
+        pass
+    notifier.addCallbacks(cancel_timeout, cancel_timeout)
+    
     if len(self.subscriptions) >= config.MAXSUBSCRIPTIONS:
       self.subscriptions.pop(0).close()
 
     self.subscriptions.append(channel)
     self.flush()
       
+  def timeout(self, channel):
+    if self.schedule:
+      return
+      
+    channel.write(simplejson.dumps([]))
+    if channel in self.subscriptions:
+      self.subscriptions.remove(channel)
+      
   def flush(self, scheduled=False):
     if scheduled:
       self.schedule = None
@@ -198,7 +216,8 @@ class AJAXEngine(resource.Resource):
     return session
     
   def subscribe(self, request):
-    self.getSession(request).subscribe(SingleUseChannel(request))
+    request.channel._savedTimeOut = None # HACK
+    self.getSession(request).subscribe(SingleUseChannel(request), request.notifyFinish())
     return NOT_DONE_YET
 
   def push(self, request):
diff --git a/run.py b/run.py
index acea65bde0ccaa7cebfd15230f87c4925b8bcf23..5cad2a1c2073b7b1a6a97d97509b9cc3c38ffbde 100755 (executable)
--- a/run.py
+++ b/run.py
@@ -2,7 +2,6 @@
 # this entire thing is a hack
 DEFAULT_PORT = 9090
 
-import qwebirc
 from twisted.scripts.twistd import run
 from optparse import OptionParser
 import sys, os
index eb62b928e4790fe053908f8273c5fb6e9dad20ef..0b2b5dedc8ebe075bb24c9b9d64793cb79cd33bd 100644 (file)
@@ -8,6 +8,7 @@ from twisted.application import internet, strports
 from twisted.web import static, server
 
 from qwebirc import RootSite
+import config as CONFIG
 
 class Options(usage.Options):
   optParameters = [["port", "p", "9090","Port to start the server on."],
@@ -44,7 +45,7 @@ class QWebIRCServiceMaker(object):
     else:
       site = RootSite(config['staticpath'])
     
-    site.timeOut = 60
+    site.timeOut = CONFIG.HTTP_REQUEST_TIMEOUT
     
     site.displayTracebacks = not config["notracebacks"]
     if config['https']: