Fossil SCM
Apply timeouts and retries to network read/write waits to avoid hangs. A clone or sync over HTTP/HTTPS could block when the peer stopped sending without closing the connection. The client sat in a blocking read on the socket with no timeout. The trick was to fix it without breaking the TLS handshake. http_socket.c: set SO_RCVTIMEO and SO_SNDTIMEO (30s) on the connection socket. The fd stays in blocking mode so the TLS handshake is unaffected. http_ssl.c: when reads time out with SO_RCVTIMEO, there is no data and BIO_should_retry() is true. Count consecutive retries without progress in ssl_send()/ssl_receive() and give up after a few.
Commit
8da6e3590a1ad6b194fe877474af1571404852afdd173ec4b7273b7bad6e04a7
Parent
922ec24c994c1a9…
2 files changed
+12
+8
+12
| --- src/http_socket.c | ||
| +++ src/http_socket.c | ||
| @@ -46,10 +46,11 @@ | ||
| 46 | 46 | # include <netdb.h> |
| 47 | 47 | #endif |
| 48 | 48 | #include <assert.h> |
| 49 | 49 | #include <sys/types.h> |
| 50 | 50 | #include <signal.h> |
| 51 | +#include <errno.h> | |
| 51 | 52 | |
| 52 | 53 | /* |
| 53 | 54 | ** There can only be a single socket connection open at a time. |
| 54 | 55 | ** State information about that socket is stored in the following |
| 55 | 56 | ** local variables: |
| @@ -196,10 +197,19 @@ | ||
| 196 | 197 | pUrlData->port); |
| 197 | 198 | rc = 1; |
| 198 | 199 | } |
| 199 | 200 | #if !defined(_WIN32) |
| 200 | 201 | signal(SIGPIPE, SIG_IGN); |
| 202 | + { | |
| 203 | + /* Bound how long any single read/write can block so a silent peer | |
| 204 | + ** cannot wedge the transfer forever. The fd stays blocking, so the | |
| 205 | + ** TLS handshake is unaffected. */ | |
| 206 | + struct timeval tv; | |
| 207 | + tv.tv_sec = 30; tv.tv_usec = 0; | |
| 208 | + setsockopt(iSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); | |
| 209 | + setsockopt(iSocket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); | |
| 210 | + } | |
| 201 | 211 | #endif |
| 202 | 212 | end_socket_open: |
| 203 | 213 | if( rc && iSocket>=0 ) socket_close(); |
| 204 | 214 | if( ai ) freeaddrinfo(ai); |
| 205 | 215 | return rc; |
| @@ -211,10 +221,11 @@ | ||
| 211 | 221 | size_t socket_send(void *NotUsed, const void *pContent, size_t N){ |
| 212 | 222 | ssize_t sent; |
| 213 | 223 | size_t total = 0; |
| 214 | 224 | while( N>0 ){ |
| 215 | 225 | sent = send(iSocket, pContent, N, 0); |
| 226 | + if( sent<0 && errno==EINTR ) continue; | |
| 216 | 227 | if( sent<=0 ) break; |
| 217 | 228 | total += (size_t)sent; |
| 218 | 229 | N -= (size_t)sent; |
| 219 | 230 | pContent = (void*)&((char*)pContent)[sent]; |
| 220 | 231 | } |
| @@ -236,12 +247,13 @@ | ||
| 236 | 247 | if( bDontBlock ) flags |= MSG_DONTWAIT; |
| 237 | 248 | #endif |
| 238 | 249 | while( N>0 ){ |
| 239 | 250 | /* WinXP fails for large values of N. So limit it to 64KiB. */ |
| 240 | 251 | got = recv(iSocket, pContent, N>65536 ? 65536 : N, flags); |
| 252 | + if( got<0 && errno==EINTR ) continue; | |
| 241 | 253 | if( got<=0 ) break; |
| 242 | 254 | total += (size_t)got; |
| 243 | 255 | N -= (size_t)got; |
| 244 | 256 | pContent = (void*)&((char*)pContent)[got]; |
| 245 | 257 | } |
| 246 | 258 | return total; |
| 247 | 259 | } |
| 248 | 260 |
| --- src/http_socket.c | |
| +++ src/http_socket.c | |
| @@ -46,10 +46,11 @@ | |
| 46 | # include <netdb.h> |
| 47 | #endif |
| 48 | #include <assert.h> |
| 49 | #include <sys/types.h> |
| 50 | #include <signal.h> |
| 51 | |
| 52 | /* |
| 53 | ** There can only be a single socket connection open at a time. |
| 54 | ** State information about that socket is stored in the following |
| 55 | ** local variables: |
| @@ -196,10 +197,19 @@ | |
| 196 | pUrlData->port); |
| 197 | rc = 1; |
| 198 | } |
| 199 | #if !defined(_WIN32) |
| 200 | signal(SIGPIPE, SIG_IGN); |
| 201 | #endif |
| 202 | end_socket_open: |
| 203 | if( rc && iSocket>=0 ) socket_close(); |
| 204 | if( ai ) freeaddrinfo(ai); |
| 205 | return rc; |
| @@ -211,10 +221,11 @@ | |
| 211 | size_t socket_send(void *NotUsed, const void *pContent, size_t N){ |
| 212 | ssize_t sent; |
| 213 | size_t total = 0; |
| 214 | while( N>0 ){ |
| 215 | sent = send(iSocket, pContent, N, 0); |
| 216 | if( sent<=0 ) break; |
| 217 | total += (size_t)sent; |
| 218 | N -= (size_t)sent; |
| 219 | pContent = (void*)&((char*)pContent)[sent]; |
| 220 | } |
| @@ -236,12 +247,13 @@ | |
| 236 | if( bDontBlock ) flags |= MSG_DONTWAIT; |
| 237 | #endif |
| 238 | while( N>0 ){ |
| 239 | /* WinXP fails for large values of N. So limit it to 64KiB. */ |
| 240 | got = recv(iSocket, pContent, N>65536 ? 65536 : N, flags); |
| 241 | if( got<=0 ) break; |
| 242 | total += (size_t)got; |
| 243 | N -= (size_t)got; |
| 244 | pContent = (void*)&((char*)pContent)[got]; |
| 245 | } |
| 246 | return total; |
| 247 | } |
| 248 |
| --- src/http_socket.c | |
| +++ src/http_socket.c | |
| @@ -46,10 +46,11 @@ | |
| 46 | # include <netdb.h> |
| 47 | #endif |
| 48 | #include <assert.h> |
| 49 | #include <sys/types.h> |
| 50 | #include <signal.h> |
| 51 | #include <errno.h> |
| 52 | |
| 53 | /* |
| 54 | ** There can only be a single socket connection open at a time. |
| 55 | ** State information about that socket is stored in the following |
| 56 | ** local variables: |
| @@ -196,10 +197,19 @@ | |
| 197 | pUrlData->port); |
| 198 | rc = 1; |
| 199 | } |
| 200 | #if !defined(_WIN32) |
| 201 | signal(SIGPIPE, SIG_IGN); |
| 202 | { |
| 203 | /* Bound how long any single read/write can block so a silent peer |
| 204 | ** cannot wedge the transfer forever. The fd stays blocking, so the |
| 205 | ** TLS handshake is unaffected. */ |
| 206 | struct timeval tv; |
| 207 | tv.tv_sec = 30; tv.tv_usec = 0; |
| 208 | setsockopt(iSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); |
| 209 | setsockopt(iSocket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); |
| 210 | } |
| 211 | #endif |
| 212 | end_socket_open: |
| 213 | if( rc && iSocket>=0 ) socket_close(); |
| 214 | if( ai ) freeaddrinfo(ai); |
| 215 | return rc; |
| @@ -211,10 +221,11 @@ | |
| 221 | size_t socket_send(void *NotUsed, const void *pContent, size_t N){ |
| 222 | ssize_t sent; |
| 223 | size_t total = 0; |
| 224 | while( N>0 ){ |
| 225 | sent = send(iSocket, pContent, N, 0); |
| 226 | if( sent<0 && errno==EINTR ) continue; |
| 227 | if( sent<=0 ) break; |
| 228 | total += (size_t)sent; |
| 229 | N -= (size_t)sent; |
| 230 | pContent = (void*)&((char*)pContent)[sent]; |
| 231 | } |
| @@ -236,12 +247,13 @@ | |
| 247 | if( bDontBlock ) flags |= MSG_DONTWAIT; |
| 248 | #endif |
| 249 | while( N>0 ){ |
| 250 | /* WinXP fails for large values of N. So limit it to 64KiB. */ |
| 251 | got = recv(iSocket, pContent, N>65536 ? 65536 : N, flags); |
| 252 | if( got<0 && errno==EINTR ) continue; |
| 253 | if( got<=0 ) break; |
| 254 | total += (size_t)got; |
| 255 | N -= (size_t)got; |
| 256 | pContent = (void*)&((char*)pContent)[got]; |
| 257 | } |
| 258 | return total; |
| 259 | } |
| 260 |
+8
| --- src/http_ssl.c | ||
| +++ src/http_ssl.c | ||
| @@ -648,18 +648,21 @@ | ||
| 648 | 648 | ** Send content out over the SSL connection from the client to |
| 649 | 649 | ** the server. |
| 650 | 650 | */ |
| 651 | 651 | size_t ssl_send(void *NotUsed, void *pContent, size_t N){ |
| 652 | 652 | size_t total = 0; |
| 653 | + int nStall = 0; | |
| 653 | 654 | while( N>0 ){ |
| 654 | 655 | int sent = BIO_write(iBio, pContent, N); |
| 655 | 656 | if( sent<=0 ){ |
| 656 | 657 | if( BIO_should_retry(iBio) ){ |
| 658 | + if( ++nStall > 4 ) break; | |
| 657 | 659 | continue; |
| 658 | 660 | } |
| 659 | 661 | break; |
| 660 | 662 | } |
| 663 | + nStall = 0; | |
| 661 | 664 | total += sent; |
| 662 | 665 | N -= sent; |
| 663 | 666 | pContent = (void*)&((char*)pContent)[sent]; |
| 664 | 667 | } |
| 665 | 668 | return total; |
| @@ -669,18 +672,23 @@ | ||
| 669 | 672 | ** Receive content back from the client SSL connection. In other |
| 670 | 673 | ** words read the reply back from the server. |
| 671 | 674 | */ |
| 672 | 675 | size_t ssl_receive(void *NotUsed, void *pContent, size_t N){ |
| 673 | 676 | size_t total = 0; |
| 677 | + int nStall = 0; | |
| 674 | 678 | while( N>0 ){ |
| 675 | 679 | int got = BIO_read(iBio, pContent, N); |
| 676 | 680 | if( got<=0 ){ |
| 677 | 681 | if( BIO_should_retry(iBio) ){ |
| 682 | + /* SO_RCVTIMEO made the underlying read time out with no data. | |
| 683 | + ** Allow a few consecutive stalls, then give up. */ | |
| 684 | + if( ++nStall > 4 ) break; | |
| 678 | 685 | continue; |
| 679 | 686 | } |
| 680 | 687 | break; |
| 681 | 688 | } |
| 689 | + nStall = 0; | |
| 682 | 690 | total += got; |
| 683 | 691 | N -= got; |
| 684 | 692 | pContent = (void*)&((char*)pContent)[got]; |
| 685 | 693 | } |
| 686 | 694 | return total; |
| 687 | 695 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -648,18 +648,21 @@ | |
| 648 | ** Send content out over the SSL connection from the client to |
| 649 | ** the server. |
| 650 | */ |
| 651 | size_t ssl_send(void *NotUsed, void *pContent, size_t N){ |
| 652 | size_t total = 0; |
| 653 | while( N>0 ){ |
| 654 | int sent = BIO_write(iBio, pContent, N); |
| 655 | if( sent<=0 ){ |
| 656 | if( BIO_should_retry(iBio) ){ |
| 657 | continue; |
| 658 | } |
| 659 | break; |
| 660 | } |
| 661 | total += sent; |
| 662 | N -= sent; |
| 663 | pContent = (void*)&((char*)pContent)[sent]; |
| 664 | } |
| 665 | return total; |
| @@ -669,18 +672,23 @@ | |
| 669 | ** Receive content back from the client SSL connection. In other |
| 670 | ** words read the reply back from the server. |
| 671 | */ |
| 672 | size_t ssl_receive(void *NotUsed, void *pContent, size_t N){ |
| 673 | size_t total = 0; |
| 674 | while( N>0 ){ |
| 675 | int got = BIO_read(iBio, pContent, N); |
| 676 | if( got<=0 ){ |
| 677 | if( BIO_should_retry(iBio) ){ |
| 678 | continue; |
| 679 | } |
| 680 | break; |
| 681 | } |
| 682 | total += got; |
| 683 | N -= got; |
| 684 | pContent = (void*)&((char*)pContent)[got]; |
| 685 | } |
| 686 | return total; |
| 687 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -648,18 +648,21 @@ | |
| 648 | ** Send content out over the SSL connection from the client to |
| 649 | ** the server. |
| 650 | */ |
| 651 | size_t ssl_send(void *NotUsed, void *pContent, size_t N){ |
| 652 | size_t total = 0; |
| 653 | int nStall = 0; |
| 654 | while( N>0 ){ |
| 655 | int sent = BIO_write(iBio, pContent, N); |
| 656 | if( sent<=0 ){ |
| 657 | if( BIO_should_retry(iBio) ){ |
| 658 | if( ++nStall > 4 ) break; |
| 659 | continue; |
| 660 | } |
| 661 | break; |
| 662 | } |
| 663 | nStall = 0; |
| 664 | total += sent; |
| 665 | N -= sent; |
| 666 | pContent = (void*)&((char*)pContent)[sent]; |
| 667 | } |
| 668 | return total; |
| @@ -669,18 +672,23 @@ | |
| 672 | ** Receive content back from the client SSL connection. In other |
| 673 | ** words read the reply back from the server. |
| 674 | */ |
| 675 | size_t ssl_receive(void *NotUsed, void *pContent, size_t N){ |
| 676 | size_t total = 0; |
| 677 | int nStall = 0; |
| 678 | while( N>0 ){ |
| 679 | int got = BIO_read(iBio, pContent, N); |
| 680 | if( got<=0 ){ |
| 681 | if( BIO_should_retry(iBio) ){ |
| 682 | /* SO_RCVTIMEO made the underlying read time out with no data. |
| 683 | ** Allow a few consecutive stalls, then give up. */ |
| 684 | if( ++nStall > 4 ) break; |
| 685 | continue; |
| 686 | } |
| 687 | break; |
| 688 | } |
| 689 | nStall = 0; |
| 690 | total += got; |
| 691 | N -= got; |
| 692 | pContent = (void*)&((char*)pContent)[got]; |
| 693 | } |
| 694 | return total; |
| 695 |