[PATCH] http client/server timeout

Alexander Malysh <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Hello together,

please find attached patch that implements http server & client idle
timeout. In order to efficient handle timeouts and don't start "yet another
thread" we use already available fdset's threads. With this patch applied
we will have a possibility to set timeout for all filedescriptors in fdset
and after timeout elapsed registered callback function with POLLERR as
event will be called. I'm surprised with sooo little code change reach full
working server/client timeout handling :)

Maybe we want implement (yet missing) things too:
- dont wait for set->timeout instead find the min timeout (this part is not
implemented yet because will cause parformance bust due to a need to rescan
the whole fdset after all callbacks were executed and anyway for boxes with
high traffic earlier timeout check will catch).
- timeout for each fd (IMHO: it's oversized for our needs)


Comments & votes please!


-- 
Thanks,
Alex
htpp-server-client-timeout.patch (text/x-diff, 13.2 KB)
Index: gwlib/fdset.c
===================================================================
RCS file: /home/cvs/gateway/gwlib/fdset.c,v
retrieving revision 1.7
diff -a -u -p -r1.7 fdset.c
--- gwlib/fdset.c	22 Jan 2004 14:08:25 -0000	1.7
+++ gwlib/fdset.c	26 Jan 2005 19:21:11 -0000
@@ -58,12 +58,15 @@
  * fdset.c - module for managing a large collection of file descriptors
  */
 
+#include "gw-config.h"
+ 
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 
 #include "gwlib/gwlib.h"
 
+
 struct FDSet
 {
     /* Thread ID of the set's internal thread, which will spend most
@@ -80,6 +83,11 @@ struct FDSet
     struct pollfd *pollinfo;
     int size;
     int entries;
+    
+    /* Array of times when appropriate fd got any event or events bitmask changed */
+    time_t *times;
+    /* timeout for this fdset */
+    long timeout;
 
     /* Arrays of callback and data fields.  They are kept in sync with
      * the pollinfo array, and are basically extra fields that we couldn't
@@ -276,6 +284,7 @@ static void remove_entry(FDSet *set, int
         set->pollinfo[entry] = set->pollinfo[set->entries - 1];
         set->callbacks[entry] = set->callbacks[set->entries - 1];
         set->datafields[entry] = set->datafields[set->entries - 1];
+        set->times[entry] = set->times[set->entries - 1];
     }
     set->entries--;
 }
@@ -307,6 +316,7 @@ static void poller(void *arg)
     struct action *action;
     int ret;
     int i;
+    time_t now;
 
     gw_assert(set != NULL);
 
@@ -317,26 +327,32 @@ static void poller(void *arg)
                 return;
         }
 
-        /* Block indefinitely, waiting for activity */
-        ret = gwthread_poll(set->pollinfo, set->entries, -1.0);
+        /* Block for defined timeout, waiting for activity */
+        ret = gwthread_poll(set->pollinfo, set->entries, set->timeout);
 
         if (ret < 0) {
 	    if (errno != EINTR) {
-                error(0, "Poller: can't handle error; sleeping 1 second.");
+                error(errno, "Poller: can't handle error; sleeping 1 second.");
                 gwthread_sleep(1.0);
             }
             continue;
         }
-
-	/* Callbacks may modify the table while we scan it, so be careful. */
-	set->scanning = 1;
+        time(&now);
+        /* Callbacks may modify the table while we scan it, so be careful. */
+        set->scanning = 1;
         for (i = 0; i < set->entries; i++) {
-            if (set->pollinfo[i].revents != 0)
+            if (set->pollinfo[i].revents != 0) {
                 set->callbacks[i](set->pollinfo[i].fd,
-                                  set->pollinfo[i].revents,
-                                  set->datafields[i]);
+                                set->pollinfo[i].revents,
+                                set->datafields[i]);
+                /* update event time */
+                time(&set->times[i]);
+            } else if (set->timeout > 0 && difftime(set->times[i] + set->timeout, now) <= 0) {
+                debug("gwlib.fdset", 0, "Timeout for fd:%d appeares.", set->pollinfo[i].fd);
+                set->callbacks[i](set->pollinfo[i].fd, POLLERR, set->datafields[i]);
+            }
         }
-	set->scanning = 0;
+        set->scanning = 0;
 
 	if (set->deleted_entries > 0)
 	    remove_deleted_entries(set);
@@ -345,7 +361,7 @@ static void poller(void *arg)
 
 
 
-FDSet *fdset_create(void)
+FDSet *fdset_create_real(long timeout)
 {
     FDSet *new;
 
@@ -358,6 +374,8 @@ FDSet *fdset_create(void)
     new->pollinfo = gw_malloc(sizeof(new->pollinfo[0]) * new->size);
     new->callbacks = gw_malloc(sizeof(new->callbacks[0]) * new->size);
     new->datafields = gw_malloc(sizeof(new->datafields[0]) * new->size);
+    new->times = gw_malloc(sizeof(new->times[0]) * new->size);
+    new->timeout = timeout > 0 ? timeout : -1;
     new->scanning = 0;
     new->deleted_entries = 0;
 
@@ -386,6 +404,7 @@ void fdset_destroy(FDSet *set)
         gw_free(set->pollinfo);
         gw_free(set->callbacks);
         gw_free(set->datafields);
+        gw_free(set->times);
         if (list_len(set->actions) > 0) {
             error(0, "Destroying fdset with %ld pending actions.",
                   list_len(set->actions));
@@ -428,6 +447,7 @@ void fdset_register(FDSet *set, int fd, 
                                    sizeof(set->callbacks[0]) * newsize);
         set->datafields = gw_realloc(set->datafields,
                                    sizeof(set->datafields[0]) * newsize);
+        set->times = gw_realloc(set->times, sizeof(set->times[0]) * newsize);
         set->size = newsize;
     }
 
@@ -440,6 +460,7 @@ void fdset_register(FDSet *set, int fd, 
     set->pollinfo[new].revents = 0;
     set->callbacks[new] = callback;
     set->datafields[new] = data;
+    time(&set->times[new]);
 }
 
 void fdset_listen(FDSet *set, int fd, int mask, int events)
@@ -478,6 +499,8 @@ void fdset_listen(FDSet *set, int fd, in
         set->pollinfo[entry].revents =
             set->pollinfo[entry].revents & (events | ~mask);
     }
+    
+    time(&set->times[entry]);
 }
 
 void fdset_unregister(FDSet *set, int fd)
Index: gwlib/fdset.h
===================================================================
RCS file: /home/cvs/gateway/gwlib/fdset.h,v
retrieving revision 1.3
diff -a -u -p -r1.3 fdset.h
--- gwlib/fdset.h	22 Jan 2004 14:08:25 -0000	1.3
+++ gwlib/fdset.h	26 Jan 2005 19:21:11 -0000
@@ -74,8 +74,11 @@ typedef void fdset_callback_t(int fd, in
 
 /*
  * Create a new, empty file descriptor set and start its thread.
+ * @timeout - idle timeout for any filedescriptor in this fdset after which
+ *            callback function will be called with POLLERR as event.
  */
-FDSet *fdset_create(void);
+#define fdset_create() fdset_create_real(-1);
+FDSet *fdset_create_real(long timeout);
 
 /*
  * Destroy a file descriptor set.  Will emit a warning if any file
Index: gwlib/http.c
===================================================================
RCS file: /home/cvs/gateway/gwlib/http.c,v
retrieving revision 1.222
diff -a -u -p -r1.222 http.c
--- gwlib/http.c	23 Jan 2005 14:48:52 -0000	1.222
+++ gwlib/http.c	26 Jan 2005 19:21:13 -0000
@@ -89,6 +89,12 @@
 /* comment this out if you don't want HTTP responses to be dumped */
 #define DUMP_RESPONSE 1
 
+/* define http client connections timeout in seconds (set to -1 for disable) */
+#define HTTP_CLIENT_TIMEOUT 240
+
+/* define http server connections timeout in seconds (set to -1 for disable) */
+#define HTTP_SERVER_TIMEOUT 60
+
 /***********************************************************************
  * Stuff used in several sub-modules.
  */
@@ -134,7 +140,7 @@ static int read_some_headers(Connection 
     for (;;) {
 	line = conn_read_line(conn);
 	if (line == NULL) {
-	    if (conn_eof(conn))
+            if (conn_eof(conn) || conn_error(conn))
 	    	return -1;
 	    return 1;
 	}
@@ -972,54 +978,30 @@ static void handle_transaction(Connectio
     int ret;
     Octstr *h;
     int rc;
-    char buf[128];
     
     trans = data;
 
     if (run_status != running) {
-	conn_unregister(conn);
-	return;
+        conn_unregister(conn);
+        return;
     }
 
     while (trans->state != transaction_done) {
-	switch (trans->state) {
-	case connecting:
-	  debug("gwlib.http", 0, "Get info about connecting socket");
-          if (conn_get_connect_result(trans->conn) != 0) {
-            debug("gwlib.http", 0, "Socket not connected");
-            conn_unregister(conn);
-            goto error;
-          }
+        switch (trans->state) {
+        case connecting:
+            debug("gwlib.http", 0, "Get info about connecting socket");
+            if (conn_get_connect_result(trans->conn) != 0) {
+                debug("gwlib.http", 0, "Socket not connected");
+                goto error;
+            }
 
-          if (trans->method == HTTP_METHOD_POST) {
-            /* 
-             * Add a Content-Length header.  Override an existing one, if
-             * necessary.  We must have an accurate one in order to use the
-             * connection for more than a single request.
-             */
-            http_header_remove_all(trans->request_headers, "Content-Length");
-            sprintf(buf, "%ld", octstr_len(trans->request_body));
-            http_header_add(trans->request_headers, "Content-Length", buf);
-          } 
-          /* 
-           * ok, this has to be an GET or HEAD request method then,
-           * if it contains a body, then this is not HTTP conform, so at
-           * least warn the user 
-           */
-          else if (trans->request_body != NULL) {
-            warning(0, "HTTP: GET or HEAD method request contains body:");
-            octstr_dump(trans->request_body, 0);
-          }
-
-          if ((rc = send_request(trans)) == 0) {
-            trans->state = reading_status;
-            conn_register(trans->conn, client_fdset, handle_transaction, 
-                          trans);
-          } else {
-            debug("gwlib.http",0,"Failed while sending request");
-            goto error;
-          }
-          break;
+            if ((rc = send_request(trans)) == 0) {
+                trans->state = reading_status;
+            } else {
+                debug("gwlib.http",0,"Failed while sending request");
+                goto error;
+            }
+            break;
 
 	case reading_status:
 	    ret = client_read_status(trans);
@@ -1502,19 +1484,37 @@ error:
 
 
 /*
- * Build and send the HTTP request. Return socket from which the
- * response can be read or -1 for error.
+ * Build and send the HTTP request. Return 0 for success or -1 for error.
  */
 static int send_request(HTTPServer *trans)
 {
-    Octstr *request;
+    char buf[128];    
+    Octstr *request = NULL;
 
-    request = NULL;
+    if (trans->method == HTTP_METHOD_POST) {
+        /* 
+         * Add a Content-Length header.  Override an existing one, if
+         * necessary.  We must have an accurate one in order to use the
+         * connection for more than a single request.
+         */
+        http_header_remove_all(trans->request_headers, "Content-Length");
+        sprintf(buf, "%ld", octstr_len(trans->request_body));
+        http_header_add(trans->request_headers, "Content-Length", buf);
+    } 
+    /* 
+     * ok, this has to be an GET or HEAD request method then,
+     * if it contains a body, then this is not HTTP conform, so at
+     * least warn the user 
+     */
+    else if (trans->request_body != NULL) {
+        warning(0, "HTTP: GET or HEAD method request contains body:");
+        octstr_dump(trans->request_body, 0);
+    }
 
     /* 
-    * we have to assume all values in trans are already set
-    * by parse_url() before calling this.
-    */
+     * we have to assume all values in trans are already set
+     * by parse_url() before calling this.
+     */
 
     if (trans->username != NULL)
         http_add_basic_auth(trans->request_headers, trans->username,
@@ -1559,7 +1559,6 @@ error:
 static void write_request_thread(void *arg)
 {
     HTTPServer *trans;
-    char buf[128];    
     int rc;
 
     while (run_status == running) {
@@ -1580,25 +1579,6 @@ static void write_request_thread(void *a
         else if (conn_is_connected(trans->conn) == 0) {
             debug("gwlib.http", 0, "Socket connected at once");
 
-            if (trans->method == HTTP_METHOD_POST) {
-                /* 
-                * Add a Content-Length header.  Override an existing one, if
-                * necessary.  We must have an accurate one in order to use the
-                * connection for more than a single request.
-                */
-                http_header_remove_all(trans->request_headers, "Content-Length");
-                sprintf(buf, "%ld", octstr_len(trans->request_body));
-                http_header_add(trans->request_headers, "Content-Length", buf);
-            } 
-            /* 
-             * ok, this has to be an GET or HEAD request method then,
-             * if it contains a body, then this is not HTTP conform, so at
-             * least warn the user 
-             */
-            else if (trans->request_body != NULL) {
-                warning(0, "HTTP: GET or HEAD method request contains body:");
-                octstr_dump(trans->request_body, 0);
-            }
             if ((rc = send_request(trans)) == 0) {
                 trans->state = reading_status;
                 conn_register(trans->conn, client_fdset, handle_transaction, 
@@ -1627,7 +1607,7 @@ static void start_client_threads(void)
 	 */
 	mutex_lock(client_thread_lock);
 	if (!client_threads_are_running) {
-	    client_fdset = fdset_create();
+	    client_fdset = fdset_create_real(HTTP_CLIENT_TIMEOUT);
 	    gwthread_create(write_request_thread, NULL);
 	    client_threads_are_running = 1;
 	}
@@ -2107,7 +2087,7 @@ static void receive_request(Connection *
 	    return;
 
 	case sending_reply:
-            /* Implicite conn_unregister() and _destroy */
+            /* Implicit conn_unregister() and _destroy */
             if (conn_error(conn))
                 goto error;
 	    if (conn_outbuf_len(conn) > 0)
@@ -2247,7 +2227,7 @@ static void start_server_thread(void)
 	 */
 	mutex_lock(server_thread_lock);
 	if (!server_thread_is_running) {
-	    server_fdset = fdset_create();
+	    server_fdset = fdset_create_real(HTTP_SERVER_TIMEOUT);
 	    server_thread_id = gwthread_create(server_thread, NULL);
 	    server_thread_is_running = 1;
 	}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.