ncurses прерывает системный вызов при изменении размера терминала

У меня проблема с ncurses, и я не смог найти решение в Интернете, поэтому я написал следующую небольшую программу, чтобы продемонстрировать проблему.

Вы можете скомпилировать его через:

sudo aptitude install ncurses-dev
g++ -lncurses -o resize resize.cpp

Он отображает целочисленный счетчик, увеличивающийся каждую секунду, разветвляясь на процесс таймера, который периодически отправляет один байт родительскому процессу через пару сокетов. Вы можете выйти из него, нажав CTRL+C.

Когда вы изменяете размер терминала, вы должны получить сообщение об ошибке «Прерванный системный вызов». Таким образом, вызов чтения прерывается SIGWINCH при изменении размера. Но как мне этого избежать? Или обычно системный вызов прерывается? Но как мне обработать прерванный системный вызов, чтобы продолжить увеличение счетчика, поскольку после прерывания дескриптор файла кажется мертвым.

Если вы используете неблокирующие сокеты, вместо этого вы получите «Ресурс временно недоступен».

Я использую стабильную версию Debian Wheezy, поэтому версия ncurses — 5.9-10, а версия libstdc++ — 4.7.2-5.

#include <ncurses.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>

//Define a second.
timespec span = {1, 0};

//Handles both, SIGWINCH and SIGINT
void handle(int signal) {
    switch (signal) {
        case SIGWINCH:
            //Reinitialize ncurses to get new size
            endwin();
            refresh();
            printw("Catched SIGWINCH and handled it.\n");
            refresh();
        break;
        case SIGINT:
            //Catched CTRL+C and quit
            endwin();
            exit(0);
        break;
    }
}

//This registers above signal handler function
void set_handler_for(int signal) {
    struct sigaction action;
    action.sa_handler = handle;
    action.sa_flags = 0;
    if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
        throw "Cannot set signal handler";
}

main() {
    int fd[2];
    //In this try block we fork into the timer process
    try {
        set_handler_for(SIGINT);
        set_handler_for(SIGWINCH);
        //Creating a socketpair to communicate between timer and parent process
        if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
            throw "Cannot create socketpair";
        pid_t pid;
        //Doing the fork
        if (-1 == (pid = fork()))
            throw "Cannot fork process";
        if (!pid) {
            //We are the timer, so closing the other end of the socketpair
            close(fd[0]);
            //We send one byte every second to the parent process
            while (true) {
                char byte;
                ssize_t bytes = write(fd[1], &byte, sizeof byte);
                if (0 >= bytes)
                    throw "Cannot write";
                nanosleep(&span, 0);
            }
            //Here the timer process ends
            exit(0);
        }
        //We are the parent process, so closing the other end of the socketpair
        close(fd[1]);
    }
    catch (const char*& what) {
        std::cerr << what << std::endl;
        exit(1);
    }
    //Parent process - Initializing ncurses
    initscr();
    noecho();
    curs_set(0);
    nodelay(stdscr, TRUE);
    //In this try block we read (blocking) the byte from the timer process every second
    try {
        int tick = 0;
        while (true) {
            char byte;
            ssize_t bytes = read(fd[0], &byte, sizeof byte);
            if (0 >= bytes)
                throw "Cannot read";
            //Clear screen and print increased counter
            clear();
            mvprintw(0, 0, "Tick: %d - Resize terminal and press CTRL+C to quit.\n", ++tick);
            //Catch special key KEY_RESIZE and reinitialize ncurses to get new size (actually not necassary)
            int key;
            while ((key = getch()) != ERR) {
                if (key == KEY_RESIZE) {
                    endwin();
                    refresh();
                    printw("Got KEY_RESIZE and handled it.\n");
                }
            }
            //Update the screen
            refresh();
        }
    }
    catch (const char*& what) {
        //We got an error - print it but don't quit in order to have time to read it
        std::string error(what);
        if (errno) {
            error.append(": ");
            error.append(strerror(errno));
        }
        error = "Catched exception: "+error+"\n";
        printw(error.c_str());
        refresh();
        //Waiting for CTRL+C to quit
        while (true)
            nanosleep(&span, 0);
    }
}

Благодарю вас!

С Уважением


person user2876571    schedule 13.10.2013    source источник


Ответы (2)


Большинство (если не все) системных вызовов имеют прерванный код ошибки (errno == EINTR), это нормально.

Я бы проверил EINTR при чтении из канала и проигнорировал бы его, просто прочитав еще раз.

Я бы не стал вызывать какие-либо функции ncurses в обработчике сигналов, некоторые из них являются реентерабельными, но я сомневаюсь, что printw таков. Просто выполните проверку KEY_RESIZE.

person parkydr    schedule 14.10.2013

Хорошо, я заработал, используя только повторно входящие функции в обработчиках сигналов. Теперь пара сокетов все еще работает после EINTR или EAGAIN.

Благодарю вас!

#include <ncurses.h>
#include <signal.h>

#include <netdb.h>
#include <unistd.h>

#include <string.h>
#include <errno.h>

#include <string>
#include <iostream>

// Define a second.
timespec base = {1, 0};

// Holds raised SIGINTs.
size_t raised_SIGINT = 0;

// Holds raised SIGWINCHs.
size_t raised_SIGWINCH = 0;

// Handle SIGWINCH
void handle_SIGWINCH(int) {
    ++raised_SIGWINCH;
}

// Handle SIGINT
void handle_SIGINT(int) {
    ++raised_SIGINT;
}

// Registers signal handlers.
void assign(int signal, void (*handler)(int)) {
    struct sigaction action;
    action.sa_handler = handler;
    action.sa_flags = 0;
    if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
        throw "Cannot set signal handler";
}

// Prints ticks alive and usage information.
inline void print(size_t ticks) {
    mvprintw(0, 0, "%ds alive. Resize terminal and press CTRL+C to quit.\n\n", ticks);
}

int main() {
    // Holds the two socketpair file descriptors.
    int fd[2];

    // Fork into the timer process.
    try {
        // Register both signals.
        assign(SIGINT, handle_SIGINT);
        assign(SIGWINCH, handle_SIGWINCH);

        // Create a socketpair to communicate between timer and parent process.
        if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
            throw "Cannot create socketpair";

        // Doing the fork.
        pid_t pid;
        if (-1 == (pid = fork()))
            throw "Cannot fork process";
        if (!pid) {
            // We are the timer, so closing the parent end of the socketpair.
            close(fd[0]);

            // We send one byte every second to the parent process.
            while (true) {
                timespec less = base;
                int ret;

                // Continue sleeping after SIGWINCH but only for the time left.
                while (-1 == (ret = nanosleep(&less, &less)) and errno == EINTR and raised_SIGWINCH);

                // Maybe quit by user.
                if (raised_SIGINT)
                    return 0;

                // If something went wrong, terminate.
                if (-1 == ret)
                    throw "Cannot sleep";

                // Repeated writing if interrupted by SIGWINCH.
                char byte;
                ssize_t bytes;
                do {
                    // Doing the write.
                    bytes = write(fd[1], &byte, sizeof byte);
                }
                while (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);

                // Maybe quit by user.
                if (raised_SIGINT)
                    return 0;

                // If something went wrong, terminate.
                if (0 >= bytes)
                    throw "Cannot write";
            }

            // Here the timer process ends.
            return 0;
        }

        // We are the parent process, so closing the timer end of the socketpair.
        close(fd[1]);
    }
    catch (const char*& what) {
        // Print fatal error and terminate timer process causing parent process to terminate, too.
        std::cerr << what << std::endl;
        return 1;
    }

    // Initializing ncurses for the parent process.
    initscr();

    // Disable typing.
    noecho();

    // Disable cursor.
    curs_set(0);

    // Make reading characters non-blocking.
    nodelay(stdscr, TRUE);

    // Catch fatal errors.
    try {
        // Holds ticks alive.
        size_t ticks = 0;

        // Blockingly read the byte from the timer process awaiking us every second.
        while (true) {
            // Print ticks alive before incrementing them.
            print(ticks++);

            // Holds typed keys.
            std::string keys;

            // Read typed keys.
            for (int key = getch(); key != ERR; key = getch())
                if (key != KEY_RESIZE)
                    keys += key;

            // Format typed keys string.
            if (keys.size())
                printw("You've typed: ");
            else
                keys += "\n";
            keys += "\n\n";

            // Print typed keys string.
            printw(keys.c_str());

            // Doing the prints.
            refresh();

            // Repeated reading if interrupted by SIGWINCH.
            ssize_t bytes = 0;
            bool again = false;
            do {                    
                // Doing the read.
                char byte;
                bytes = read(fd[0], &byte, sizeof byte);
                again = (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);

                // Print how often we got interrupted by SIGWINCH per time base.
                if (again) {
                    // Next two calls are the common way to handle a SIGWINCH.
                    endwin();
                    refresh();

                    // For simpicity clear everything.
                    clear();

                    // Re-print ticks.
                    print(ticks);

                    // Print the interruption counter.
                    printw("%dx catched SIGWINCH per time base.\n\n", raised_SIGWINCH);

                    // Doing the prints.
                    refresh();
                }
            }
            while (again);

            // Reset SIGWINCH raises per time base.
            raised_SIGWINCH = 0;

            // Maybe quit by user.
            if (raised_SIGINT) {
                endwin();
                return 0;
            }

            // If something went wrong, terminate.
            if (0 >= bytes)
                throw "Cannot read";
        }
    }
    catch (const char*& what) {
        // We got an error, appending errno if set.
        std::string error(what);
        if (errno) {
            error.append(": ");
            error.append(strerror(errno));
        }
        error = "Catched exception: "+error+"\n";

        // Print the fatal error.
        printw(error.c_str());

        //Doing the print.
        refresh();

        // Waiting for CTRL+C to quit.
        while (true)
            nanosleep(&base, 0);

        // Quit by user.
        endwin();
        return 0;
    }
}
person user2876571    schedule 21.10.2013