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.

danshearer 2026-06-14 22:05 UTC trunk
Commit 8da6e3590a1ad6b194fe877474af1571404852afdd173ec4b7273b7bad6e04a7
--- src/http_socket.c
+++ src/http_socket.c
@@ -46,10 +46,11 @@
4646
# include <netdb.h>
4747
#endif
4848
#include <assert.h>
4949
#include <sys/types.h>
5050
#include <signal.h>
51
+#include <errno.h>
5152
5253
/*
5354
** There can only be a single socket connection open at a time.
5455
** State information about that socket is stored in the following
5556
** local variables:
@@ -196,10 +197,19 @@
196197
pUrlData->port);
197198
rc = 1;
198199
}
199200
#if !defined(_WIN32)
200201
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
+ }
201211
#endif
202212
end_socket_open:
203213
if( rc && iSocket>=0 ) socket_close();
204214
if( ai ) freeaddrinfo(ai);
205215
return rc;
@@ -211,10 +221,11 @@
211221
size_t socket_send(void *NotUsed, const void *pContent, size_t N){
212222
ssize_t sent;
213223
size_t total = 0;
214224
while( N>0 ){
215225
sent = send(iSocket, pContent, N, 0);
226
+ if( sent<0 && errno==EINTR ) continue;
216227
if( sent<=0 ) break;
217228
total += (size_t)sent;
218229
N -= (size_t)sent;
219230
pContent = (void*)&((char*)pContent)[sent];
220231
}
@@ -236,12 +247,13 @@
236247
if( bDontBlock ) flags |= MSG_DONTWAIT;
237248
#endif
238249
while( N>0 ){
239250
/* WinXP fails for large values of N. So limit it to 64KiB. */
240251
got = recv(iSocket, pContent, N>65536 ? 65536 : N, flags);
252
+ if( got<0 && errno==EINTR ) continue;
241253
if( got<=0 ) break;
242254
total += (size_t)got;
243255
N -= (size_t)got;
244256
pContent = (void*)&((char*)pContent)[got];
245257
}
246258
return total;
247259
}
248260
--- 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
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -648,18 +648,21 @@
648648
** Send content out over the SSL connection from the client to
649649
** the server.
650650
*/
651651
size_t ssl_send(void *NotUsed, void *pContent, size_t N){
652652
size_t total = 0;
653
+ int nStall = 0;
653654
while( N>0 ){
654655
int sent = BIO_write(iBio, pContent, N);
655656
if( sent<=0 ){
656657
if( BIO_should_retry(iBio) ){
658
+ if( ++nStall > 4 ) break;
657659
continue;
658660
}
659661
break;
660662
}
663
+ nStall = 0;
661664
total += sent;
662665
N -= sent;
663666
pContent = (void*)&((char*)pContent)[sent];
664667
}
665668
return total;
@@ -669,18 +672,23 @@
669672
** Receive content back from the client SSL connection. In other
670673
** words read the reply back from the server.
671674
*/
672675
size_t ssl_receive(void *NotUsed, void *pContent, size_t N){
673676
size_t total = 0;
677
+ int nStall = 0;
674678
while( N>0 ){
675679
int got = BIO_read(iBio, pContent, N);
676680
if( got<=0 ){
677681
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;
678685
continue;
679686
}
680687
break;
681688
}
689
+ nStall = 0;
682690
total += got;
683691
N -= got;
684692
pContent = (void*)&((char*)pContent)[got];
685693
}
686694
return total;
687695
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button