Авторизация

Сегодня:Сегодня:67
Всего: Всего: 605357
 


Приветствую вас, дорогие друзья! Иногда требуется прерваться. Например, в том случае, если какая-то программа, вызванная к жизни из недр вашего скрипта вдруг подвисает и отказывается завершать свою работу в ожидаемые разумные сроки. Или если по каким-либо случайным причинам родной до боли код в ходе тестирования вдруг "подвешивает" скрипт в бесконечном цикле.

При возникновении подобного рода проблем не очень грамотно написанные скрипты попросту "замрут", создав возможно маленькие, а возможно и очень серьёзные сбои в работе системы.

Что же нужно для того, чтобы написать свой код правильно, с учётом периодов разумного ожидания завершения (таймаутов в английском варианте :)) отдельных участков?

BASH-программистам (не побоюсь этого слова), которые пользуются стандартными утилитами проекта GNU, нужно знать по крайней мере две ключевых команды, позволяющих так или иначе вывести ваш код из состояния зимней спячки: at и timeout.  BASH-программистам-полиглотам, которые пишут под любые системы, необходимо ещё знать команду doalarm.

Утилита timeout в соответствии со своиv названием предназначена только для завершения по истечению определённого интервала времени целого скрипта. at - гораздо более гибкая (хотя и есть у неё существенный недостаток, о чём - ниже), так что о ней мы и поговорим, оставив обсуждение timeout и её близкого родственника doalarm до выхода расширенного и дополненного варианта этой статьи.

Если вы не сталкивались с этой командой раньше, то будете удивлены тому, что at вообще-то сама по себе ничего не делает! Она всего лишь выполняет функции регистратора, принимающего заявки, формирующего соотв. "задания" и передающего их исполнительной части - процессу-демону atd, - с указанием времени начала выполнения.

Демон atd периодически просматривает очередь порученных ему заданий и выполняет их в нужный момент, после чего удаляет соотв. записи из очереди поручений регистратора. Текст заданий можно передавать команде at либо в потоке стандартного ввода stdin, либо через файл.

Последний метод подходит лишь для достаточно специфических случаев, в нашем случае будем использовать stdin. Указать время выполнения команды можно также различными способами (см. man at), но дело в том, что в общем случае мы не можем знать, когда и во сколько будет запущен наш скрипт, поэтому можем оперировать лишь относительными величинами, показывающими, сколько времени должно пройти с момента обращения к команде at для того, чтобы нужное нам задание выполнилось.

Для этого, к счастью, не требуется в коде скрипта вычислять даты самому (при помощи date), а можно просто указать тот самый "момент X" в формате now+N, где now - указывает на необходимость взять текущий момент за точку отсчёта, а +N - на то, сколько минут, часов, дней, недель и т.д., следует "отсчитать".

Обратите внимание, на то, что минимальной дискретной единицей времени для демона atd является одна минута. На мой взгляд, это один из главных недостатков данного ПО, так же, собственно, как и его близкого родственника - планировщика Cron. Ведь одна минута... много это или мало?

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

Как же пользоваться at в нашем случае? Дабы не растекаться мыслью по древу, приведу один наглядный пример:

#!/bin/bash

# Запоминаем PID процесса
slfPID=$$

# Просим atd послать нашему процессу сигнал
# (по умолчанию, если тип сигнала не указан, это должен быть TERM)
# время отправки сигнала - через 1 минуту
at now+1min <<<"/bin/kill $slfPID"

# Входим в бесконечный цикл и ждём, когда нас "завершат" извне
while :; do
sleep 1
done

 

Из комментариев надеюсь, понятно, что делает скрипт. Конструкция "<<<" позволяет передавать в stdin текст, не пользуясь более медленной конструкцией "echo SOME_TEXT |" (и аналогичными). Запоминать значение PID процесса в переменной не обязательно, это просто здравый расчёт на то, что данное значение нам ещё понадобиться при дальнешей доработке скрипта, да и просто иметь дело с "говорящими" переменными приятнее. Обратите особое внимание, что пути к исполняемым файлам в задании для atd должны указываться явным образом!

Представленный код работает "идеально", если скрипт завершается нештатно извне, но если "всё идёт по плану", то он, к сожалению, оставит за собой немытую посуду и ошмётки грязи в прихожей, то есть - оставит задание для ATD, которое никогда не сможет быть выполнено.

Ещё хуже, если задание не будет отменено при нормальной работе кода, в этом случае вы получите "нож в спину" в виде нежданно-негаданного сигнала TERM тогда, когда этого совсем не ждёте.

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

#!/bin/bash

# Запоминаем PID процесса
slfPID=$$

# Просим atd послать нашему процессу сигнал
# (по умолчанию, если тип сигнала не указан, это должен быть TERM)
# время отправки сигнала - через 1 минуту
# "Запомним" идентификатор задания в переменной jobID
jobID=$(at now+1min 2>&1 <<<"/bin/kill $slfPID" | sed -nr 's/^job\s+([0-9]+)\s+at.+$/\1/p')

# Здесь участок кода, который может "подвиснуть".
# В данном случае мы просто задаём каверзный вопрос
# при помощи утилиты dialog
dialog --yesno 'Ты успеешь меня закрыть?' 5 28

# Прибираемся за собой, если мы всё-таки оказались здесь,
# а значит, работа скрипта продолжается:
atrm $jobID 2>/dev/null

 

А что, если нам нужно не просто завершить работу скрипта, но ещё и обработать исключительную ситуацию его нештатной работы? Всё очень просто, посылайте скрипту командной kill определённый сигнал, предварительно перехватывая его, для этого добавьте после строки #!/bin/bash следующие строки:

sigTimeout='SIGHUP'
trap doHndlTimeout $sigTimeout
doHndlTimeOut () {
# Здесь процедура обработчика, например:
dialog --msgbox 'Произошёл таймаут, вы не успели!' 6 24
}

 

А команде kill передайте параметр "-s $sigTimeout".
Вуаля! Теперь вы можете использовать полученные знания для переписывания зачастую весьма корявых скриптов начальной загрузки с целью постичь Дзен, а также кардинально улучшить свой любимый   дистрибутив, или для того, чтобы просто в ходе особо длительных операций наподобие копирования жёсткого диска по сети рассказывать своим пользователям анекдоты.

А теперь даю установку: если вам понравилась эта статься, отпишитесь на форуме и ждите следующую, идея которой уже есть, тестовый код тоже, осталось только понять, что ж я накодил и описать это доступным языком.

Да пребудет с вами Сила!

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


Защитный код
Обновить


МЫ Вконтакте и в Телеграм

Наша страница Вконтакте  Наш канал в Телеграм

Наши услуги

Установка и настройка серверов на основе Unix;
Консультируем по выбору конфигурации ПК;
Лечим телефоны и ПК от вирусов;
Прошивка телефонов, установка Windows
(с вашего дистрибутива)
Оформление и оплата услуг - на основе договора
с нами как юридическим лицом (ИП)

Контакты

Контакты владельцев сайта:
Страница DRVTiny: ссылка
Страница DMS: ссылка
Электропочта: тыц!!!
Адрес канала сайта на Яндекс-Дзен: 
ссылка

Индекс качества сайта