Fossil SCM

Replace read timeout with non-blocking poll, and add TCP_NODELAY clone and sync often hang or appear to hang on links with highly variable latency, sometimes doing so many retries the transfer rate is infeasibly slow and at others spinning on reads that never complete. Both git and mercurial cope well with the same network doing very similar transfers, seemingly due to their protocol design and implementatin. The fixes here improve the fossil implementation somewhat. Everything here was tested with the fossil HTTP 1.1 patch. * Set TCP_NODELAY on the client socket The sync protocol interleaves small control writes with bulk transfer, and with the Nagle algorithm active those small writes are held waiting for ACKs; combined with the peer's delayed ACKs this throttles throughput badly on higher-latency paths. Disabling Nagle can give a 10x throughput improvement. This is what curl and many other HTTP clients do (Git uses curl.) * No blocking forever on TLS reads and writes After the TLS handshake the socket is switched to non-blocking mode. ssl_send() and ssl_receive() now wait for readiness with poll() and a timeout, and abandon a connection that has stopped communicating or been reset. The blocking socket remains during the handshake so behaviour is unchanged. * Retry on EINTR If a signal interrupts recv()/send() we now return -1/EINTR. This seems to be an improvement on treating the signal as end-of-stream and silently truncating the transfer. * Report truncated replies as errors When a read makes no progress due to a stalled or half-closed connection we now return NULL to indicate end of input. This means we no longer try to parse partial responses.

danshearer 2026-06-15 07:44 UTC client-timeout-fixes
Commit e4180437441b1f1c074bc11e587011441c358ed664b247202b933990bb130318
--- src/http_socket.c
+++ src/http_socket.c
@@ -39,10 +39,11 @@
3939
#if defined(_WIN32)
4040
# include <winsock2.h>
4141
# include <ws2tcpip.h>
4242
#else
4343
# include <netinet/in.h>
44
+# include <netinet/tcp.h>
4445
# include <arpa/inet.h>
4546
# include <sys/socket.h>
4647
# include <netdb.h>
4748
#endif
4849
#include <assert.h>
@@ -181,10 +182,18 @@
181182
if( iSocket<0 ) continue;
182183
if( connect(iSocket,p->ai_addr,p->ai_addrlen)<0 ){
183184
socket_close();
184185
continue;
185186
}
187
+ {
188
+ /* Disable Nagle's algorithm. Without this, the small writes that the
189
+ ** sync protocol interleaves with bulk transfer are held by Nagle waiting
190
+ ** for ACKs which, combined with the peer's delayed ACKs, can throttle a
191
+ ** clone or sync to a small fraction of the available bandwidth. */
192
+ int on = 1;
193
+ setsockopt(iSocket, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on));
194
+ }
186195
rc = getnameinfo(p->ai_addr, p->ai_addrlen, zRemote, sizeof(zRemote),
187196
0, 0, NI_NUMERICHOST);
188197
if( rc ){
189198
socket_set_errmsg("getnameinfo() failed: %s", gai_strerror(rc));
190199
goto end_socket_open;
@@ -197,19 +206,10 @@
197206
pUrlData->port);
198207
rc = 1;
199208
}
200209
#if !defined(_WIN32)
201210
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
- }
211211
#endif
212212
end_socket_open:
213213
if( rc && iSocket>=0 ) socket_close();
214214
if( ai ) freeaddrinfo(ai);
215215
return rc;
216216
--- src/http_socket.c
+++ src/http_socket.c
@@ -39,10 +39,11 @@
39 #if defined(_WIN32)
40 # include <winsock2.h>
41 # include <ws2tcpip.h>
42 #else
43 # include <netinet/in.h>
 
44 # include <arpa/inet.h>
45 # include <sys/socket.h>
46 # include <netdb.h>
47 #endif
48 #include <assert.h>
@@ -181,10 +182,18 @@
181 if( iSocket<0 ) continue;
182 if( connect(iSocket,p->ai_addr,p->ai_addrlen)<0 ){
183 socket_close();
184 continue;
185 }
 
 
 
 
 
 
 
 
186 rc = getnameinfo(p->ai_addr, p->ai_addrlen, zRemote, sizeof(zRemote),
187 0, 0, NI_NUMERICHOST);
188 if( rc ){
189 socket_set_errmsg("getnameinfo() failed: %s", gai_strerror(rc));
190 goto end_socket_open;
@@ -197,19 +206,10 @@
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;
216
--- src/http_socket.c
+++ src/http_socket.c
@@ -39,10 +39,11 @@
39 #if defined(_WIN32)
40 # include <winsock2.h>
41 # include <ws2tcpip.h>
42 #else
43 # include <netinet/in.h>
44 # include <netinet/tcp.h>
45 # include <arpa/inet.h>
46 # include <sys/socket.h>
47 # include <netdb.h>
48 #endif
49 #include <assert.h>
@@ -181,10 +182,18 @@
182 if( iSocket<0 ) continue;
183 if( connect(iSocket,p->ai_addr,p->ai_addrlen)<0 ){
184 socket_close();
185 continue;
186 }
187 {
188 /* Disable Nagle's algorithm. Without this, the small writes that the
189 ** sync protocol interleaves with bulk transfer are held by Nagle waiting
190 ** for ACKs which, combined with the peer's delayed ACKs, can throttle a
191 ** clone or sync to a small fraction of the available bandwidth. */
192 int on = 1;
193 setsockopt(iSocket, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on));
194 }
195 rc = getnameinfo(p->ai_addr, p->ai_addrlen, zRemote, sizeof(zRemote),
196 0, 0, NI_NUMERICHOST);
197 if( rc ){
198 socket_set_errmsg("getnameinfo() failed: %s", gai_strerror(rc));
199 goto end_socket_open;
@@ -197,19 +206,10 @@
206 pUrlData->port);
207 rc = 1;
208 }
209 #if !defined(_WIN32)
210 signal(SIGPIPE, SIG_IGN);
 
 
 
 
 
 
 
 
 
211 #endif
212 end_socket_open:
213 if( rc && iSocket>=0 ) socket_close();
214 if( ai ) freeaddrinfo(ai);
215 return rc;
216
+21 -8
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -35,10 +35,12 @@
3535
3636
#include <openssl/bio.h>
3737
#include <openssl/ssl.h>
3838
#include <openssl/err.h>
3939
#include <openssl/x509.h>
40
+#include <poll.h>
41
+#include <fcntl.h>
4042
4143
#include <assert.h>
4244
#include <sys/types.h>
4345
4446
/*
@@ -505,10 +507,19 @@
505507
pUrlData->useProxy ? pUrlData->proxyOrigPort : pUrlData->port,
506508
ERR_reason_error_string(ERR_get_error()));
507509
ssl_close_client();
508510
return 1;
509511
}
512
+ {
513
+ /* The TLS handshake completes on a blocking socket. Switch to non-blocking
514
+ ** afterwards so that a stalled read returns instead of sleeping in the
515
+ ** kernel indefinitely; ssl_send()/ssl_receive() use poll() with a timeout
516
+ ** to wait for the socket and abandon a connection that has gone silent. */
517
+ int fd = socket_get_fd();
518
+ int fl = fcntl(fd, F_GETFL, 0);
519
+ if( fl!=-1 ) fcntl(fd, F_SETFL, fl | O_NONBLOCK);
520
+ }
510521
/* Check if certificate is valid */
511522
cert = SSL_get_peer_certificate(ssl);
512523
513524
if ( cert==NULL ){
514525
ssl_set_errmsg("No SSL certificate was presented by the peer");
@@ -648,21 +659,23 @@
648659
** Send content out over the SSL connection from the client to
649660
** the server.
650661
*/
651662
size_t ssl_send(void *NotUsed, void *pContent, size_t N){
652663
size_t total = 0;
653
- int nStall = 0;
654664
while( N>0 ){
655665
int sent = BIO_write(iBio, pContent, N);
656666
if( sent<=0 ){
657667
if( BIO_should_retry(iBio) ){
658
- if( ++nStall > 4 ) break;
668
+ struct pollfd pfd;
669
+ pfd.fd = socket_get_fd();
670
+ pfd.events = BIO_should_read(iBio) ? POLLIN : POLLOUT;
671
+ if( poll(&pfd, 1, 120000)<=0 ) break;
672
+ if( pfd.revents & (POLLHUP|POLLERR|POLLNVAL) ) break;
659673
continue;
660674
}
661675
break;
662676
}
663
- nStall = 0;
664677
total += sent;
665678
N -= sent;
666679
pContent = (void*)&((char*)pContent)[sent];
667680
}
668681
return total;
@@ -672,23 +685,23 @@
672685
** Receive content back from the client SSL connection. In other
673686
** words read the reply back from the server.
674687
*/
675688
size_t ssl_receive(void *NotUsed, void *pContent, size_t N){
676689
size_t total = 0;
677
- int nStall = 0;
678690
while( N>0 ){
679691
int got = BIO_read(iBio, pContent, N);
680692
if( got<=0 ){
681693
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;
694
+ struct pollfd pfd;
695
+ pfd.fd = socket_get_fd();
696
+ pfd.events = BIO_should_write(iBio) ? POLLOUT : POLLIN;
697
+ if( poll(&pfd, 1, 120000)<=0 ) break;
698
+ if( pfd.revents & (POLLHUP|POLLERR|POLLNVAL) ) break;
685699
continue;
686700
}
687701
break;
688702
}
689
- nStall = 0;
690703
total += got;
691704
N -= got;
692705
pContent = (void*)&((char*)pContent)[got];
693706
}
694707
return total;
695708
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -35,10 +35,12 @@
35
36 #include <openssl/bio.h>
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/x509.h>
 
 
40
41 #include <assert.h>
42 #include <sys/types.h>
43
44 /*
@@ -505,10 +507,19 @@
505 pUrlData->useProxy ? pUrlData->proxyOrigPort : pUrlData->port,
506 ERR_reason_error_string(ERR_get_error()));
507 ssl_close_client();
508 return 1;
509 }
 
 
 
 
 
 
 
 
 
510 /* Check if certificate is valid */
511 cert = SSL_get_peer_certificate(ssl);
512
513 if ( cert==NULL ){
514 ssl_set_errmsg("No SSL certificate was presented by the peer");
@@ -648,21 +659,23 @@
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;
@@ -672,23 +685,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
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -35,10 +35,12 @@
35
36 #include <openssl/bio.h>
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/x509.h>
40 #include <poll.h>
41 #include <fcntl.h>
42
43 #include <assert.h>
44 #include <sys/types.h>
45
46 /*
@@ -505,10 +507,19 @@
507 pUrlData->useProxy ? pUrlData->proxyOrigPort : pUrlData->port,
508 ERR_reason_error_string(ERR_get_error()));
509 ssl_close_client();
510 return 1;
511 }
512 {
513 /* The TLS handshake completes on a blocking socket. Switch to non-blocking
514 ** afterwards so that a stalled read returns instead of sleeping in the
515 ** kernel indefinitely; ssl_send()/ssl_receive() use poll() with a timeout
516 ** to wait for the socket and abandon a connection that has gone silent. */
517 int fd = socket_get_fd();
518 int fl = fcntl(fd, F_GETFL, 0);
519 if( fl!=-1 ) fcntl(fd, F_SETFL, fl | O_NONBLOCK);
520 }
521 /* Check if certificate is valid */
522 cert = SSL_get_peer_certificate(ssl);
523
524 if ( cert==NULL ){
525 ssl_set_errmsg("No SSL certificate was presented by the peer");
@@ -648,21 +659,23 @@
659 ** Send content out over the SSL connection from the client to
660 ** the server.
661 */
662 size_t ssl_send(void *NotUsed, void *pContent, size_t N){
663 size_t total = 0;
 
664 while( N>0 ){
665 int sent = BIO_write(iBio, pContent, N);
666 if( sent<=0 ){
667 if( BIO_should_retry(iBio) ){
668 struct pollfd pfd;
669 pfd.fd = socket_get_fd();
670 pfd.events = BIO_should_read(iBio) ? POLLIN : POLLOUT;
671 if( poll(&pfd, 1, 120000)<=0 ) break;
672 if( pfd.revents & (POLLHUP|POLLERR|POLLNVAL) ) break;
673 continue;
674 }
675 break;
676 }
 
677 total += sent;
678 N -= sent;
679 pContent = (void*)&((char*)pContent)[sent];
680 }
681 return total;
@@ -672,23 +685,23 @@
685 ** Receive content back from the client SSL connection. In other
686 ** words read the reply back from the server.
687 */
688 size_t ssl_receive(void *NotUsed, void *pContent, size_t N){
689 size_t total = 0;
 
690 while( N>0 ){
691 int got = BIO_read(iBio, pContent, N);
692 if( got<=0 ){
693 if( BIO_should_retry(iBio) ){
694 struct pollfd pfd;
695 pfd.fd = socket_get_fd();
696 pfd.events = BIO_should_write(iBio) ? POLLOUT : POLLIN;
697 if( poll(&pfd, 1, 120000)<=0 ) break;
698 if( pfd.revents & (POLLHUP|POLLERR|POLLNVAL) ) break;
699 continue;
700 }
701 break;
702 }
 
703 total += got;
704 N -= got;
705 pContent = (void*)&((char*)pContent)[got];
706 }
707 return total;
708
--- src/http_transport.c
+++ src/http_transport.c
@@ -439,14 +439,21 @@
439439
int iStart;
440440
441441
i = iStart = transport.iCursor;
442442
while(1){
443443
if( i >= transport.nUsed ){
444
+ i64 nRcvdBefore = transport.nRcvd;
444445
transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
445446
i -= iStart;
446447
iStart = 0;
447448
if( i >= transport.nUsed ){
449
+ /* No newline yet. If the load brought in no new bytes, the peer has
450
+ ** stalled or closed mid-line: return NULL (truncation) rather than a
451
+ ** partial, newline-less line that the caller would misparse. */
452
+ if( transport.nRcvd==nRcvdBefore ){
453
+ return 0;
454
+ }
448455
transport.pBuf[i] = 0;
449456
transport.iCursor = i;
450457
break;
451458
}
452459
}
453460
--- src/http_transport.c
+++ src/http_transport.c
@@ -439,14 +439,21 @@
439 int iStart;
440
441 i = iStart = transport.iCursor;
442 while(1){
443 if( i >= transport.nUsed ){
 
444 transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
445 i -= iStart;
446 iStart = 0;
447 if( i >= transport.nUsed ){
 
 
 
 
 
 
448 transport.pBuf[i] = 0;
449 transport.iCursor = i;
450 break;
451 }
452 }
453
--- src/http_transport.c
+++ src/http_transport.c
@@ -439,14 +439,21 @@
439 int iStart;
440
441 i = iStart = transport.iCursor;
442 while(1){
443 if( i >= transport.nUsed ){
444 i64 nRcvdBefore = transport.nRcvd;
445 transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
446 i -= iStart;
447 iStart = 0;
448 if( i >= transport.nUsed ){
449 /* No newline yet. If the load brought in no new bytes, the peer has
450 ** stalled or closed mid-line: return NULL (truncation) rather than a
451 ** partial, newline-less line that the caller would misparse. */
452 if( transport.nRcvd==nRcvdBefore ){
453 return 0;
454 }
455 transport.pBuf[i] = 0;
456 transport.iCursor = i;
457 break;
458 }
459 }
460

Keyboard Shortcuts

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