Tcl Source Code

View Ticket
Login
Ticket UUID: 06f19cc401de076eea76538424796d1ee54ed221
Title: socket_*-12.3 crashes on Windows 11
Type: Bug Created on: 2025-11-25 14:32:35
Submitter: dkf Assigned to: nobody
Subsystem: 27. Channel Types Severity: Important
Priority: 5 Medium Last modified: 2026-05-29 15:08:05
Status: Closed Closed by: apnadkarni
Resolution: Fixed Closed on: 2026-05-29 15:08:05
Version:
Description:

In what appears to be a recurrence of [217794], I'm getting crashes in socket-*-12.3 (both address families, commit [4724603ffb] on trunk) apparently due to inheritance of the socket, but only when the process exits with an event loop running. Here's a typical piece of output:

==== socket_inet-12.3 testing inheritance of accepted sockets FAILED
==== Contents of test case:

    # Launch the script2 process and connect to it. See how long the socket
    # stays open
    ## exec [interpreter] script2 &
    set p [open "|[list [interpreter] $path(script2)]" r]
    gets $p listen
    set f [socket $localhost $listen]
    fconfigure $f -buffering full -blocking 0
    fileevent $f readable [list getdata $f]
    # If the socket is still open after 5 seconds, the script1 process must
    # have inherited the accepted socket.
    set failed 0
    set after [after 5000 [list set x "accepted socket was inherited"]]
    proc getdata { file } {
        # Read handler on the client socket.
        global x
        global failed
        set status [catch {read $file} data]
        if {$status != 0} {
            set x "read failed, error was $data"
        } elseif {[string compare {} $data]} {
        } elseif {[fblocked $file]} {
        } elseif {[eof $file]} {
            set x "accepted socket was not inherited"
        } else {
            set x "impossible case"
        }
        return
    }
    vwait x
    set x

---- Test cleanup failed:
child killed: segmentation violation
---- errorInfo(cleanup): child killed: segmentation violation
    while executing
"close $p"
    ("uplevel" body line 5)
    invoked from within
"uplevel 1 $cleanup"
---- errorCode(cleanup): CHILDKILLED 18364 SIGSEGV {segmentation violation}
==== socket_inet-12.3 FAILED

The crash comes from one of the subprocesses launched, but only when the innermost subprocess does an exit with the event loop still running; altering the callbacks so that the subprocess exits after stopping the event loop (socket_*.12.4) prevents the crash.

I don't think the code should crash. I don't know why the code is crashing. I've marked the test as a knownBug.

User Comments:
sebres added on 2025-11-25 16:03:33:

I don't think changes of 12.4 "fixed" that. Because the exe of script1 running really 10 seconds (in both cases, by 12.3 or 12.4), where the test got "not inherited" already after few milliseconds (and test ends then). The helper-exe (supposed to check whether the socket handle may be shared across child processes) is running further and doesn't exit on done (because no eof - no readable event), but on after. What would be a nonsense for a segfault. Moreover "child killed" with segfault is from script2, not from script1 (where your changed it), because script1 is started as detached process by [exec $tcltest $delay &] and cannot throw it (especially because it still always running after the test end).

Looks rather like a sporadic bug to me. I can also not reproduce it with 9.0/trunk, neither with test 12.3, nor with test 12.4, even if I run them in timerate hundred times.

How good it is reproducible on your side, Donal? Or did you speak only about CI (GHA)?


dkf added on 2025-11-26 12:49:48:

I wouldn't have bothered reporting this unless it was reproducible on my end.

The only change I needed to apply to get the behaviour to alter was in script1 to make after 10000 exit (the timeout case) to become after 10000 set forever 1 (i.e., to slay the event loop), which made the test not report a SEGV on cleanup. This shouldn't be occurring... but it looks like the server socket, or the client socket it makes on connect, (both in script2) are getting leaked into script1 under circumstances that I don't understand. (This machine is dual-homed IPv4 and IPv6 on this network; that might matter?)

(I was actually hunting for tests that fail because of wrong handling of spaces in directory names...)


dkf added on 2025-11-26 13:06:51:

OK, it's only semi-reliably crashing. Yuck. Sounds like a race condition.

The crashes appear to be originating from script2, or at least the PID reported in the errorCode correlates with the PID of the subprocess made directly there.

I've also seen 12.2 crash, though only the one time.


sebres added on 2025-11-26 13:31:34:

> The crashes appear to be originating from script2

Exactly my point. As well as related to backtrace from the ticket it happened while executing "close $p" (which is definitely pipe of script2).

I'm still unable to reproduce it, no matter what I do.


dkf added on 2025-11-26 14:04:16:

I'm wondering if it's relating to how Windows Defender is configured to run on this machine (something I've very little visibility of and even less control over).


sebres added on 2025-11-26 14:38:43:

Hmm... socket_*-12.1 is not correct (algorithmically) - it opens listener on some free port in child process and output the port to parent, but the parent never reads it and thus the test never tries to open that port, because instead it covers [socket $localhost $listen]. What uses completely wrong listener port here.


sebres added on 2025-11-26 15:06:27:

Similar thing is with 12.2, however a bit different - it trying to cover that accepted socket is not inherited by child of child, but doesn't close it properly in child itself, so it seems to work only in a RC. Moreover after some of tests child processes left behind (and ends only by 10 seconds timeout), what can also confuse the next tests (as for inheritance check consistence).

I'll try to fix all 12.* tests now (regarding their consistence and correctness, regardless the segfault in original tests).


sebres added on 2025-11-26 15:44:08:

[26240753b7a764f9] shall fix all found issues (what I mentioned before, however not SF).

@dkf, since it is not about segfault, just a consistence/correctness fix, but since I'm unable to reproduce the SF in my boxes, can you possible try it on your side (whether it'd also avoid SF)? Just would be good to know to investigate deeper for original issue.


dkf added on 2025-11-27 09:15:03:

Rerunning that part of the test suite while I write this, but focused testing looks good...

I didn't want to just fix the tests though. I was more concerned that there was a crash in the first place.


dkf added on 2025-11-27 09:15:59:

Full run of socket.test passed.


sebres added on 2025-11-27 13:36:51:

> I didn't want to just fix the tests though. I was more concerned that there was a crash in the first place.

This was also my intention, just... I'd like to understand what exactly may cause the segfault by exit in child.

However since the 2 from 3 tests were functional wrong, I thought it'd be better to repair them. The previous (segfaulting) version is in the repository too and even if I merge the new versions, the previous can be also created by revert/backout of merge or switch to some old revision (and tee in another branch). I guess it has probably nothing with socket, but with exit/finalization process or pipes.


apnadkarni added on 2026-03-14 02:53:41:

For the record, socket-12.4 also crashed with a segmentation violation that looks very similar in one of my github CI runs. Cannot reproduce despite running continuously in a loop and have never seen it before on my own systems.


apnadkarni added on 2026-04-02 08:19:24:
For the record, 12.3 crash also appeared again on core-9-0-branch in Github CI, gcc --enable-symbols build.

apnadkarni added on 2026-05-27 09:09:13:
Examining the github logs, it appears this has only shown up with memory debug enabled, (configure --enable-symbols=mem or nmake OPTS=symbols STATS=compdbg,memdbg)

Will try to reproduce that locally.

apnadkarni added on 2026-05-28 06:40:09:

Managed to reliably reproduce by running the tests continuously. Fails within 30 iterations due to use after free. The bug is present in release builds as well but shows up more easily in debug builds because the C ucrt/msvcrt runtimes scrub memory only in debug builds.

Proposed fix is in the bug-06f19cc4-with-tcltest branch.

Diagnosis: The socket implementation of Windows maintains a separate "socket thread" for a Tcl thread to handle socket I/O. The list of live sockets is maintained in a TSD list within the Tcl thread and also shared with the socket thread. When a channel is in normal operation, the socket state structure is removed from this list via the socket channel implementation's ThreadActionProc (via a CutChannel call). However, when a thread exits, TclFinalizeIOSubsystem frees the socket state structure without calling CutChannel. The socket thread still has access to it via the shared list and depending on when the socket closure windows message is received, accesses the freed memory.

The fix simply adds a CutChannel to TclFinalizeIOSubsystem prior to the ChanClose. I believe this should apply to all platforms but I'm not sure of the Unix implementations so it's ifdef'ed to WIN32.

Although a one-liner, it's also a fairly low level change that needs substantial airtime for an issue that only arises on thread exits with open sockets so do not think it should be backported to 9.0.4.

Reviews welcomed.


apnadkarni added on 2026-05-29 15:08:05:
Fixed in [361fb72eaa] for 9.1.