fielding 97/04/07 02:44:20
Modified: src CHANGES Configure conf.h http_main.c Log: Improved lingering_close by adding a special timeout, removing the spurious log messages, removing the nonblocking settings (they are not needed with the better timeout), and adding commentary about the NO_LINGCLOSE and USE_SO_LINGER issues. NO_LINGCLOSE is now the default for SunOS4, Unixware, NeXT, and Irix. Send error messages about setsockopt failures to the server error log instead of stderr. Reviewed by: Jim Jagielski, Chuck Murcko, Randy Terbush Revision Changes Path 1.222 +9 -0 apache/src/CHANGES Index: CHANGES =================================================================== RCS file: /export/home/cvs/apache/src/CHANGES,v retrieving revision 1.221 retrieving revision 1.222 diff -C3 -r1.221 -r1.222 *** CHANGES 1997/04/06 07:43:38 1.221 --- CHANGES 1997/04/07 09:44:16 1.222 *************** *** 1,5 **** --- 1,14 ---- Changes with Apache 1.2b8 + *) Improved lingering_close by adding a special timeout, removing the + spurious log messages, removing the nonblocking settings (they + are not needed with the better timeout), and adding commentary + about the NO_LINGCLOSE and USE_SO_LINGER issues. NO_LINGCLOSE is + now the default for SunOS4, Unixware, NeXT, and Irix. [Roy Fielding] + + *) Send error messages about setsockopt failures to the server error + log instead of stderr. [Roy Fielding] + *) Fix loopholes in proxy cache expiry vis a vis alarms. [Brian Moore] *) Stopgap solution for CGI 3-second delay with server-side includes: if 1.88 +2 -2 apache/src/Configure Index: Configure =================================================================== RCS file: /export/home/cvs/apache/src/Configure,v retrieving revision 1.87 retrieving revision 1.88 diff -C3 -r1.87 -r1.88 *** Configure 1997/04/05 04:31:42 1.87 --- Configure 1997/04/07 09:44:17 1.88 *************** *** 347,359 **** *-unixware1) DEF_WANTHSREGEX=yes OS='Unixware' ! CFLAGS="$CFLAGS -DSVR4" LIBS="$LIBS -lsocket -lnsl -lcrypt" ;; *-unixware2) DEF_WANTHSREGEX=yes OS='Unixware' ! CFLAGS="$CFLAGS -DSVR4" LIBS="$LIBS -lsocket -lnsl -lcrypt" ;; *-unixware211) --- 347,359 ---- *-unixware1) DEF_WANTHSREGEX=yes OS='Unixware' ! CFLAGS="$CFLAGS -DSVR4 -DNO_LINGCLOSE" LIBS="$LIBS -lsocket -lnsl -lcrypt" ;; *-unixware2) DEF_WANTHSREGEX=yes OS='Unixware' ! CFLAGS="$CFLAGS -DSVR4 -DNO_LINGCLOSE" LIBS="$LIBS -lsocket -lnsl -lcrypt" ;; *-unixware211) 1.91 +3 -1 apache/src/conf.h Index: conf.h =================================================================== RCS file: /export/home/cvs/apache/src/conf.h,v retrieving revision 1.90 retrieving revision 1.91 diff -C3 -r1.90 -r1.91 *** conf.h 1997/04/02 13:25:16 1.90 --- conf.h 1997/04/07 09:44:17 1.91 *************** *** 71,77 **** #define NEED_STRCASECMP #define NEED_STRDUP #define NEED_STRNCASECMP - #define FNDELAY O_NDELAY extern void GETPRIVMODE(); extern void GETUSERMODE(); extern char *inet_ntoa(); --- 71,76 ---- *************** *** 113,118 **** --- 112,118 ---- #define HAVE_CRYPT_H #define NO_LONG_DOUBLE #define HAVE_BSTRING_H + #define NO_LINGCLOSE #elif defined(HIUX) #define HAVE_SYS_RESOURCE_H *************** *** 198,203 **** --- 198,204 ---- #undef NO_KILLPG #define NO_SETSID #define NEED_STRDUP + #define NO_LINGCLOSE #define NO_UNISTD_H #undef _POSIX_SOURCE #ifndef FD_CLOEXEC *************** *** 333,338 **** --- 334,340 ---- #define USE_FCNTL_SERIALIZED_ACCEPT #elif defined(UW) + #define NO_LINGCLOSE #define NO_KILLPG #undef NO_SETSID #undef NEED_STRDUP 1.135 +168 -133 apache/src/http_main.c Index: http_main.c =================================================================== RCS file: /export/home/cvs/apache/src/http_main.c,v retrieving revision 1.134 retrieving revision 1.135 diff -C3 -r1.134 -r1.135 *** http_main.c 1997/04/06 07:43:39 1.134 --- http_main.c 1997/04/07 09:44:18 1.135 *************** *** 301,401 **** #define accept_mutex_off() #endif - /* - * More machine-dependant networking gooo... on some systems, - * you've got to be *really* sure that all the packets are acknowledged - * before closing the connection. - */ - - #ifndef NO_LINGCLOSE - static void lingering_close (request_rec *r) - { - int dummybuf[512]; - struct timeval tv; - fd_set lfds, fds_read, fds_err; - int select_rv = 0, read_rv = 0; - int lsd; - - /* Prevent a slow-drip client from holding us here indefinitely */ - - hard_timeout("lingering_close", r); - - /* Send any leftover data to the client, but never try to again */ - - bflush(r->connection->client); - bsetflag(r->connection->client, B_EOUT, 1); - - /* Close our half of the connection --- send the client a FIN and - * set the socket to non-blocking for later reads. - */ - lsd = r->connection->client->fd; - - #ifdef MPE - if (((shutdown(lsd, 1)) != 0) || (sfcntl(lsd, F_SETFL, FNDELAY) == -1)) { - #else - if (((shutdown(lsd, 1)) != 0) || (fcntl(lsd, F_SETFL, FNDELAY) == -1)) { - #endif - /* if it fails, no need to go through the rest of the routine */ - if (errno != ENOTCONN) - log_unixerr("shutdown", NULL, "lingering_close", r->server); - bclose(r->connection->client); - kill_timeout(r); - return; - } - - /* Set up to wait for readable data on socket... */ - - FD_ZERO(&lfds); - FD_SET(lsd, &lfds); - - /* Wait for readable data or error condition on socket; - * slurp up any data that arrives... We exit when we go for - * an interval of tv length without getting any more data, get an - * error from select(), get an exception on lsd, get an error or EOF - * on a read, or the timer expires. - */ - - do { - /* We use a 1 second timeout because current (Feb 97) browsers - * fail to close a connection after the server closes it. Thus, - * to avoid keeping the child busy, we are only lingering long enough - * for a client that is actively sending data on a connection. - * This should be sufficient unless the connection is massively - * losing packets, in which case we might have missed the RST anyway. - * These parameters are reset on each pass, since they might be - * changed by select. - */ - tv.tv_sec = 1; - tv.tv_usec = 0; - read_rv = 0; - fds_read = lfds; - fds_err = lfds; - - #ifdef SELECT_NEEDS_CAST - select_rv = select(lsd+1, (int*)&fds_read, NULL, (int*)&fds_err, &tv); - #else - select_rv = select(lsd+1, &fds_read, NULL, &fds_err, &tv); - #endif - } while ((select_rv > 0) && /* Something to see on socket */ - !FD_ISSET(lsd, &fds_err) && /* that isn't an error condition */ - FD_ISSET(lsd, &fds_read) && /* and is worth trying to read */ - ((read_rv = read(lsd, dummybuf, sizeof dummybuf)) > 0)); - - /* Log any errors that occurred (client close or reset is not an error) */ - - if (select_rv < 0) - log_unixerr("select", NULL, "lingering_close", r->server); - else if (read_rv < 0 && errno != ECONNRESET) - log_unixerr("read", NULL, "lingering_close", r->server); - - /* Should now have seen final ack. Safe to finally kill socket */ - - bclose(r->connection->client); - - kill_timeout(r); - } - #endif /* ndef NO_LINGCLOSE */ - void usage(char *bin) { fprintf(stderr,"Usage: %s [-d directory] [-f file] [-v] [-h] [-l]\n",bin); --- 301,306 ---- *************** *** 558,563 **** --- 463,620 ---- } } + /* + * More machine-dependent networking gooo... on some systems, + * you've got to be *really* sure that all the packets are acknowledged + * before closing the connection, since the client will not be able + * to see the last response if their TCP buffer is flushed by a RST + * packet from us, which is what the server's TCP stack will send + * if it receives any request data after closing the connection. + * + * In an ideal world, this function would be accomplished by simply + * setting the socket option SO_LINGER and handling it within the + * server's TCP stack while the process continues on to the next request. + * Unfortunately, it seems that most (if not all) operating systems + * block the server process on close() when SO_LINGER is used. + * For those that don't, see USE_SO_LINGER below. For the rest, + * we have created a home-brew lingering_close. + * + * Many operating systems tend to block, puke, or otherwise mishandle + * calls to shutdown only half of the connection. You should define + * NO_LINGCLOSE in conf.h if such is the case for your system. + */ + #ifdef USE_SO_LINGER + #define NO_LINGCLOSE /* The two lingering options are exclusive */ + + static void sock_enable_linger (int s) + { + struct linger li; + + li.l_onoff = 1; + li.l_linger = 30; + + if (setsockopt(s, SOL_SOCKET, SO_LINGER, + (char *)&li, sizeof(struct linger)) < 0) { + log_unixerr("setsockopt", "(SO_LINGER)", NULL, server_conf); + /* not a fatal error */ + } + } + + #else + #define sock_enable_linger(s) /* NOOP */ + #endif /* USE_SO_LINGER */ + + #ifndef NO_LINGCLOSE + + /* Special version of timeout for lingering_close */ + + static void lingerout(sig) + int sig; + { + if (alarms_blocked) { + alarm_pending = 1; + return; + } + + if (!current_conn) { + #if defined(USE_LONGJMP) + longjmp(jmpbuffer,1); + #else + siglongjmp(jmpbuffer,1); + #endif + } + current_conn->aborted = 1; + } + + static void linger_timeout () + { + const int max_secs_to_linger = 30; + + timeout_name = "lingering close"; + + signal(SIGALRM,(void (*)())lingerout); + alarm(max_secs_to_linger); + } + + /* Since many clients will abort a connection instead of closing it, + * attempting to log an error message from this routine will only + * confuse the webmaster. There doesn't seem to be any portable way to + * distinguish between a dropped connection and something that might be + * worth logging. + */ + static void lingering_close (request_rec *r) + { + int dummybuf[512]; + struct timeval tv; + fd_set lfds, fds_read, fds_err; + int select_rv = 0, read_rv = 0; + int lsd; + + /* Prevent a slow-drip client from holding us here indefinitely */ + + linger_timeout(); + + /* Send any leftover data to the client, but never try to again */ + + bflush(r->connection->client); + bsetflag(r->connection->client, B_EOUT, 1); + + /* Close our half of the connection --- send the client a FIN */ + + lsd = r->connection->client->fd; + + if ((shutdown(lsd, 1) != 0) || r->connection->aborted) { + kill_timeout(r); + bclose(r->connection->client); + return; + } + + /* Set up to wait for readable data on socket... */ + + FD_ZERO(&lfds); + FD_SET(lsd, &lfds); + + /* Wait for readable data or error condition on socket; + * slurp up any data that arrives... We exit when we go for + * an interval of tv length without getting any more data, get an + * error from select(), get an exception on lsd, get an error or EOF + * on a read, or the timer expires. + */ + + do { + /* We use a 2 second timeout because current (Feb 97) browsers + * fail to close a connection after the server closes it. Thus, + * to avoid keeping the child busy, we are only lingering long enough + * for a client that is actively sending data on a connection. + * This should be sufficient unless the connection is massively + * losing packets, in which case we might have missed the RST anyway. + * These parameters are reset on each pass, since they might be + * changed by select. + */ + tv.tv_sec = 2; + tv.tv_usec = 0; + read_rv = 0; + fds_read = lfds; + fds_err = lfds; + + #ifdef SELECT_NEEDS_CAST + select_rv = select(lsd+1, (int*)&fds_read, NULL, (int*)&fds_err, &tv); + #else + select_rv = select(lsd+1, &fds_read, NULL, &fds_err, &tv); + #endif + } while ((select_rv > 0) && /* Something to see on socket */ + !FD_ISSET(lsd, &fds_err) && /* that isn't an error condition */ + FD_ISSET(lsd, &fds_read) && /* and is worth trying to read */ + ((read_rv = read(lsd, dummybuf, sizeof dummybuf)) > 0)); + + /* Should now have seen final ack. Safe to finally kill socket */ + + bclose(r->connection->client); + + kill_timeout(r); + } + #endif /* ndef NO_LINGCLOSE */ + /***************************************************************** * * Dealing with the scoreboard... a lot of these variables are global *************** *** 1566,1573 **** if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&just_say_no, sizeof(int)) < 0) { ! perror ("setsockopt(TCP_NODELAY)"); ! fprintf(stderr, "httpd: could not set socket option TCP_NODELAY\n"); } } #else --- 1623,1629 ---- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&just_say_no, sizeof(int)) < 0) { ! log_unixerr("setsockopt", "(TCP_NODELAY)", NULL, server_conf); } } #else *************** *** 1840,1902 **** return 0; } ! static int ! make_sock(pool *pconf, const struct sockaddr_in *server) { int s; int one = 1; if ((s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1) { ! perror("socket"); ! fprintf(stderr,"httpd: could not get socket\n"); exit(1); } ! note_cleanups_for_fd (pconf, s); /* arrange to close on exec or restart */ #ifndef MPE /* MPE does not support SO_REUSEADDR and SO_KEEPALIVE */ if (setsockopt(s, SOL_SOCKET,SO_REUSEADDR,(char *)&one,sizeof(int)) < 0) { ! perror("setsockopt(SO_REUSEADDR)"); ! fprintf(stderr,"httpd: could not set socket option SO_REUSEADDR\n"); exit(1); } one = 1; if (setsockopt(s, SOL_SOCKET,SO_KEEPALIVE,(char *)&one,sizeof(int)) < 0) { ! perror("setsockopt(SO_KEEPALIVE)"); ! fprintf(stderr,"httpd: could not set socket option SO_KEEPALIVE\n"); ! exit(1); } #endif sock_disable_nagle(s); - #ifdef USE_SO_LINGER /* If puts don't complete, you could try this. */ - { - /* Unfortunately, SO_LINGER causes problems as severe as it - * cures on many of the affected systems; now trying the - * lingering_close trick (see routine by that name above) - * instead... - */ - struct linger li; - li.l_onoff = 1; - li.l_linger = 900; - - if (setsockopt(s, SOL_SOCKET, SO_LINGER, - (char *)&li, sizeof(struct linger)) < 0) { - perror("setsockopt(SO_LINGER)"); - fprintf(stderr,"httpd: could not set socket option SO_LINGER\n"); - exit(1); - } - } - #endif /* USE_SO_LINGER */ - /* * To send data over high bandwidth-delay connections at full ! * speed we must the TCP window to open wide enough to keep the ! * pipe full. Default the default window size on many systems * is only 4kB. Cross-country WAN connections of 100ms ! * at 1Mb/s are not impossible for well connected sites in 1995. * If we assume 100ms cross-country latency, * a 4kB buffer limits throughput to 40kB/s. * --- 1896,1936 ---- return 0; } ! static int make_sock(pool *pconf, const struct sockaddr_in *server) { int s; int one = 1; if ((s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1) { ! log_unixerr("socket", NULL, "Failed to get a socket, exiting child", ! server_conf); exit(1); } ! note_cleanups_for_fd(pconf, s); /* arrange to close on exec or restart */ #ifndef MPE /* MPE does not support SO_REUSEADDR and SO_KEEPALIVE */ if (setsockopt(s, SOL_SOCKET,SO_REUSEADDR,(char *)&one,sizeof(int)) < 0) { ! log_unixerr("setsockopt", "(SO_REUSEADDR)", NULL, server_conf); exit(1); } one = 1; if (setsockopt(s, SOL_SOCKET,SO_KEEPALIVE,(char *)&one,sizeof(int)) < 0) { ! log_unixerr("setsockopt", "(SO_KEEPALIVE)", NULL, server_conf); ! exit(1); } #endif sock_disable_nagle(s); + sock_enable_linger(s); /* * To send data over high bandwidth-delay connections at full ! * speed we must force the TCP window to open wide enough to keep the ! * pipe full. The default window size on many systems * is only 4kB. Cross-country WAN connections of 100ms ! * at 1Mb/s are not impossible for well connected sites. * If we assume 100ms cross-country latency, * a 4kB buffer limits throughput to 40kB/s. * *************** *** 1908,1921 **** * * -John Heidemann <[EMAIL PROTECTED]> 25-Oct-96 * - * * If no size is specified, use the kernel default. */ if (server_conf->send_buffer_size) { if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&server_conf->send_buffer_size, sizeof(int)) < 0) { ! perror("setsockopt(SO_SNDBUF), using default buffer size"); ! /* Fail soft. */ } } --- 1942,1956 ---- * * -John Heidemann <[EMAIL PROTECTED]> 25-Oct-96 * * If no size is specified, use the kernel default. */ if (server_conf->send_buffer_size) { if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&server_conf->send_buffer_size, sizeof(int)) < 0) { ! log_unixerr("setsockopt", "(SO_SNDBUF)", ! "Failed to set SendBufferSize, using default", ! server_conf); ! /* not a fatal error */ } }