Linux таймаут для чтения () на именованном канале - PullRequest
1 голос
/ 25 марта 2020

Предположим, я создаю именованный канал в Linux системе:

$ mkfifo my_pipe

Следующее, что я хочу сделать, это написать небольшую программу монитора, которая пытается read() из my_pipe, но через некоторое время. В следующем псевдокоде я использовал вымышленную функцию wait_for_avail(fd, timeout_ms):

int fd = open("my_pipe", O_RDONLY);
while (1) {
    //Fictional wait_for_avail(fd, timeout_ms). Is there a real function
    //that has this behaviour?
    int rc = wait_for_avail(fd, 500);
    if (rc == 1) {
        char buf[64];
        read(fd, buf, 64);
        //do something with buf
    } else {
        fprintf(stderr, "Timed out while reading from my_pipe\n");
        //do something else in the program
    }
}

Я думал, poll с флагом POLLIN может работать, но это не так. Из моих простых испытаний я обнаружил, что он просто ждет, пока другой процесс не откроет именованный канал для записи (но не для данных, которые будут доступны, то есть read() не заблокирует). Кстати, по какой-то причине poll игнорирует ваш тайм-аут и, кажется, просто блокируется навсегда, пока другой процесс не открывает канал.

Единственное другое решение, которое я могу придумать, - это open() файл с O_NONBLOCK и как бы вручную наблюдать за временем, пока я постоянно пытаюсь read() с подсчетом 0 байт.

Есть ли лучшее решение там?

РЕДАКТИРОВАТЬ: У меня здесь есть блоки на открытие именованной трубы. Однако, если вы используете флаг O_NONBLOCK, файл откроется сразу. В этот момент poll() может использоваться для ожидания (с необязательным таймаутом), когда другой конец канала будет открыт для записи.

Однако, это все еще имеет поведение реализации тайм-аута для функция read(). Он по-прежнему блокируется, как только вы звоните read() (даже если канал был открыт с O_NONBLOCK)

Ответы [ 2 ]

1 голос
/ 25 марта 2020

Ваша идея об открытии fifo в неблокирующем режиме верна. Если вы сделаете это, poll() / select() / et c. может использоваться для ожидания открытия другого конца или для первого таймаута.

Следующая программа-пример просто работает в бесконечном l oop, ожидая, пока другие программы запишут в my_pipe, и выводит записанное текст, с периодическим обновлением статуса при отсутствии данных или записи:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  while (1) {
    int fd = open("my_pipe", O_RDONLY | O_NONBLOCK);
    if (fd < 0) {
      perror("open");
      return EXIT_FAILURE;
    }

    struct pollfd waiter = {.fd = fd, .events = POLLIN};

    while (1) {
      // 10 second timeout
      switch (poll(&waiter, 1, 10 * 1000)) {
      case 0:
        puts("The fifo timed out.");
        break;
      case 1:
        if (waiter.revents & POLLIN) {
          char buffer[BUFSIZ];
          ssize_t len = read(fd, buffer, sizeof buffer - 1);
          if (len < 0) {
            perror("read");
            return EXIT_FAILURE;
          }
          buffer[len] = '\0';
          printf("Read: %s\n", buffer);
        } else if (waiter.revents & POLLERR) {
          puts("Got a POLLERR");
          return EXIT_FAILURE;
        } else if (waiter.revents & POLLHUP) {
          // Writer closed its end
          goto closed;
        }
        break;
      default:
        perror("poll");
        return EXIT_FAILURE;
      }
    }
  closed:
    if (close(fd) < 0) {
      perror("close");
      return EXIT_FAILURE;
    }
  }
}
0 голосов
/ 25 марта 2020

После большой помощи и терпения от @Shawn мне удалось найти ответ, который я нашел удовлетворительным. Вот содержимое файла с именем pipe_watcher.c:

#include <stdio.h>  //printf etc.
#include <errno.h>  //errno
#include <string.h> //perror
#include <signal.h> //SIGALRM, sigaction, sigset
#include <time.h>   //timer_create, timer_settime
#include <fcntl.h>  //open, O_RDONLY
#include <unistd.h> //close

/* This code demonstrates how you can monitor a named pipe with timeouts on the
 * read() system call.
 * 
 * Compile with:
 * 
 *  gcc -o pipe_watcher pipe_watcher.c -lrt
 * 
 * And run with:
 * 
 *  ./pipe_watcher PIPE_FILENAME
*/

//Just needed a dummy handler
void sigalrm_handler(int s) {
    return;
}

int main(int argc, char **argv) {
    //Check input argument count
    if (argc != 2) {
        puts("Usage:\n");
        puts("\t./pipe_watcher PIPE_FILENAME");
        return -1;
    }

    //Create a timer object
    timer_t clk;
    int rc = timer_create(CLOCK_REALTIME, NULL, &clk);
    if (rc < 0) {
        perror("Could not create CLOCK_REALTIME timer");
        return -1;
    }

    //Create some time values for use with timer_settime
    struct itimerspec half_second = {
        .it_interval = {.tv_sec = 0, .tv_nsec = 0},
        .it_value = {.tv_sec = 0, .tv_nsec = 500000000}
    };

    struct itimerspec stop_timer = {
        .it_interval = {.tv_sec = 0, .tv_nsec = 0},
        .it_value = {.tv_sec = 0, .tv_nsec = 0}
    };

    //Set up SIGALRM handler
    struct sigaction sigalrm_act = {
        .sa_handler = sigalrm_handler,
        .sa_flags = 0
    };
    sigemptyset(&sigalrm_act.sa_mask);
    rc = sigaction(SIGALRM, &sigalrm_act, NULL);
    if (rc < 0) {
        perror("Could not register signal handler");
        timer_delete(clk);
        return -1;
    }

    //We deliberately omit O_NONBLOCK, since we want blocking behaviour on
    //read(), and we're willing to tolerate dealing with the blocking open()
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        char msg[80];
        sprintf(msg, "Could not open [%s]", argv[1]);
        perror(msg);
        timer_delete(clk);
        return -1;
    }

    puts("File opened");

    while (1) {
        //Buffer to read() into
        char buf[80];
        int len;

        //Set up a timer to interrupt the read() call after 0.5 seconds
        timer_settime(clk, 0, &half_second, NULL);

        //Issue read() system call
        len = read(fd, buf, 80);

        //Check for errors. The else-if checks for EOF
        if (len < 0) {
            if (errno == EINTR) {
                //This means we got interrupted by the timer; we can keep going
                fprintf(stderr, "Timeout, trying again\n");
                continue;
            } else {     
                //Something really bad happened. Time to quit.       
                perror("read() failed");
                //No point waiting for the timer anymore
                timer_settime(clk, 0, &stop_timer, NULL);
                break;
            }
        } else if (len == 0) {
            puts("Reached end of file");
            break;
        }

        //No error or EOF; stop the timer and print the results
        timer_settime(clk, 0, &stop_timer, NULL);
        write(STDOUT_FILENO, buf, len);
    }

    //Cleanup after ourselves
    timer_delete(clk);
    close(fd);
    return 0;
}

Техника заключается в установке таймера перед (блокированием) read() вызова. Затем мы можем просто проверить возвращаемое значение read(), чтобы увидеть, было ли оно прервано из-за тайм-аута, произошла ли общая ошибка, достигнут ли EOF или успешно прочитаны данные.

Есть только одна загвоздка: вы не можете открыть файл в неблокирующем режиме; это заставляет open() блокироваться, пока другой процесс не откроет канал для записи. Тем не менее, в моем приложении это на самом деле желательная функция. Вы также можете настроить SIGALRM для принудительного тайм-аута на open(), или, возможно, сделать это в другом потоке.

На самом деле, этот метод должен работать с любым другим системным вызовом, поэтому я мог бы собрать немного вспомогательная библиотека для упрощения использования этого шаблона.

EDIT

Еще одна вещь: очень важно не использовать флаг SA_RESTART при регистрации обработчика сигнала , В противном случае, даже если системный вызов прерван сигналом, Linux попытается повторить его после обработки сигнала.

...