Browse Source

punk::repl 0.2.2: waiting-chunks path avoids drained-channel read on tcl 8.6 consoles

Companion to punk::console 0.7.1 (three-site 8.6 console misdetection fix).
The line-mode waiting-chunks path in repl_handler performed an unsized read
that, entered via 'after idle' with the channel drained, made the tcl 8.6
windows console driver park a blocking cooked-mode ReadConsole (driver reads
sample the console mode at issue time - a later raw flip cannot cancel one).
After typed-ahead was stashed during a terminal-query raw window, that
parked read swallowed every subsequent query response in the session until
Enter, with the responses later leaking to the reader.

On hostage-prone consoles (windows, no -inputmode configure key, real twapi
console handle) the path now consumes only data already in the Tcl channel
buffer (chan pending input + sized read - no driver probe). With nothing
buffered it processes the stashed complete lines directly - preserving the
recovered-typeahead execution order - and arms the readable handler for any
remaining partial line, replacing an after-idle reinvoke that, without the
read, could never have progressed; the parked read that arming creates is
the reader legitimately waiting for the user to finish typing. Behaviour on
tcl 9/8.7 (-inputmode consoles) and in raw mode is unchanged.

Interactively verified on punksys (tcl 8.6.13, src launch): the previously
failing case - typing a full command during a line-mode brokered query loop
- now runs with all queries answering, the typed line executing after the
loop, and no timeouts, response leakage or chan-blocked diagnostics; the
session remains healthy afterwards. Full suite passes at baseline
(exec-14.3 only) under Tcl 9.0.3.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 1 week ago
parent
commit
552a311d6e
  1. 49
      src/modules/punk/repl-999999.0a1.0.tm
  2. 3
      src/modules/punk/repl-buildversion.txt

49
src/modules/punk/repl-999999.0a1.0.tm

@ -1635,6 +1635,7 @@ proc repl::repl_handler {inputchan readmore prompt_config} {
#note -inputmode not available in Tcl 8.6 for chan configure!
#According to DKF - -buffering option doesn't affect input channels
set rawmode 0
set waiting_needs_reader 0 ;#set when the 8.6-console waiting-chunks path skips its read on a drained channel - the readable handler must be armed (not the after-idle reinvoke) or the pending partial line could never complete
set original_input_conf [chan configure $inputchan] ;#whether repl is in line or raw mode - we restore the inputchan (stdin) state
if {[dict exists $original_input_conf -inputmode]} {
@ -1693,9 +1694,46 @@ proc repl::repl_handler {inputchan readmore prompt_config} {
uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config]
set input_chunks_waiting($inputchan) [list $waitingchunk]
} else {
set chunk [read $inputchan]
set hostage_noread 0
if {"windows" eq $::tcl_platform(platform)
&& ![dict exists $original_input_conf -inputmode]
&& $::punk::console::has_twapi
&& ![catch {twapi::get_console_handle stdin}]} {
#tcl 8.6 windows console channel (no -inputmode configure key, real console
#handle). On the 8.6 console driver a read on a *drained* channel parks a
#blocking cooked-mode ReadConsole that a later raw-mode flip cannot cancel:
#it swallows terminal query responses until Enter (see punk::console 0.7.1
#buildversion notes). This waiting-chunks path is normally entered via
#'after idle' with the channel drained - only consume what is already in the
#Tcl channel buffer (sized read - no driver probe); when nothing is buffered,
#process the stashed complete lines without reading and arm the readable
#handler for the rest.
set avail [chan pending input $inputchan]
if {$avail > 0} {
set chunk [read $inputchan $avail]
} else {
set chunk ""
set hostage_noread 1
set waiting_needs_reader 1
}
} else {
set chunk [read $inputchan]
}
set chunksize [string length $chunk]
if {$chunksize > 0} {
if {$hostage_noread} {
#nothing in the Tcl buffer and we must not probe the drained driver.
#Process the complete stashed lines now; a remaining partial line can only
#progress via more user input, which the armed readable handler delivers.
if {[llength $stdinlines]} {
punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf
uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config]
}
if {$waitingchunk ne ""} {
set input_chunks_waiting($inputchan) [list $waitingchunk]
} else {
set input_chunks_waiting($inputchan) [list]
}
} elseif {$chunksize > 0} {
if {[string index $chunk end] eq "\n"} {
lappend stdinlines $waitingchunk[string range $chunk 0 end-1]
#punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]"
@ -1864,7 +1902,12 @@ proc repl::repl_handler {inputchan readmore prompt_config} {
##################################################################################
#Re-enable channel read handler only if no waiting chunks - must process in order
##################################################################################
if {![llength $input_chunks_waiting($inputchan)]} {
if {![llength $input_chunks_waiting($inputchan)] || $waiting_needs_reader} {
#waiting_needs_reader: the 8.6-console waiting-chunks path found nothing in the
#Tcl channel buffer and must not probe the drained driver - the waiting data is
#an incomplete line that can only progress when the user types more, so arm the
#reader (the driver listening for input is its job here) instead of an after-idle
#reinvoke that could never make progress.
chan event $inputchan readable [list ::repl::repl_handler $inputchan $readmore $prompt_config]
} else {
#review

3
src/modules/punk/repl-buildversion.txt

@ -1,6 +1,7 @@
0.2.1
0.2.2
#First line must be a semantic version number
#all other lines are ignored.
#0.2.2 - repl_handler line-mode waiting-chunks path no longer performs its opportunistic unsized read on tcl 8.6 windows console channels (no -inputmode key, real twapi console handle): the path is entered via 'after idle' with the channel usually drained, and on the 8.6 console driver a drained read parks a blocking cooked-mode ReadConsole that a later raw flip cannot cancel - after typed-ahead was stashed during a terminal-query raw window, that parked read swallowed every subsequent query response in the session until Enter (companion to the punk::console 0.7.1 three-site console-misdetection fix). On such consoles the path now consumes only data already in the Tcl channel buffer (chan pending input + sized read - no driver probe); with nothing buffered it processes the stashed complete lines directly and arms the readable handler for any remaining partial line (replacing an after-idle reinvoke that could never progress). Behaviour on tcl 9/8.7 (-inputmode consoles) and in raw mode is unchanged.
#0.2.1 - call-site update for punk::console 0.6.0 tsv array rename: console -> punk_console (is_raw); no behaviour change
#0.2.0 - codethread init_script now propagates package prefer via %packageprefer% scriptmap entry
#0.2.0 - code interp (punk/0 repltype) now uses punk::lib::interp_sync_package_paths -libunknown 1 instead of manual epoch-only copy, propagating tm list, auto_path, package prefer, and libunknown to the code interp

Loading…
Cancel
Save