Issue #832: bound TermWidth probes and honor $COLUMNS#833
Conversation
Two changes scoped to tools/TermWidth.lua so the global capture() callers are unaffected: * l_askSystem now reads $COLUMNS before the stty/tput subprocess probes. Interactive shells already export it from their SIGWINCH handler, so the common case skips the probes entirely. This also covers wedged-PTY environments where the shell happens to have a sensible value. * The remaining stty size / tput cols capture() calls are wrapped with `timeout` when the GNU coreutils binary is on PATH, detected once and cached. A broken PTY whose ioctl(TIOCGWINSZ) never returns can no longer hang `module` indefinitely; the probe is killed after one second and the next fallback (or the caller-supplied default) is used. When `timeout` is not available the prefix is empty, preserving the pre-fix behavior so no regression is introduced. Adds rt/termWidthFallback to lock the behavior in. It installs a fake stty/tput on PATH that report a sentinel width and log each invocation, then runs `module -D avail` with and without $COLUMNS set. The log is expected to stay empty in the COLUMNS-set scenario (subprocess probes skipped) and to be non-empty when COLUMNS is unset (probes executed and returned the sentinel). Golden out.txt/err.txt for the new RT will be generated via the standard bless flow once CI runs the test.
Replace GNU coreutils timeout with a TermWidth-local fork/pipe capture bounded by SIGALRM (1s). Read $COLUMNS in TermWidth() before stty/tput probes so interactive shells skip subprocess calls entirely. Extend rt/termWidthFallback with Hermes-safe assertions and a wedged-stty scenario. Golden files blessed.
…luaposix 35 The SIGALRM-bounded probe indexed `posix.signal.kill` on the merged `posix` table. On luaposix 35 (the version packaged for EL9 / Rocky / Alma 9) `posix.signal` is the signal() *function*, not a table, so this raised "attempt to index a function value (field 'signal')" at require time and Lmod failed to start entirely. (The same file already called `posix.signal(SIGALRM, ...)` as a function elsewhere, so the two uses were inconsistent.) Require the `posix.signal` submodule and take kill / signal / SIGALRM / SIGKILL from it, which is a table exposing both the functions and the constants across supported luaposix versions. Verified on Rocky 9 (lua-posix 35.0): before this change `lmod --version` aborts at TermWidth.lua load; after it, Lmod loads and the TACCGH-832 wedged-stty reproducer is bounded to ~1s (falls back to the 80-column default) with $COLUMNS and LMOD_TERM_WIDTH also honored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @mrcawood — pulled the updated branch and tested it in a clean Rocky Linux 9 container (lua-posix 35.0, the EL9-packaged luaposix). Two findings: the width-bounding logic works, but the branch as-is ( 1. Load-time break on luaposix 35 (EL9/Rocky/Alma 9)
On luaposix 35 My follow-up requires the local psignal = require("posix.signal")
local kill = psignal.kill
local SIGALRM = psignal.SIGALRM
local SIGKILL = psignal.SIGKILL
...
local oldSig = psignal.signal(SIGALRM, function() ... end)
...
psignal.signal(SIGALRM, oldSig)2. With that fix, the bounding worksReproducer = my original wedged
Before the fix, case 1 hung indefinitely; after, it's bounded to the 1 s alarm. The follow-up commit is on the branch. 1 s felt fine in my docker / ttyd / etty setups. Happy to adjust anything else you'd like before merge. |
Fixes #832.
Implements both fixes suggested in the issue discussion, scoped to
tools/TermWidth.luaso the globalcapture()callers are unaffected (per @mrcawood's note in #832).Changes
l_askSystemhonors$COLUMNSbefore the stty/tput probes. Interactive shells already exportCOLUMNSfrom theirSIGWINCHhandler, so the common path skips the subprocess probes entirely. This is the cheapest fix and also covers wedged-PTY environments where the shell still has a sensibleCOLUMNS.stty size/tput colscapture() calls are wrapped withtimeout(GNU coreutils) when the binary is onPATH. Detection happens once, lazily, and the result is cached. A broken PTY whoseioctl(TIOCGWINSZ)never returns can no longer hangmoduleindefinitely — the probe is killed after one second and the next fallback (or the caller's default) is used. Whentimeoutis not onPATHthe prefix is empty, so the pre-fix behavior is preserved on systems without coreutils (no regression).Both changes are local to
TermWidth.lua;capture()and its other callers are untouched.Regression test
Adds
rt/termWidthFallback/. It installs a fakestty/tputonPATHthat report a sentinel width (999) and log every invocation, then runsmodule -D availtwice — once with$COLUMNSset, once without. The log is expected to be empty in the COLUMNS-set scenario (subprocess probes skipped) and non-empty whenCOLUMNSis unset (probes executed and returned the sentinel).Verification done
stty size→sleep 999999), and verified thatmodule use ...returns normally after this patch, with bothLMOD_TERM_WIDTHunset andCOLUMNSunset (relying on thetimeoutwrapper) — fallback to default 80 columns.COLUMNS=120set, confirmed the fake stty/tput probes are not invoked at all.capture()to confirm nothing outsideTermWidth.luachanges behavior.