2009-09-18 11 views
21

Estoy escribiendo una secuencia de comandos de Python que puede o no (durante un buen número de cosas) ejecutarse durante mucho tiempo, y me gustaría asegurarme de que varias instancias (iniciadas a través de cron) no pise cada una otros dedos de los pies La forma lógica de hacer esto parece ser un archivo de bloqueo basado en PID ... Pero no quiero reinventar la rueda si ya hay un código para hacerlo.Python: módulo para crear archivo de bloqueo basado en PID?

Entonces, ¿hay algún módulo de Python que administre los detalles de un archivo de bloqueo basado en PID?

+1

Mi respuesta aquí también podría ser de interés: [Utiliza conectores para crear un lo ck archivo que desaparece incluso si el proceso se envía un sigkill -] [1] [1]: http://stackoverflow.com/questions/788411/check-to-see-if-python- script-is-running/7758075 # 7758075 – aychedee

Respuesta

8

Si puede utilizar GPLv2, Mercurial tiene un módulo para que: el uso

http://bitbucket.org/mirror/mercurial/src/tip/mercurial/lock.py

Ejemplo:

from mercurial import error, lock 

try: 
    l = lock.lock("/path/to/lock", timeout=600) # wait at most 10 minutes 
    # do something 
except error.LockHeld: 
    # couldn't take the lock 
else: 
    l.release() 
+0

Gracias por todas las otras respuestas útiles, pero esta resultó ser la solución más simple, ya que la dependencia mercurial añadida no es un problema para mí (solo lo estoy usando para "poco" "scripts de utilidad". –

+0

Tenga en cuenta que esta respuesta no funciona con las versiones más recientes de la biblioteca mercurial (3.0.1 en el momento de la escritura); la clase 'lock' espera tanto' vfs' como 'file' args en init (' timeout' es opcional). – ropable

+0

El argumento 'vfs' se puede generar de la siguiente manera:' from mercurial import scmutil; vfs = scmutil.vfs ("/") '. Sin embargo, depender de un módulo interno de un producto más grande probablemente no sea una buena idea. –

4

I cree que encontrará la información necesaria here. La página en cuestión se refiere a un paquete para construir daemons en python: este proceso implica la creación de un archivo de bloqueo PID.

+0

Este módulo parece ser un contenedor sobre el módulo de archivo de bloqueo de biblioteca estándar de Python, que parece ser atómico para mí. –

+0

muchas gracias, absolutamente lo espero. – pylover

+0

Tiene un parche de dispersión sobre el github con este https://github.com/khertan/Khweeteur/blob/master/khweeteur/pydaemon/pidlockfile.py que es un código más reciente de Ben Finney. –

2

Hay un recipe on ActiveState on creating lockfiles.

Para generar el nombre de archivo puede usar os.getpid() para obtener el PID.

+1

La solución ActiveState no me parece atómica. Creo que es necesario crear el archivo de bloqueo con un nombre temporal, como "archivo de bloqueo. $ PID", escribir el PID en él y cambiar el nombre de "archivo de bloqueo. $ PID" a "archivo de bloqueo". Luego revise volviendo a leer el archivo de bloqueo para ver si tiene su PID. Esto es probablemente excesivo para muchos propósitos, pero es la forma más sólida. –

1

He estado muy contento con todos ellos, así que escribí esto:

class Pidfile(): 
    def __init__(self, path, log=sys.stdout.write, warn=sys.stderr.write): 
     self.pidfile = path 
     self.log = log 
     self.warn = warn 

    def __enter__(self): 
     try: 
      self.pidfd = os.open(self.pidfile, os.O_CREAT|os.O_WRONLY|os.O_EXCL) 
      self.log('locked pidfile %s' % self.pidfile) 
     except OSError as e: 
      if e.errno == errno.EEXIST: 
       pid = self._check() 
       if pid: 
        self.pidfd = None 
        raise ProcessRunningException('process already running in %s as pid %s' % (self.pidfile, pid)); 
       else: 
        os.remove(self.pidfile) 
        self.warn('removed staled lockfile %s' % (self.pidfile)) 
        self.pidfd = os.open(self.pidfile, os.O_CREAT|os.O_WRONLY|os.O_EXCL) 
      else: 
       raise 

     os.write(self.pidfd, str(os.getpid())) 
     os.close(self.pidfd) 
     return self 

    def __exit__(self, t, e, tb): 
     # return false to raise, true to pass 
     if t is None: 
      # normal condition, no exception 
      self._remove() 
      return True 
     elif t is PidfileProcessRunningException: 
      # do not remove the other process lockfile 
      return False 
     else: 
      # other exception 
      if self.pidfd: 
       # this was our lockfile, removing 
       self._remove() 
      return False 

    def _remove(self): 
     self.log('removed pidfile %s' % self.pidfile) 
     os.remove(self.pidfile) 

    def _check(self): 
     """check if a process is still running 

the process id is expected to be in pidfile, which should exist. 

if it is still running, returns the pid, if not, return False.""" 
     with open(self.pidfile, 'r') as f: 
      try: 
       pidstr = f.read() 
       pid = int(pidstr) 
      except ValueError: 
       # not an integer 
       self.log("not an integer: %s" % pidstr) 
       return False 
      try: 
       os.kill(pid, 0) 
      except OSError: 
       self.log("can't deliver signal to %s" % pid) 
       return False 
      else: 
       return pid 

class ProcessRunningException(BaseException): 
    pass 

para ser utilizado algo como esto:

try: 
    with Pidfile(args.pidfile): 
     process(args) 
except ProcessRunningException: 
    print "the pid file is in use, oops." 
1

Sé que esto es un hilo viejo, pero También creé un bloqueo simple que solo se basa en las bibliotecas nativas de Python:

import fcntl 
import errno 


class FileLock: 
    def __init__(self, filename=None): 
     self.filename = os.path.expanduser('~') + '/LOCK_FILE' if filename is None else filename 
     self.lock_file = open(self.filename, 'w+') 

    def unlock(self): 
     fcntl.flock(self.lock_file, fcntl.LOCK_UN) 

    def lock(self, maximum_wait=300): 
     waited = 0 
     while True: 
      try: 
       fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) 
       return True 
      except IOError as e: 
       if e.errno != errno.EAGAIN: 
        raise e 
       else: 
        time.sleep(1) 
        waited += 1 
        if waited >= maximum_wait: 
         return False 
Cuestiones relacionadas