Tcl Source Code

View Ticket
Login
Ticket UUID: f10d91c2d39e1b015be44c41840f4cd0b4f4640
Title: dead-console busy-loop in tclWinConsole.c
Type: Bug Created on: 2026-07-12 03:30:07
Submitter: juliannoble2 Assigned to: apnadkarni
Subsystem: 02. Event Loops Severity: Important
Priority: 5 Medium Last modified: 2026-09-18 17:52:23
Status: Closed Closed by: sebres
Resolution: Fixed Closed on: 2026-09-18 17:52:23
Version: 9
Description:
process orphaned by console-host death busy-loops two threads indefinitely.

This is a placeholder ticket because CDN gates aggressively.
Unclear what text in here triggers it.
Backchannel for ticket details?
User Comments:
juliannoble2 added on 2026-07-12 04:01:53:
My failed initial ticket submission was clearly marked described as having been generated by use of Fable 5 - with my own preamble.

I mentioned that it should be taken with a grain of salt as to whether this is triggered in real-world application use versus the llm generated scratch scripts and testing harnesses that caught this.

I was able to reproduce the effect using the generated repro powershell scripts and noticed increased cpu use and fan spinups.

First part of report here. I've ommitted the rest under the guess that CDN was triggered by code samples rather than detecting AI content.

llm was pointed at up to date tcl source and did a fairly full analysis with suggestions.

Ping me for more if it seems interesting.

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


**Component:** win/tclWinConsole.c (channel driver)
**Versions affected:** verified 9.0.2 and 9.0.3 on Windows 11 (build 26200); the defective
code paths are unchanged in 9.1b1 sources. Tcl 8.6 is not affected (pre-rewrite console
driver; different mechanism).
**Severity:** Serious. Each occurrence leaves a near-invisible orphan burning ~1-2 cores
forever, and occurrences accumulate: nothing ever reaps them, so every console-host death
against a fileevent-reading tclsh adds one. Eight concurrent orphans produced an audible
thermal/fan response on the test workstation while still showing nothing conspicuous in
Task Manager (see Observability).

## Summary

When the console *server* (conhost.exe) serving a Tcl 9 process dies without the client
being terminated, a script with a `chan event stdin readable` handler is never notified:
the driver detects the error internally, but the event it queues for the interp thread is
discarded before `Tcl_NotifyChannel`, so no readable event, no EOF, no read error ever
reaches script level. Meanwhile the driver's console reader thread busy-loops on the
persistent error with no wait, and its continuous watcher-nudging keeps the interp
thread's event loop cycling at zero block time. The process cannot even implement its own
defence via fileevents - only a poll (see Workaround) can see the condition.

## Real-world triggers (legitimate application scenarios)

The reproduction below force-kills a conhost to make the trigger deterministic, but the
same client-side state - console server gone, client alive - arises in ordinary use.
Exposed application profile: any long-running Tcl program that reads console stdin via a
fileevent - a REPL, a monitor accepting operator commands, a server with console control
input - since the trigger is entirely outside the application's control:

- **A user kills a hung console window via Task Manager.** Killing "Console Window Host"
  (conhost.exe) is common folk-advice for a stuck window; TerminateProcess delivers no
  CTRL_CLOSE_EVENT, so the client survives against a dead console. (Identical mechanism
  to the scripted repro - verified.)
- **conhost.exe crashes.** The host is an ordinary user-mode process; when it dies of its
  own bug the client is orphaned exactly as above.
- **A terminal emulator crashes.** A clean close runs ConPTY/tab teardown that terminates
  the client tree, but a *crashed* terminal (Windows Terminal or third-party) never runs
  that cleanup; its console server goes away ungracefully. (Plausible-by-mechanism;
  untested here - the classic-conhost case is the verified repro.)
- **Automation/CI harnesses that kill their console host.** A harness that launches work
  under a hidden console (Start-Process -WindowStyle Hidden, scheduled tasks, agent
  runners) and later "cleans up" by killing the launcher or host process - without a job
  object that covers the clients - leaves the worker tclsh orphaned. This is how the bug
  was first hit in production use: an interactive shell under a hidden console outlived
  its host and was found 37 minutes later with ~1550 cpu-seconds accumulated, discovered
  only because it polluted a test harness's process-liveness checks.
- **Session teardown edge cases.** Any path where the session infrastructure tears down
  the console host before (or without) terminating its clients produces the same state.

In every case the failure compounds: each event adds one immortal spinner, and the
Observability section explains why nobody notices them.

<elided>

apnadkarni added on 2026-07-12 11:26:40:
Sounds like something that should be fixed. Since you cannot seem to post the script here, could you post it in some pastebin? So I can verify once it's fixed?

juliannoble2 added on 2026-07-13 01:43:41:
full Fable 5 text at: http://paste.tclers.tk/6226

apnadkarni added on 2026-07-13 02:54:01:

Thanks for the paste. I reached pretty much an identical conclusion inspecting the code yesterday. I was able to manually trigger the condition by killing from twapi.

Actually, quite impressed with the diagnosis from Fable. I'm now officially redundant :-)


apnadkarni added on 2026-07-13 07:59:33:

Proposed fix is in [bug-f10d91c2] branch. However, note that with the fix in place, the process still does not exit though CPU usage is 0. This is by intent as it is not clear (at the driver level) whether process should exit on stdin closure, and secondly, whether errors should be treated as eof.

IMO, it is up to the stdin file event handler to catch the error that will be generated on the read in the event callback and take appropriate action (exit or whatever.)


apnadkarni added on 2026-07-14 16:28:15:
Fixed in [49925e3365]

sebres added on 2026-09-18 17:52:23:

I don't think it is good idea to cover tcl-tests with ps scripts (cannot it be scripted in tcl?)...
Let alone, it is disabled by default per policies to invoke ps-scripts (at least from temp-folders and/or unsigned), and there are people (like me) which don't allow this intentionally. Not to mention rude corporate policies.

Then you'd see something like this:

==== console-bug-f10d91c2 Test termination of console process with extreme prejudice FAILED
==== Contents of test case:
    exec {*}[auto_execok powershell] -File $psScript
---- Result was:
File T:\Temp\bug-f10d91.ps1 cannot be loaded because running scripts is disabled on this system.
For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.
    + CategoryInfo          : SecurityError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnauthorizedAccess
---- Result should have been (glob matching):
*tclsh terminated*readable fired*exiting*
==== console-bug-f10d91c2 FAILED

I fixed it in [6128e26dc47a54bd] by skipping the test in case of policy violation, but...
However it "works" for me now, I didn't test it on machine where starting of such ps-scripts is allowed, so please test it.