LINUX.ORG.RU

Один экземпляр python-daemon

 ,


7

3

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

Для демонизации использую python-daemon из PEP-3143. В общем случае, демон выглядит так:

# -*- coding: utf-8 -*-
import os
import daemon
import time

PIDFILE='/tmp/foo.pid'

class App():
    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  PIDFILE
        self.pidfile_timeout = 5
    def run(self):
        while True:
            print "test..."
            time.sleep(10)

    
app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

Но, несмотря на pid-файл второй демон запускается спокойно.

Самое простое что пришло в голову - проверять os.path.exists(PIDFILE), а потом try os.kill(pidfile.read_pid(), 0) и ловить except OSError

Но, выглядит как-то костылевато.

Когда гуглил, нашел функцию http://www.thecodingforums.com/threads/single-instance-daemons.644188/

def single_instance(id):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    sock.bind('\0' + id)
    return sock

Но, ума не приложу как ей пользоваться, она у меня валится на sock.bind

Еще я нашел такую проверку (средствами самого python-daemon):

pidfile = daemon.pidfile.TimeoutPIDLockFile("/tmp/mydaemon.pid", -1)
if daemon.runner.is_pidfile_stale(pidfile):
Но она вообще непонятно как работает - файл /tmp/mydaemon.pid не создается и is_pidfile_stale всегда возвращает false.

Как правильно ограничивать количество копий процесса? Можете просто ткнуть в доки или бросить ссылкой.


Ответ на: комментарий от anonymous

Python-daemon из https://pypi.python.org/pypi/python-daemon тоже делает double-fork, всё по-пацански, как у Стивенса.

Я посмотрел Вами приведенную ссылку. Там ловится except OSError, но мне кажется, это немного похоже на костыль.

Вот бы понять, что как работает встроенная daemon.runner.is_pidfile_stale(pidfile) ....

JANB
() автор топика
import sys, fcntl, time
from contextlib import contextmanager

@contextmanager
def FileLock(fname):
    fp = open(fname, 'w')

    try:
        fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        print >>sys.stderr, 'Another instance already running'
        sys.exit(2)

    yield


with FileLock('/tmp/boo.lock'):
    print 'Foo'
    time.sleep(10)
bj
()
Ответ на: комментарий от bj

Спасибо, попробую!

А можете объяснить, какой смысл в pid-файлах, если его можно удалить? Просто не понимаю.

Вот идея с сокетом мне нравится (только у меня не получилось её повторить).

JANB
() автор топика
Ответ на: комментарий от JANB

Пид-файл это дешего и сердито. Нет никакого смысла изобретать железобетонные схемы для обычных систем.

bj
()

В общем, сделал как посоветовал bj, спасибо ему!

Но, вопрос про сокет остается актуальным - я не понимаю как использовать эту функцию:

def single_instance(id):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    sock.bind('\0' + id)
    return sock

Там на сайте парень даёт пояснение:

On linux (at least) there's one nice trick to get a single-instance program. Create a unix domain socket, and bind it to an address that begins with the null character '\0'. You can bind the same address a second time, and if the process dies the socket is automatically destroyed. It will not leave anything on the filesystem.

Вольный перевод:

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

Кто знает, как это можно использовать для запуска одной копии демона?

JANB
() автор топика

Кстати, о птичках. Зачем демонезировать средствами питона, когда для этого дела есть куча классных тулз? Можно, скажем, отталкиваться от доки по gunicorn-у ( http://gunicorn-docs.readthedocs.org/en/latest/deploy.html ), где есть и supervisord, и системные runit, systemd и upstart.

Вот убунтовый upstart:

description "myapp"

start on (filesystem)
stop on runlevel [016]

respawn
console log
setuid nobody
setgid nogroup
chdir /path/to/app/directory

exec /path/to/virtualenv/bin/gunicorn myapp:app

Просто, понятно. Если конфиг называется mydaemon.conf, то запуск start mydaemon, а остановка — stop mydaemon

Логи, единственность демона, перезапуск при падениях должны сработаться upstart-ом, для того он и сделан.

anonymous
()
Ответ на: комментарий от JANB
#!/bin/bash

function socket_instance() {
python - <<EOF
import socket
import time

def single_instance(id):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    sock.bind('\0'+id)
    return sock

print 'start...'
try:
    s = single_instance('123')
    time.sleep(3)
    print 'end...'
except:
    print 'already used'
EOF
}

for i in {0..5}; do
    echo $i
    socket_instance &
    sleep 1
done

sleep 6
$ ./run.sh
0
start...
1
start...
already used
2
start...
already used
3
start...
already used
end...
4
start...
5
start...
already used
end...
anonymous
()
Ответ на: комментарий от JANB

создайте сокет

Кто знает, как это можно использовать для запуска одной копии демона?

Очень просто. Биндишь порт, например (возможно, твой демон и так должен это делать). Если bind() фейлится с EBUSY, или как там - значит, данный процесс должен завершиться. В итоге, у тебя никогда не будет двух процессов демона, раз уж демон биндит некий порт.

Krieger_Od ★★
()
Ответ на: комментарий от anonymous

Ага... Спасибо! Теперь понятно!

Блин, круто.

JANB
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.