mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-22 18:59:59 +08:00
Improve UNIX signal handling (#518)
There was a dead lock caused by the reentrancy of the post method. Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
parent
05f29ff3b3
commit
f21ca3aa14
@ -52,6 +52,8 @@ class ScreenInteractive : public Screen {
|
||||
Closure WithRestoredIO(Closure);
|
||||
|
||||
private:
|
||||
void ExitNow();
|
||||
|
||||
void Install();
|
||||
void Uninstall();
|
||||
|
||||
@ -64,8 +66,9 @@ class ScreenInteractive : public Screen {
|
||||
|
||||
void HandleTask(Component component, Task& task);
|
||||
void Draw(Component component);
|
||||
void ResetCursorPosition();
|
||||
|
||||
void SigStop();
|
||||
void Signal(int signal);
|
||||
|
||||
ScreenInteractive* suspended_screen_ = nullptr;
|
||||
enum class Dimension {
|
||||
@ -106,7 +109,7 @@ class ScreenInteractive : public Screen {
|
||||
public:
|
||||
class Private {
|
||||
public:
|
||||
static void SigStop(ScreenInteractive& s) { return s.SigStop(); }
|
||||
static void Signal(ScreenInteractive& s, int signal) { s.Signal(signal); }
|
||||
};
|
||||
friend Private;
|
||||
};
|
||||
|
@ -148,7 +148,8 @@ void ftxui_on_resize(int columns, int rows) {
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#else // POSIX (Linux & Mac)
|
||||
|
||||
#include <sys/time.h> // for timeval
|
||||
|
||||
int CheckStdinReady(int usec_timeout) {
|
||||
@ -178,9 +179,74 @@ void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
void OnExit() {
|
||||
while (!on_exit_functions.empty()) {
|
||||
on_exit_functions.top()();
|
||||
on_exit_functions.pop();
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<int> g_signal_exit_count = 0;
|
||||
#if !defined(_WIN32)
|
||||
std::atomic<int> g_signal_stop_count = 0;
|
||||
std::atomic<int> g_signal_resize_count = 0;
|
||||
#endif
|
||||
|
||||
// Async signal safe function
|
||||
void RecordSignal(int signal) {
|
||||
switch (signal) {
|
||||
case SIGABRT:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGINT:
|
||||
case SIGSEGV:
|
||||
case SIGTERM:
|
||||
g_signal_exit_count++;
|
||||
break;
|
||||
|
||||
#if !defined(_WIN32)
|
||||
case SIGTSTP:
|
||||
g_signal_stop_count++;
|
||||
break;
|
||||
|
||||
case SIGWINCH:
|
||||
g_signal_resize_count++;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ExecuteSignalHandlers() {
|
||||
int signal_exit_count = g_signal_exit_count.exchange(0);
|
||||
while (signal_exit_count--) {
|
||||
ScreenInteractive::Private::Signal(*g_active_screen, SIGABRT);
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
int signal_stop_count = g_signal_stop_count.exchange(0);
|
||||
while (signal_stop_count--) {
|
||||
ScreenInteractive::Private::Signal(*g_active_screen, SIGTSTP);
|
||||
}
|
||||
|
||||
int signal_resize_count = g_signal_resize_count.exchange(0);
|
||||
while (signal_resize_count--) {
|
||||
ScreenInteractive::Private::Signal(*g_active_screen, SIGWINCH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void InstallSignalHandler(int sig) {
|
||||
auto old_signal_handler = std::signal(sig, RecordSignal);
|
||||
on_exit_functions.push(
|
||||
[=] { std::ignore = std::signal(sig, old_signal_handler); });
|
||||
};
|
||||
|
||||
const std::string CSI = "\x1b["; // NOLINT
|
||||
|
||||
// DEC: Digital Equipment Corporation
|
||||
@ -230,31 +296,6 @@ std::string DeviceStatusReport(DSRMode ps) {
|
||||
return CSI + std::to_string(int(ps)) + "n";
|
||||
}
|
||||
|
||||
using SignalHandler = void(int);
|
||||
std::stack<Closure> on_exit_functions; // NOLINT
|
||||
void OnExit(int signal) {
|
||||
(void)signal;
|
||||
while (!on_exit_functions.empty()) {
|
||||
on_exit_functions.top()();
|
||||
on_exit_functions.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const auto install_signal_handler = [](int sig, SignalHandler handler) {
|
||||
auto old_signal_handler = std::signal(sig, handler);
|
||||
on_exit_functions.push(
|
||||
[=] { std::ignore = std::signal(sig, old_signal_handler); });
|
||||
};
|
||||
|
||||
Closure g_on_resize = [] {}; // NOLINT
|
||||
void OnResize(int /* signal */) {
|
||||
g_on_resize();
|
||||
}
|
||||
|
||||
void OnSigStop(int /*signal*/) {
|
||||
ScreenInteractive::Private::SigStop(*g_active_screen);
|
||||
}
|
||||
|
||||
class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
public:
|
||||
explicit CapturedMouseImpl(std::function<void(void)> callback)
|
||||
@ -378,10 +419,13 @@ void ScreenInteractive::PreMain() {
|
||||
// Suspend previously active screen:
|
||||
if (g_active_screen) {
|
||||
std::swap(suspended_screen_, g_active_screen);
|
||||
std::cout << suspended_screen_->reset_cursor_position
|
||||
<< suspended_screen_->ResetPosition(/*clear=*/true);
|
||||
// Reset cursor position to the top of the screen and clear the screen.
|
||||
suspended_screen_->ResetCursorPosition();
|
||||
std::cout << suspended_screen_->ResetPosition(/*clear=*/true);
|
||||
suspended_screen_->dimx_ = 0;
|
||||
suspended_screen_->dimy_ = 0;
|
||||
|
||||
// Reset dimensions to force drawing the screen again next time:
|
||||
suspended_screen_->Uninstall();
|
||||
}
|
||||
|
||||
@ -393,20 +437,22 @@ void ScreenInteractive::PreMain() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostMain() {
|
||||
g_active_screen->Uninstall();
|
||||
g_active_screen = nullptr;
|
||||
|
||||
// Put cursor position at the end of the drawing.
|
||||
std::cout << reset_cursor_position;
|
||||
ResetCursorPosition();
|
||||
|
||||
g_active_screen = nullptr;
|
||||
|
||||
// Restore suspended screen.
|
||||
if (suspended_screen_) {
|
||||
// Clear screen, and put the cursor at the beginning of the drawing.
|
||||
std::cout << ResetPosition(/*clear=*/true);
|
||||
dimx_ = 0;
|
||||
dimy_ = 0;
|
||||
Uninstall();
|
||||
std::swap(g_active_screen, suspended_screen_);
|
||||
g_active_screen->Install();
|
||||
} else {
|
||||
Uninstall();
|
||||
// On final exit, keep the current drawing and reset cursor position one
|
||||
// line after it.
|
||||
std::cout << std::endl;
|
||||
@ -430,6 +476,8 @@ ScreenInteractive* ScreenInteractive::Active() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::Install() {
|
||||
frame_valid_ = false;
|
||||
|
||||
// After uninstalling the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
on_exit_functions.push([] { Flush(); });
|
||||
@ -439,7 +487,7 @@ void ScreenInteractive::Install() {
|
||||
// Install signal handlers to restore the terminal state on exit. The default
|
||||
// signal handlers are restored on exit.
|
||||
for (int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
|
||||
install_signal_handler(signal, OnExit);
|
||||
InstallSignalHandler(signal);
|
||||
}
|
||||
|
||||
// Save the old terminal configuration and restore it on exit.
|
||||
@ -474,6 +522,10 @@ void ScreenInteractive::Install() {
|
||||
SetConsoleMode(stdin_handle, in_mode);
|
||||
SetConsoleMode(stdout_handle, out_mode);
|
||||
#else
|
||||
for (int signal : {SIGWINCH, SIGTSTP}) {
|
||||
InstallSignalHandler(signal);
|
||||
}
|
||||
|
||||
struct termios terminal; // NOLINT
|
||||
tcgetattr(STDIN_FILENO, &terminal);
|
||||
on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
|
||||
@ -488,12 +540,6 @@ void ScreenInteractive::Install() {
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
|
||||
|
||||
// Handle resize.
|
||||
g_on_resize = [&] { task_sender_->Send(Event::Special({0})); };
|
||||
install_signal_handler(SIGWINCH, OnResize);
|
||||
|
||||
// Handle SIGTSTP/SIGCONT.
|
||||
install_signal_handler(SIGTSTP, OnSigStop);
|
||||
#endif
|
||||
|
||||
auto enable = [&](const std::vector<DECMode>& parameters) {
|
||||
@ -529,8 +575,8 @@ void ScreenInteractive::Install() {
|
||||
DECMode::kMouseSgrExtMode,
|
||||
});
|
||||
|
||||
// After installing the new configuration, flush it to the terminal to ensure
|
||||
// it is fully applied:
|
||||
// After installing the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
Flush();
|
||||
|
||||
quit_ = false;
|
||||
@ -542,28 +588,27 @@ void ScreenInteractive::Install() {
|
||||
}
|
||||
|
||||
void ScreenInteractive::Uninstall() {
|
||||
ExitLoopClosure()();
|
||||
ExitNow();
|
||||
event_listener_.join();
|
||||
animation_listener_.join();
|
||||
|
||||
OnExit(0);
|
||||
OnExit();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::RunOnceBlocking(Component component) {
|
||||
ExecuteSignalHandlers();
|
||||
Task task;
|
||||
if (task_receiver_->Receive(&task)) {
|
||||
HandleTask(component, task);
|
||||
}
|
||||
|
||||
RunOnce(component);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
void ScreenInteractive::RunOnce(Component component) {
|
||||
Task task;
|
||||
while (task_receiver_->ReceiveNonBlocking(&task)) {
|
||||
HandleTask(component, task);
|
||||
ExecuteSignalHandlers();
|
||||
}
|
||||
Draw(std::move(component));
|
||||
}
|
||||
@ -650,7 +695,8 @@ void ScreenInteractive::Draw(Component component) {
|
||||
}
|
||||
|
||||
bool resized = (dimx != dimx_) || (dimy != dimy_);
|
||||
std::cout << reset_cursor_position << ResetPosition(/*clear=*/resized);
|
||||
ResetCursorPosition();
|
||||
std::cout << ResetPosition(/*clear=*/resized);
|
||||
|
||||
// Resize the screen if needed.
|
||||
if (resized) {
|
||||
@ -710,7 +756,8 @@ void ScreenInteractive::Draw(Component component) {
|
||||
set_cursor_position += "\033[?25l";
|
||||
} else {
|
||||
set_cursor_position += "\033[?25h";
|
||||
set_cursor_position += "\033[" + std::to_string(int(cursor_.shape)) + " q";
|
||||
set_cursor_position +=
|
||||
"\033[" + std::to_string(int(cursor_.shape)) + " q";
|
||||
}
|
||||
}
|
||||
|
||||
@ -720,32 +767,50 @@ void ScreenInteractive::Draw(Component component) {
|
||||
frame_valid_ = true;
|
||||
}
|
||||
|
||||
void ScreenInteractive::ResetCursorPosition() {
|
||||
std::cout << reset_cursor_position;
|
||||
reset_cursor_position = "";
|
||||
}
|
||||
|
||||
Closure ScreenInteractive::ExitLoopClosure() {
|
||||
return [this] { Exit(); };
|
||||
}
|
||||
|
||||
void ScreenInteractive::Exit() {
|
||||
Post([this] {
|
||||
quit_ = true;
|
||||
task_sender_.reset();
|
||||
});
|
||||
Post([this] { ExitNow(); });
|
||||
}
|
||||
|
||||
void ScreenInteractive::SigStop() {
|
||||
#if defined(_WIN32)
|
||||
// Windows do no support SIGTSTP.
|
||||
#else
|
||||
void ScreenInteractive::ExitNow() {
|
||||
quit_ = true;
|
||||
task_sender_.reset();
|
||||
}
|
||||
|
||||
void ScreenInteractive::Signal(int signal) {
|
||||
if (signal == SIGABRT) {
|
||||
OnExit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows do no support SIGTSTP / SIGWINCH
|
||||
#if !defined(_WIN32)
|
||||
if (signal == SIGTSTP) {
|
||||
Post([&] {
|
||||
ResetCursorPosition();
|
||||
std::cout << ResetPosition(/*clear*/ true); // Cursor to the beginning
|
||||
Uninstall();
|
||||
std::cout << reset_cursor_position;
|
||||
reset_cursor_position = "";
|
||||
std::cout << ResetPosition(/*clear=*/true);
|
||||
dimx_ = 0;
|
||||
dimy_ = 0;
|
||||
Flush();
|
||||
std::ignore = std::raise(SIGTSTP);
|
||||
Install();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (signal == SIGWINCH) {
|
||||
Post(Event::Special({0}));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user