Могу ли я использовать именованные каналы для достижения временной декуплирования?

Вопрос или проблема

У меня есть 2 приложения, которые обмениваются данными:

приложение1 | приложение2

В основном, приложение1 генерирует лог с событиями, которые обрабатывает приложение2. Проблема в том, что я часто обновляю приложение2. То есть, мне нужно остановить их, обновить бинарные файлы и перезапустить. В этот небольшой промежуток времени приложение1 может пропустить данные.

Я прочитал о использовании именованных конвейеров с помощью mkfifo и подумал, что это может быть идеальным решением. Держать приложение1 работающим и заставить его записывать в файл с именованным конвейером, чтобы при обновлении приложения не потерять данные, и как только приложение2 запустится, оно получит данные.

Тестируя это с помощью cat для имитации читателя/писателя, все работало до тех пор, пока не оставался читатель. Это было неожиданно.

Альтернативой было бы использование обычного файла, но это имеет свои проблемы:

  • Он остается на диске и не ведет себя как FIFO
  • Требуется какая-то форма ротации, чтобы избежать роста файлов до огромных размеров
  • Насколько я знаю, когда читатель находится в конце (в хвосте?), ему придется опрашивать по таймеру, увеличился ли размер файла, что увеличивает задержку обработки

Я контролирую читателя, текущее поведение уже автоматически перезапускается, но я не контролирую писателя. Я могу только направить его вывод куда-то еще.

  1. Можно ли настроить именованные конвейеры так, чтобы они были устойчивыми?
  2. Я читал о “закреплении” конвейера писателем, но мне не удалось это сделать
  3. Могу ли я предотвратить закрытие конвейера после выхода читателя?
  4. Есть ли альтернативы, которые ведут себя как конвейер?

Решение https://unix.stackexchange.com/a/784153/585995 работает!

mkfifo /tmp/mypipe
writerapp 1<>/tmp/mypipe

Писатель продолжает работать, пока я перезапускаю читателя:

readerapp </tmp/mypipe

Кстати, вы можете протестировать это сами, используя cat

Вы можете рассматривать именованные конвейеры как своего рода якоря для создания обычных конвейеров.

При:

application1 > fifo

Оболочка выполняет open("fifo", O_WRONLY). Этот open() блокируется, пока другой процесс (или тот же процесс) не выполнит open("fifo", O_RDONLY)¹:

application2 < fifo

В этот момент создается конвейер (очень похожий на неназванные, создаваемые с помощью pipe()).

Если application2 завершится, конец чтения этого конвейера закроется, он станет сломанным конвейером, и application1 получит SIGPIPE в следующий раз, когда попытается записать в него.

Чтобы избежать этого, вам нужно убедиться, что конвейер остается активным, например, имея другой процесс, который открыл дескриптор для чтения с конвейера, или вы можете заставить application1 открыть fifo в режиме O_RDWR¹, чтобы он стал как писателем, так и читателем этого конвейера, даже если он не собирается фактически читать с него.

С:

application1 1<> fifo

Stdout открывается в O_RDWR и конвейер немедленно создается. application1 начнет записывать в конвейер, пока буфер конвейера не заполнится. Если захочет, application1 может прочитать из своего дескриптора stdout и использовать то, что он записал ранее, но приложения обычно не читают из своего stdout.

Затем, вы делаете:

application2 < fifo

application2 станет другим читателем этого конвейера. Если он завершится, конвейер не станет сломанным, потому что к нему все еще есть читатель (application1), и вы можете запустить новую версию application2 как: application2 < fifo, которая продолжит с места, на котором остановился предыдущий запуск.

С неназванными конвейерами вы также могли бы сделать что-то вроде:

./application1 |
  while ./application2; do
    until [ -f new-application2 ]; do
      sleep 1
    done
    mv new-application2 application2 || break
  done

Где вы будете предоставлять новые версии вашего application2 как new-application2², который автоматически переименовывается и запускается в цикле.

Цикл останавливается, когда либо application2, либо mv завершаются, в этот момент конвейер станет сломанным, и application1 получит SIGPIPE в следующий раз, когда попытается записать в него.


¹ Если я правильно помню, это не работает на всех Unix, но я не могу сказать, на каких именно.

² Вы хотите убедиться, что new-application2 появляется целиком (например, переименовав его из предыдущего имени/местоположения), чтобы избежать риска его исполнения, пока он не будет полностью создан.

Можно ли настроить именованные конвейеры так, чтобы они были устойчивыми?

В зависимости от вашего определения устойчивого, да. mkfifo создает fifo или именованный конвейер, и он остается там, пока вы его не удалите.

Я читал о “закреплении” конвейера писателем, но мне не удалось это сделать

Не уверен, что вы имеете в виду. Однако, если вы не контролируете писателя, это не будет решением.

Могу ли я предотвратить закрытие конвейера после выхода читателя?

Вы можете использовать tail -f на fifo, вместо cat. Или даже tail -n +1 -f имя_fifo | команда

Некоторая дополнительная информация: когда cat встречает конец файла, он выходит. Поэтому, если вы сделаете

$ mkfifo fifootje
$ cat fifootje

и в другом терминале

echo hop > fifootje

вы получите hop от cat, и cat выйдет. Если ваш писатель затем сделает

$ echo plop > fifootje

он будет зависать, пока не будет выдана новая команда cat fifootje.

tail -f (“следить” или “навсегда”) просто ждет, пока появятся новые строки, и не выйдет, пока его не убьют. Так,

$ tail -f fifootje
hop
plop
и еще текст
введенный через cat
^C

и в терминале писателя:

$ echo hop > fifootje 
ljm@verlaine[/tmp]$ echo plop > fifootje 
ljm@verlaine[/tmp]$ cat > fifootje
и еще текст
введенный через cat
^D

В качестве альтернативы, вы могли бы:

while : ; do
    cat fifootje
done

Есть ли альтернативы, которые ведут себя как конвейер?

Да, сокеты.

Ответ или решение

Использование именованных каналов (named pipes) может быть эффективным решением для обеспечения временной декомплетации между двумя приложениями, как в вашем случае. Позвольте мне более подробно рассмотреть ваши вопросы и предложить возможные решения.

1. Можно ли настроить именованные каналы так, чтобы они были устойчивыми?

Да, если под устойчивостью вы имеете в виду, что именованный канал будет существовать до тех пор, пока вы его не удалите, то это возможно. Вы создаете именованный канал с помощью mkfifo, и он будет храниться в файловой системе, пока вы его не удалите командой rm <имя_канала>. Тем не менее, имейте в виду, что если приложение, записывающее данные в канал, закрывает свою запись, то это может привести к возникновению ошибки записи (SIGPIPE) при попытке записи в несуществующий конец канала.

2. Я слышал о «припине» канала писателем, но не смог это реализовать.

Если вы не контролируете писатель, прямое «прилипание» не получится. Однако вы можете использовать комбинацию O_RDWR для открытия именованного канала, позволяя вашим приложениям одновременно писать и читать из него. Например, при открытии канала в режиме O_RDWR, писатель сможет продолжать записи даже после закрытия чтения вторым приложением.

3. Можно ли предотвратить закрытие канала после выхода читателя?

Для того чтобы избежать закрытия канала после выхода читателя, вы можете использовать tail -f <канал>, что позволяет следить за данными, поступающими в канал, и не завершать процесс. Это предотвращает закрытие процесса, когда читатель (например, cat) завершится. Как уже упоминалось, использование cat непосредственно заканчивается при достижении EOF, а tail -f будет ожидать новые данные бесконечно.

Также можно использовать цикл, чтобы непрерывно считывать данные из канала:

while true; do
    cat <name_of_fifo
done

4. Есть ли альтернативы, которые ведут себя как канал?

Да, вы можете рассмотреть использование сокетов как альтернатива. Сокеты могут быть настроены для работы между процессами на одном или разных машинах и обеспечивают более гибкое взаимодействие. Они также могут быть использованы для создания серверов и клиентов, что может быть полезно, если вам нужно будет масштабировать ваши приложения в будущем.

Заключение

Итак, использование именованных каналов действительно может помочь решить вашу проблему временной декомплетации между application1 и application2. Важно правильно организовать логику работы с именованными каналами и использовать такие подходы, как открытие в режиме O_RDWR, работа с tail -f или использование циклов для продолжительного считывания. Кроме того, рассмотрите возможность применения сокетов для более сложных сценариев. Если у вас есть дополнительные вопросы или потребуется помощь в реализации, не стесняйтесь обращаться.

Оцените материал
Добавить комментарий

Капча загружается...