libutil/libmain: never mask SIGWINCH

we need to cache the current terminal size for progress bar reasons, but
we don't want to catch SIGWINCH to update the current terminal size from
a dedicated thread for repl reasons. a SIGWINCH handler function is much
easier to square with these requirements than communication with another
thread to have it change its signal mask, and since any races in handler
code affect only progress bar output and only very rarely (if ever) we'd
better chose the simplest approach. the progress bar could set a handler
of its own for this purpose, but we would much rather replace it instead

fixes #1246

Change-Id: I814d9aaf1b6fbb6a8cefc5af675a3aa372549dc8
This commit is contained in:
eldritch horrors
2026-07-10 16:28:11 +00:00
parent 1a2853bf7b
commit 64c486e22c
5 changed files with 26 additions and 21 deletions
+4
View File
@@ -1,4 +1,5 @@
#include "libutil/fmt.hh"
#include "libutil/terminal.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/logging.hh"
@@ -65,6 +66,9 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
{
auto s = rl->ask(promptForType(promptType));
// rustyline temporarily sets a SIGWINCH handler
KJ_DEFER(invalidateWindowSize());
return match_result(
std::move(s),
[&](rust::String ok) {
-6
View File
@@ -176,12 +176,6 @@ void initNix()
}
#if __APPLE__
/* HACK: on darwin, we need cant use sigprocmask with SIGWINCH.
* Instead, add a dummy sigaction handler, and signalHandlerThread
* can handle the rest. */
act.sa_handler = sigHandler;
if (sigaction(SIGWINCH, &act, 0)) throw SysError("handling SIGWINCH");
/* Disable SA_RESTART for interrupts, so that system calls on this thread
* error with EINTR like they do on Linux.
* Most signals on BSD systems default to SA_RESTART on, but Nix
+4 -4
View File
@@ -7,6 +7,7 @@
#include <atomic>
#include <chrono>
#include <csignal>
#include <map>
#include <memory>
#include <optional>
@@ -118,8 +119,6 @@ static void signalHandlerThread(sigset_t set)
}
} else if (signal == SIGTERM || signal == SIGHUP) {
triggerInterrupt();
} else if (signal == SIGWINCH) {
updateWindowSize();
}
}
}
@@ -164,7 +163,7 @@ void saveSignalMask() {
void startSignalHandlerThread()
{
updateWindowSize();
invalidateWindowSize();
saveSignalMask();
sigset_t set;
@@ -173,10 +172,11 @@ void startSignalHandlerThread()
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGPIPE);
sigaddset(&set, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &set, nullptr))
throw SysError("blocking signals");
signal(SIGWINCH, [](int) { invalidateWindowSize(); });
std::thread(signalHandlerThread, set).detach();
}
+16 -9
View File
@@ -183,22 +183,29 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w
return t;
}
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
static volatile sig_atomic_t windowSizeInvalid = 0;
static Sync<std::pair<unsigned short, unsigned short>> windowSize;
void updateWindowSize()
void invalidateWindowSize()
{
struct winsize ws;
if (ioctl(2, TIOCGWINSZ, &ws) == 0 || ioctl(1, TIOCGWINSZ, &ws) == 0) {
auto windowSize_(windowSize.lock());
windowSize_->first = ws.ws_row;
windowSize_->second = ws.ws_col;
}
windowSizeInvalid = 1;
}
std::pair<unsigned short, unsigned short> getWindowSize()
{
return *windowSize.lock();
auto windowSize_(windowSize.lock());
// this is racy, but given that window sizes change very rarely we'll just accept it
if (windowSizeInvalid) {
windowSizeInvalid = 0;
struct winsize ws;
if (ioctl(2, TIOCGWINSZ, &ws) == 0 || ioctl(1, TIOCGWINSZ, &ws) == 0) {
windowSize_->first = ws.ws_row;
windowSize_->second = ws.ws_col;
}
}
return *windowSize_;
}
std::string makeHyperlink(std::string_view linkText, std::string_view target)
+2 -2
View File
@@ -57,10 +57,10 @@ std::string filterANSIEscapes(std::string_view s,
bool eatTabs = true);
/**
* Recalculate the window size, updating a global variable. Used in the
* Invalidate the window size, updating a global variable. Used in the
* `SIGWINCH` signal handler.
*/
void updateWindowSize();
void invalidateWindowSize();
/**
* @return the number of rows and columns of the terminal.