Ответ Жиля объясняет состояние гонки. Я просто собираюсь ответить на эту часть:
Есть ли способ заставить этот скрипт выводить всегда 0 строк (поэтому перенаправление ввода / вывода на tmp всегда готовится первым и поэтому данные всегда уничтожаются)? Чтобы было понятно, я имею в виду изменение настроек системы
IDK, если инструмент для этого уже существует, но у меня есть идея, как его можно реализовать. (Но обратите внимание, что это не всегда будет 0 строк, просто полезный тестер, который легко ловит простые гонки, подобные этой, и некоторые более сложные гонки. См. Комментарий @Gilles .) Это не гарантирует, что скрипт безопасен , но может быть полезным инструментом в тестировании, аналогично тестированию многопоточной программы на разных процессорах, включая слабо упорядоченные не x86-процессоры, такие как ARM.
Вы бы запустить его как racechecker bash foo.sh
Используйте тот же системный вызов трассировки / перехват объектов , которые strace -f
и ltrace -f
использовать для крепления к каждому дочернему процессу. (В Linux это тот же ptrace
системный вызов, который используется GDB и другими отладчиками для установки точек останова, одного шага и изменения памяти / регистров другого процесса.)
Инструмент open
и openat
системные вызовы: когда любой процесс , работающий в рамках этого инструмента делает системный вызов (или ) с , сном, может быть , 1/2 или 1 секунду. Пусть другие системные вызовы (особенно те, в том числе ) выполняются без задержки.open(2)
openat
O_RDONLY
open
O_TRUNC
Это должно позволить автору выиграть гонку почти во всех условиях гонки, если только нагрузка на систему не была высокой или это было сложное состояние гонки, когда усечение не происходило до некоторого другого чтения. Так что случайное изменение того, какие open()
s (и, возможно, read()
s или записи) задерживаются , увеличит мощность обнаружения этого инструмента, но, конечно, без тестирования в течение бесконечного количества времени с имитатором задержки, который в конечном итоге охватит все возможные ситуации, с которыми вы можете столкнуться в в реальном мире, вы не можете быть уверены, что ваши сценарии свободны от гонок, если вы не прочитаете их внимательно и докажете, что это не так.
Вы, вероятно , нужно, чтобы белый список (не задержка open
) для файлов /usr/bin
и /usr/lib
поэтому процесс-старте не принимает навсегда. (Динамическое связывание во время выполнения имеет отношение к open()
нескольким файлам (посмотрите strace -eopen /bin/true
или /bin/ls
когда-нибудь), хотя, если родительская оболочка сама выполняет усечение, это будет хорошо. Но для этого инструмента все равно будет хорошо не делать сценарии чрезмерно медленными).
Или, может быть, белый список каждого файла, который вызывающий процесс не имеет разрешения на обрезание. т.е. процесс трассировки может сделать access(2)
системный вызов, прежде чем фактически приостановить процесс, который хотел open()
файл.
racechecker
Само по себе должно быть написано на C, а не в оболочке, но, возможно, может использовать strace
код в качестве отправной точки и может не потребовать много работы для реализации.
Вы можете получить ту же функциональность с файловой системой FUSE . Вероятно, есть пример FUSE чистой сквозной файловой системы, так что вы можете добавить в open()
функцию проверки , которые переводят ее в спящий режим только для чтения, но позволяют усечению происходить сразу же.
racechecker
все время. И, возможно, вы захотите, чтобы время сна было открыто для чтения, чтобы его можно было настраивать для людей на очень сильно загруженных машинах, которые хотят установить его выше, например, на 10 секунд. Или установите его ниже, как 0,1 секунды для длительного или неэффективные сценарии , которые повторно открыть файлы лота .Почему существует состояние гонки?
Две стороны трубы выполняются параллельно, а не одна за другой. Есть очень простой способ продемонстрировать это: запустить
Это занимает одну секунду, а не две.
Оболочка запускает два дочерних процесса и ожидает завершения обоих. Эти два процесса выполняются параллельно: единственная причина, по которой один из них синхронизируется с другим, заключается в том, что ему нужно ждать другого. Наиболее распространенная точка синхронизации - это когда правая сторона блокирует ожидание чтения данных на своем стандартном входе и становится разблокированной, когда левая сторона записывает больше данных. Обратное также может произойти, когда правая сторона медленно читает данные, а левая сторона блокирует свою операцию записи, пока правая сторона не прочитает больше данных (в самом канале есть буфер, управляемый ядро, но оно имеет небольшой максимальный размер).
Для наблюдения за точкой синхронизации соблюдайте следующие команды (
sh -x
печатает каждую команду по мере ее выполнения):Играйте с вариациями, пока не освоитесь с тем, что вы наблюдаете.
Учитывая составную команду
левый процесс выполняет следующее (я только перечислил шаги, которые имеют отношение к моему объяснению):
cat
с аргументомtmp
.tmp
для чтения.Правый процесс выполняет следующие действия:
tmp
усечение файла в процессе.head
с аргументом-1
.Единственная точка синхронизации состоит в том, что right-3 ожидает, пока left-3 обработает одну полную строку. Синхронизация между left-2 и right-1 отсутствует, поэтому они могут происходить в любом порядке. В каком порядке они происходят, непредсказуемо: это зависит от архитектуры ЦП, от оболочки, от ядра, на каких ядрах планируются процессы, какие прерывания получает ЦП в это время и т. Д.
Как изменить поведение
Вы не можете изменить поведение, изменив настройки системы. Компьютер делает то, что вы говорите. Вы сказали ему обрезать
tmp
и читатьtmp
параллельно, поэтому он выполняет две вещи параллельно.Хорошо, есть один «системный параметр», который вы можете изменить: вы можете заменить
/bin/bash
его другой программой, отличной от bash. Я надеюсь, что само собой разумеется, что это не очень хорошая идея.Если вы хотите, чтобы усечение происходило до левой стороны канала, вам нужно поместить его вне конвейера, например:
или
Я понятия не имею, почему ты этого хочешь. Какой смысл читать из файла, который, как вы знаете, пуст?
И наоборот, если вы хотите, чтобы перенаправление вывода (включая усечение) происходило после
cat
завершения чтения, то вам необходимо либо полностью буферизовать данные в памяти, например:или запишите в другой файл, а затем переместите его на место. Обычно это надежный способ выполнения действий в сценариях, и его преимущество заключается в том, что файл записывается полностью, прежде чем он будет виден через исходное имя.
Коллекция moreutils включает в себя программу под названием
sponge
.Как автоматически определить проблему
Если вашей целью было взять плохо написанные сценарии и автоматически выяснить, где они ломаются, то извините, жизнь не так проста. Анализ во время выполнения не может надежно найти проблему, потому что иногда
cat
заканчивает чтение до того, как произойдет усечение. Статический анализ в принципе может это сделать; Shellcheck поймал упрощенный пример в вашем вопросе , но он может не уловить подобную проблему в более сложном скрипте.источник
strace
(например, Linuxptrace
), чтобыopen
системные вызовы для чтения (во всех дочерних процессах) спали в течение полсекунды, поэтому при гонке с усечение, усечение почти всегда побеждает.