[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;
}