View Ticket
2026-02-06
03:21 Ticket [48eddca89e] http:geturl https://localhost:8143/info hangs when server not running status still Open with 3 other changes artifact: 735b6ddee6 user: bohagan
2026-02-04
09:06 Open ticket [48eddca89e]. artifact: 7732bc8568 user: anonymous
2026-01-22
02:37 Closed ticket [48eddca89e]. artifact: 12b29daa4d user: bohagan
2025-10-11
22:05 Ticket [48eddca89e]: 3 changes artifact: e26df122f7 user: bohagan
21:49 Ticket [48eddca89e]: 5 changes artifact: 8ed6d326bf user: bohagan
2025-05-22
09:14 New ticket [48eddca89e]. artifact: 45d0b0632d user: anonymous

Ticket Hash: 48eddca89ea79b5cb6477d5f9f46cc890873cf92
Title: http:geturl https://localhost:8143/info hangs when server not running
Status: Open Type: Code Defect
Severity: Critical Priority: Immediate
Subsystem: Resolution: Open
Last Modified: 2026-02-06 03:21:45
40.5 days ago
Created: 2025-05-22 09:14:23
300.2 days ago
Version Found In: 2.0+, 1.8, commit ca1a846290, but affects older and newer commits as well
User Comments:
anonymous added on 2025-05-22 09:14:23:

There is a subtle bug when trying to connect to a server not running via http::geturl using tcltls. tcltls waits for the connection and loops endlessly not giving the Tcl event loop the chance to handle timer events for a timeout. Example code to reproduce:

package require http;
package require tls;
set tok [http::geturl https://localhost:8143/info -timeout 3000] # use port with no service listening here!
http::status $tok;

The problem is fixed with the following patch. Another way to correct the issue might be to change the Tcl http package to set the socket to async even when waiting for the initial connection but this seems to be a bigger effort. I consider and endless loop in a Tcl command as problematic so I decided to avoid it. Keep in mind that tcltls properly aborts the loop for async sockets during the connection phase.

diff -rcN vanilla/tcltls-ca1a846290/generic/tlsIO.c tcltls-1.8-ca1a846290/generic/tlsIO.c
*** vanilla/tcltls-ca1a846290/generic/tlsIO.c   Thu Jan  2 19:05:36 2025
--- tcltls-1.8-ca1a846290/generic/tlsIO.c       Thu May 22 10:46:27 2025
***************
*** 222,235 ****
        dprintf("bioShouldRetry = %d", bioShouldRetry);

        if (err <= 0) {
!           if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT) {
!               bioShouldRetry = 1;
!           } else if (rc == SSL_ERROR_WANT_READ) {
!               bioShouldRetry = 1;
!               statePtr->want |= TCL_READABLE;
!           } else if (rc == SSL_ERROR_WANT_WRITE) {
!               bioShouldRetry = 1;
!               statePtr->want |= TCL_WRITABLE;
            }
        }

--- 222,250 ----
        dprintf("bioShouldRetry = %d", bioShouldRetry);

        if (err <= 0) {
!           /*
!            * Problem here when socket is not async and still not
!            * connected, avoid to loop endlessly. This happens to be the case
!            * the the destination IP is reachable but no service responds on
!            * the requested port. Tcl http does not put the socket in async
!            * mode until connected so we need to allow the Tcl event loop to
!            * catch the timeoout and sits on a vwait for the http token.
!            * Test case which yields hang in endless loop here without this patch is:
!            * package require http;
!            * package require tls;
!            * set tok [http::geturl https://localhost:8143/info -timeout 3000]
!            * http::status $tok;
!            */
!           if(statePtr->flags & TLS_TCL_ASYNC) {
!               if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT) {
!                   bioShouldRetry = 1;
!               } else if (rc == SSL_ERROR_WANT_READ) {
!                   bioShouldRetry = 1;
!                   statePtr->want |= TCL_READABLE;
!               } else if (rc == SSL_ERROR_WANT_WRITE) {
!                   bioShouldRetry = 1;
!                   statePtr->want |= TCL_WRITABLE;
!               }
            }
        }

bohagan added on 2025-10-11 21:49:34:
fixed in [b342c5f3023b3344] and [10199abaf9e00249].

bohagan added on 2026-01-22 02:37:29:

No re-occurrence of the issue has been reported.


anonymous added on 2026-02-04 09:06:13:

The problem still occurs with V2.0 (tcltls-20260122101940-ba2ee7744c).

The delivered patch has not bee integrated into the source. Here is an updated version of the patch (starting around tlsIO.c:230).

	if (ret <= 0) {
	    /*
	     * Problem here when socket is not async and still not
	     * connected, avoid to loop endlessly. This happens to be the case
	     * the the destination IP is reachable but no service responds on
	     * the requested port. Tcl http does not put the socket in async
	     * mode until connected so we need to allow the Tcl event loop to
	     * catch the timeout and sit on a vwait for the http token.
	     * Test case which yields hang in endless loop here without this patch is:
	     * package require http;
	     * package require tls;
	     * ::http::register https 443 [list ::tls::socket -autoservername true]
	     * set tok [http::geturl https://localhost:8143/info -timeout 3000]
	     * http::status $tok;
	     */
	    if(statePtr->flags & TLS_TCL_ASYNC) {
		if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT) {
		    bioShouldRetry = 1;
		} else if (rc == SSL_ERROR_WANT_READ) {
		    bioShouldRetry = 1;
		    statePtr->want |= TCL_READABLE;
		} else if (rc == SSL_ERROR_WANT_WRITE) {
		    bioShouldRetry = 1;
		    statePtr->want |= TCL_WRITABLE;
		}
	    }
	}

This fixes the described problem. Please integrate the patch in to 2.0+.


bohagan added on 2026-02-06 03:21:45:
The issue was fixed as noted in the commits listed previously, just not exactly as you suggested. See lines 230-253 in tlsIO.c:

	if (ret <= 0) {
	    if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT) {
		bioShouldRetry = 1;
	    } else if (rc == SSL_ERROR_WANT_READ) {
		bioShouldRetry = 1;
		statePtr->want |= TCL_READABLE;
	    } else if (rc == SSL_ERROR_WANT_WRITE) {
		bioShouldRetry = 1;
		statePtr->want |= TCL_WRITABLE;
	    }
	}

	if (bioShouldRetry) {
	    dprintf("The I/O did not complete -- but we should try it again");

	    if (statePtr->flags & TLS_TCL_ASYNC) {
		dprintf("Returning EAGAIN so that it can be retried later");
		*errorCodePtr = EAGAIN;
		return 0;
	    } else {
		dprintf("Doing so now");
		continue;
	    }
	}

-------------------

I just tested it with the below script on both Windows and Linux and it worked on both. See below:

-------------------

Script

package require Tcl
package require http
package require tls
::http::register https 443 ::tls::socket

set token [::http::geturl https://localhost:8143/info -timeout 3000]; # use port with no service listening here!
http::status $token


-------------------

OpenSUSE 16.0

Brian@Blizzard:~> /usr/bin/tclsh
% package require Tcl
8.6.17
% package require http
2.9.8
% package require tls
2.0
% ::http::register https 443 ::tls::socket
443 ::tls::socket
% set token [::http::geturl https://localhost:8143/info -timeout 3000]; # use port with no service listening here!
connect failed connection refused
% http::status $token
can't read "token": no such variable
% exit

Brian@Blizzard:~> /opt/tcl9/bin/tclsh9.0
% package require Tcl
9.0.3
% package require http
2.10.1
% package require tls
2.0
% ::http::register https 443 ::tls::socket
443 ::tls::socket {} 0 0
% set token [::http::geturl https://localhost:8143/info -timeout 3000]; # use port with no service listening here!
connect failed: connection refused
% http::status $token
can't read "token": no such variable
%

-------------------

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\Brian>c:\TCL\bin\tclsh
% package require Tcl
8.6.14
% package require http
2.9.8
% package require tls
2.0
% ::http::register https 443 ::tls::socket
443 ::tls::socket
% set token [::http::geturl https://localhost:8143/info -timeout 3000]; # use port with no service listening here!
connect failed connection refused
% http::status $token
can't read "token": no such variable
% exit

C:\Users\Brian>c:\TCL9\bin\tclsh
% package require Tcl
9.0.3
% package require http
2.10.1
% package require tls
2.0
% ::http::register https 443 ::tls::socket
443 ::tls::socket {} 0 0
% set token [::http::geturl https://localhost:8143/info -timeout 3000]; # use port with no service listening here!
connect failed: connection refused
% http::status $token
can't read "token": no such variable
%