diff --git a/goals/G-036-tcl9-udp-console-worker-wedge.md b/goals/G-036-tcl9-udp-console-worker-wedge.md index 8139f1b3..9a548b45 100644 --- a/goals/G-036-tcl9-udp-console-worker-wedge.md +++ b/goals/G-036-tcl9-udp-console-worker-wedge.md @@ -61,6 +61,32 @@ Ingredients ruled out by standalone probes (all clean under a hidden console on - + 30 datagrams and a later send on the ICMP-flagged socket - + CONIN$ opened in the main thread with a readable handler armed - + twapi::SetStdHandle(-10, console) before worker creation +- + main thread closing stdin and ADOPTING the CONIN$ channel into the stdin std-channel + slot (channel name becomes 'stdin'), SetStdHandle, and an active gets-on-readable reader + armed - i.e. the same visible stdin arrangement the restarted repl has ('adopt' probe + variant, 2026-07-08) + +Upstream reconnaissance (2026-07-08, TEMP_REFERENCE fossil checkouts: Tcl core trunk-side +branch @ 2026-07-07, tcludp trunk @ 2026-07-05): + +- core-9-0-branch has had NO tclWinConsole.c change since 9.0.2 beyond an include-hygiene + commit (2025-10-24) - the wedge, if in the console driver, is likely still present in + 9.0.x maintenance. Trunk has console-channel activity by apnadkarni (2025-12-29 + metakey-notification change + revert; branch apn-winconsole-noreadahead) worth reading. +- Current tclWinConsole.c architecture confirms a credible wedge surface: process-global + gConsoleLock, a console-handle list SHARED ACROSS ALL Tcl THREADS, per-handle SRWLOCKs, + reader/writer threads coordinated via CONDITION_VARIABLEs (consoleThreadCV / + interpThreadCV "for awakening interp thread(s)"), and Tcl_ThreadAlert fan-out to + interested channel-owning threads. A missed CV wakeup leaves an interp thread blocked + inside a console-driver call - matching the observed "blocked not spinning, timers and + thread::sends unserviced". +- Sharpened worker-side ingredient hypothesis: a fresh thread's std channels initialize on + its FIRST channel creation (udp_open, or a -file log open) - with the process stdin + handle pointing at the console (post SetStdHandle), that first channel creation attaches + the worker to the shared console machinery. Fits the data exactly: run-2 workers that + never create any channel (-file {} -syslog {}) survive; channel-creating ones wedge; + run-1 workers (stdin handle = dead pipe at creation) are immune. NOT yet reproduced by + probes, so at least one more live-shell ingredient is still missing (candidates below). Untested ingredients (next candidates): @@ -79,12 +105,20 @@ per-handle reader/writer threads + gConsoleLock) or tcludp's Windows event sourc ## Approach -1. Complete the minimal repro by adding the untested ingredients one at a time to the standalone - probe (console read pumping and console output channels first - they are what the live shell - has and every clean probe lacked). -2. If no single addition wedges, capture native thread stacks of a wedged in-context specimen - (spawn via the hidden-console repro below; it idles indefinitely) and work backwards from the - blocked wait. +1. Capture native thread stacks of a wedged in-context specimen and work backwards from the + blocked wait - promoted to first step after seven single-ingredient probe variants all came + up clean (ingredient bisection is guessing; the stack names the exact wait). Spawn a specimen + via the hidden-console repro below (post-fix, punkshout/punksherr still wedge in run 2 - their + syslog comes from the logging config, and they idle indefinitely as specimens). Needs a native + tool: none installed as of 2026-07-08 (no procdump/cdb/windbg) - e.g. + `winget install Microsoft.WinDbg` or Sysinternals procdump (user's call), or + `rundll32 C:\Windows\System32\comsvcs.dll, MiniDump full` writes a dump + without extra tools but analysis still needs a debugger. Read stacks against + tclWinConsole.c's CV waits and tclWinNotify.c. +2. Continue minimal-repro construction guided by what the stacks reveal (remaining untried + live-shell ingredients: codethread presence, fifo2 tee stack with thread::transfer'd halves, + punk::console raw/line + VT SetConsoleMode churn and -inputmode queries, console output + channels, multi-thread channel-creation concurrency). 3. Write up the mechanism here; reduce the repro accordingly. 4. Re-verify against a Tcl 9 built from current core sources before any upstream report (the kit's 9.0.2 may carry an already-fixed bug). Building Tcl 9 from source is G-005 territory; @@ -174,8 +208,8 @@ Write-Output "batch done" ### Standalone probe - udpthread_test.tcl (all variants clean so far) -Run: `punk902z src script udpthread_test.tcl [conin] [setstd] [twapi]` - directly (consoleless) -or via hidden console (`Start-Process cmd.exe -WindowStyle Hidden -ArgumentList '/c','...exe src +Run: `punk902z src script udpthread_test.tcl [adopt|conin [setstd]] [twapi]` - directly +(consoleless) or via hidden console. (`Start-Process cmd.exe -WindowStyle Hidden -ArgumentList '/c','...exe src script ...tcl conin setstd > out 2>&1'`). Diag appends to `%TEMP%\udpthread_diag.txt`. ```tcl @@ -193,6 +227,25 @@ proc d {msg} { } d "=== run start (tcl [info patchlevel]) argv:'$::argv' ===" +# mimic punkshell's actual restart: close stdin so the CONIN$ channel is ADOPTED into the +# stdin std-channel slot, then read it like the repl reader does +if {"adopt" in $::argv} { + if {[catch { + close stdin + set ::conin [open {CONIN$} r] + package require twapi + set h [twapi::get_tcl_channel_handle $::conin in] + twapi::SetStdHandle -10 $h + chan configure $::conin -blocking 0 + chan event $::conin readable [list apply {{c} { + if {[gets $c line] < 0 && [eof $c]} {chan event $c readable {}} + }} $::conin] + d "stdin closed, CONIN\$ adopted as '$::conin' (stdin slot), SetStdHandle done, reader armed" + } err]} { + d "adopt setup FAILED: $err" + } +} + # mimic the restarted repl: main thread opens CONIN$ and arms a readable handler if {"conin" in $::argv} { if {[catch {