20.10.2010Определение, запущен ли процесс
Пролог
Ого! Уже три месяца я ничего не писал в этот блог! Лето выдалось жаркое не только на погоду. Поскольку летом погода лучше, а световой день длиннее, было много работы. Причём работы связанной с поддержкой того, что уже и так нормально функционировало в прошлом сезоне. Ничего серьёзно нового не писалось активно, а значит и захватывающих сюжетов для статей не находилось.
Но теперь у меня появилась возможность писать кое-что новое. Поэтому есть, что рассказать.
Введение
Если вы любите процессы-демоны, как люблю их я, то, возможно, перед вами уже возникала задача определить, запущен ли уже такой демон, перед тем как создавать дочерний процесс. Об этом и будет сегодняшняя статья.
Баш в помощь
Предположим, что у нас есть простейший демон. Хорошо бы имя у него было уникальное, чтобы можно его потом было отыскать. Файл uniq_name_simple_daemon:
#!/usr/bin/env ruby
pid = fork do
begin
running = true
Signal.trap("TERM") do
running = false
end
while running
sleep 0.01
end
rescue Exception => e
puts e.to_s
puts e.backtrace.join "\n"
ensure
exit!
end
end
Мы всегда можем запускать с помощью другого скрипта, например на баше (simple_daemon_runner.sh):
#!/bin/bash
if ps ax | grep uniq_name_simple_daemon | grep -vq grep
then
echo "uniq_name_simple_daemon is already running"
else
echo "starting uniq_name_simple_daemon"
./uniq_name_simple_daemon
fi
На подобной команде будут базироваться все наши последующие методы. Тут, если кто не понял, мы фильтруем вывод ps ax сначала ища там имя нашего скрипта, а затем исключая из списка сам процесс поиска (команду grep). Ключ q позволяет нам получить код выхода, не выводя ничего на экран. То есть если строчка найдена, то запускаем первый блок, если нет, то второй.
Можно сделать такой же скрипт для остановки процесса (simple_daemon_stopper.sh):
#!/bin/bash
pid=$(ps ax | grep uniq_name_simple_daemon | grep -v grep | awk '{ print $1; }')
if [[ -n $pid ]]
then
echo "stopping uniq_name_simple_daemon"
kill -TERM $pid
else
echo "nothing to stop"
fi
Конечно же, при таком раскладе всегда есть возможность запустить нашего демона без помощи скриптов. И тогда проверка делаться не будет. В таком случае полезно проверять, запущен ли процесс уже внутри самого руби, перед тем, как отпочковать дочерний процесс.
Сам себе хозяин
В данном случае задача сводится к проверке наличия в памяти ещё одного процесса с таким же именем кроме текущего. Так же нужно уметь останавливать процесс с помощью того же файла. Вот, какое решение получилось у меня (uniq_name_auto_daemon):
#!/usr/bin/env ruby
ps_ax = `ps ax | grep #{File.basename(__FILE__)} | grep -v grep`.split("\n").map{ |l| l.strip.split(/\s+/) }.reject{ |l| l[0].to_i == Process.pid }
if ps_ax.any?
case ARGV[0]
when /stop/i
ps_ax.each do |l|
system "kill -TERM #{l[0]}"
end
when /kill/i
ps_ax.each do |l|
system "kill -KILL #{l[0]}"
end
else
puts "#{File.basename(__FILE__)} is already running. If you want to stop it, run './#{File.basename(__FILE__)} stop|kill'"
end
else
pid = fork do
begin
running = true
Signal.trap("TERM") do
running = false
end
while running
sleep 0.01
end
rescue Exception => e
puts e.to_s
puts e.backtrace.join "\n"
ensure
exit!
end
end
end
Во-первых, обходимся одним файлом, который никак иначе не запустить. Во-вторых, нигде не нужно хардкодить его имя. По-моему, очень удобно.
Оффтопик
С одной стороны, когда я пишу текст, то мне удобнее писать все термины по-русски и склонять их: «демоны», «руби», «баш», но с другой стороны это не поможет тому, кто будет искать решение похожей задачи.
Внутри примеров кода — наоборот, удобнее писать комментарии и тексты по-английски, чтобы не переключать раскладку, но как-то это не очень соответствует русскоязычном блогу.
Что же делать? :)