listen socket more frequent service 03/2903/1
authorAndy Green <andy.green@linaro.org>
Tue, 15 Jan 2013 23:59:47 +0000 (07:59 +0800)
committerKevron Rees <kevron_m_rees@linux.intel.com>
Thu, 7 Mar 2013 21:01:25 +0000 (13:01 -0800)
From an idea by Edwin van den Oetelaar <oetelaar.automatisering@gmail.com>

When testing libwebsockets with ab, Edwin found an unexpected bump in
the distribution of latencies, some connections were held back almost
the whole test duration.

http://ml.libwebsockets.org/pipermail/libwebsockets/2013-January/000006.html

Studying the problem revealed that when there are mass pending connections
amongst many active connections, we do not service the listen socket often
enough to clear the backlog, some seem to get stale violating FIFO ordering.

This patch introduces listen socket service "piggybacking", where every n
normal socket service actions we also check the listen socket and deal with
pending connections there.

Normally, it checks the listen socket gratuitously every 10 normal socket
services.  However, if it finds something waiting, it forces a check on the
next normal socket service too by keeping stats on how often something was
waiting.  If the probability of something waiting each time becomes high,
it will allow up to two waiting connections to be serviced for each normal
socket service.

In that way it has low burden in the normal case, but rapidly adapts by
detecting mass connection loads as found in ab.

Signed-off-by: Andy Green <andy.green@linaro.org>
lib/libwebsockets.c
lib/private-libwebsockets.h

index 8bc91de..bbbbe49 100644 (file)
@@ -1534,6 +1534,41 @@ libwebsocket_service_fd(struct libwebsocket_context *context,
 
        /* no, here to service a socket descriptor */
 
+       /*
+        * deal with listen service piggybacking
+        * every listen_service_modulo services of other fds, we
+        * sneak one in to service the listen socket if there's anything waiting
+        *
+        * To handle connection storms, as found in ab, if we previously saw a
+        * pending connection here, it causes us to check again next time.
+        */
+
+       if (context->listen_service_fd && pollfd->fd != context->listen_service_fd) {
+               context->listen_service_count++;
+               if (context->listen_service_extraseen ||
+                               context->listen_service_count == context->listen_service_modulo) {
+                       context->listen_service_count = 0;
+                       m = 1;
+                       if (context->listen_service_extraseen > 5)
+                               m = 2;
+                       while (m--) {
+                               /* even with extpoll, we prepared this internal fds for listen */
+                               n = poll(&context->fds[0], 1, 0);
+                               if (n > 0) { /* there's a connection waiting for us */
+                                       libwebsocket_service_fd(context, &context->fds[0]);
+                                       context->listen_service_extraseen++;
+                               } else {
+                                       if (context->listen_service_extraseen)
+                                               context->listen_service_extraseen--;
+                                       break;
+                               }
+                       }
+               }
+
+       }
+
+       /* okay, what we came here to do... */
+
        wsi = wsi_from_fd(context, pollfd->fd);
 
        if (wsi == NULL) {
@@ -3040,10 +3075,14 @@ libwebsocket_create_context(int port, const char *interf,
                wsi->mode = LWS_CONNMODE_SERVER_LISTENER;
                insert_wsi(context, wsi);
 
+               context->listen_service_modulo = LWS_LISTEN_SERVICE_MODULO;
+               context->listen_service_count = 0;
+               context->listen_service_fd = sockfd;
+
                listen(sockfd, LWS_SOMAXCONN);
                lwsl_info(" Listening on port %d\n", port);
 
-               /* list in the internal poll array */
+               /* list in the internal poll array - we're always first */
 
                context->fds[context->fds_count].fd = sockfd;
                context->fds[context->fds_count++].events = POLLIN;
index 27b2049..3d16a32 100644 (file)
@@ -173,6 +173,11 @@ extern void _lws_log(int filter, const char *format, ...);
 #define LWS_MAX_ZLIB_CONN_BUFFER (64 * 1024)
 #endif
 
+/*
+ * if not in a connection storm, check for incoming
+ * connections this many normal connection services
+ */
+#define LWS_LISTEN_SERVICE_MODULO 10
 
 enum lws_websocket_opcodes_04 {
        LWS_WS_OPCODE_04__CONTINUATION = 0,
@@ -294,6 +299,10 @@ struct libwebsocket_context {
        unsigned long last_timeout_check_s;
 
        int fd_random;
+       int listen_service_modulo;
+       int listen_service_count;
+       int listen_service_fd;
+       int listen_service_extraseen;
 
 #ifdef LWS_OPENSSL_SUPPORT
        int use_ssl;