29 gennaio, 2007

Demoni in python

In questo post voglio proporvi un metodo più o meno standard per scrivere processi demoni in python. Un processo demone (per chi non lo sapesse), è un processo che di solito è in esecuzione nel sistema per fornire dei servizi su richiesta all'utente o ad altri processi. Il metodo standard per demonizzare un processo, consiste nell'usare due fork (si legge in giro che due fork garantiscono la correttezza del funzionamento su tutti i sistemi unix). La fork è una chiamata di sistema unix che crea in memoria una copia esatta del processo chiamante e restituisce il pid (process id) del nuovo processo al "genitore" e zero al figlio. Andiamo a vedere il codice:


import os
import sys

class Log:
def __init__(self, logfile):
self.logfile = logfile

def write(self, data):
self.logfile.write(data)
self.logfile.flush()

def daemonize(logfile):
"""
Questa funzione fara' diventare demone il processo che la richiamera'
e redirigera' il suo standard output e standard error sul file logfile
"""
# La maschera' con la quale verranno creati gli eventuali nuovi
# files
UMASK = 0

try:
pid = os.fork()
except OSError, e:
raise Exception, "la prima fork e' fallita: %d (%s)" % \
(e.errno, e.strerror)

if (pid == 0):
os.setsid()
try:
pid = os.fork()
except OSError, e:
raise Exception, "la seconda fork e' fallita: %d (%s)" % \
(e.errno, e.strerror)

if (pid == 0):
os.chdir('/')
os.umask(UMASK)
else:
os._exit(0)
else:
os._exit(0)

sys.stdin.close()
# Redirigo standard output ed error sul log file
try:
sys.stdout = sys.stderr = Log(open(logfile, 'a+'))
except IOError, details:
raise IOError, details

return 0

Un paio di punti da notare:
  • Il secondo figlio imposta come sua directory corrente la root ("/")
  • Lo stdin viene chiuso

Per quanto riguarda il primo punto, ciò viene fatto per prevenire situazioni anomale nelle quali un filesystem non può essere smontato in quanto c'è un processo che lo impedisce. Infatti avrete notato che se apriamo una shell e ci spostiamo con il comando cd in una directory sotto un filesystem montato quest'ultimo non potrà essere smontato finché la nostra shell non "smette" di impegnarlo. Il secondo punto è un po più ovvio: un demone non deve ricevere dati dallo standard input. Probabilmente se dovrà ricevere dati userà metodi differenti, come ad esempio i sockets.

Esempio d'uso


Possiamo salvare il codice appena visto in un modulo python che chiamiamo ad esempio daemon.py per poi richiamarlo in questo modo:

from daemon import daemonize

daemonize("/var/log/mylog.log")

import time
i = 0
while 1:
print i
time.sleep(1)
i = i + 1

Se salviamo questo semplice script in, ad esempio, test.py e lo eseguiamo (da root se vogliamo usare /var/log/mylog.log oppure possiamo cambiare file di log e usare un percorso scrivibile dall'utente non privilegiato, ad esempio la nostra home), quello che otterremo sarà un bel demone in esecuzione in background che ogni secondo andrà a scrivere sul nostro file di log il valore della variabile i. Il demone rimarrà in esecuzione fin quando non lo andremo a uccidere. Per farlo dobbiamo scoprire il suo pid con ps x (l'opzione x è necessaria per vedere i parametri passati ai processi), troviamo qualcosa di simile a 65100 python test.py e lo uccidiamo con kill 65100 (sostituire 65100 con il vostro pid.

2 commenti:

  1. Ciao, interessante il tuo tutorial sul demone, "peccato" che sia per UNIX (a proposito funziona quindi anche su MacOSX?).
    Dico peccato perché al momento uso solo Windows XP (anche se ho installato anche Ubuntu 7.04). Hai qualche info su come farlo su Win?

    Cmq ora o a Natale mi prenderò finalmente un MacBook e allora finalmente ne vedremo delle belle :o!!

    RispondiElimina
  2. mi dispiace ma la fork è una chiamata di sistema solo unix. Quindi non funzionerà su windows.

    Cambia sistema operativo ;)

    RispondiElimina