Asegúrese de que solo se ejecute una instancia de un progtwig

¿Hay una forma Pythonic de tener solo una instancia de un progtwig ejecutándose?

La única solución razonable que he encontrado es intentar ejecutarlo como un servidor en algún puerto, luego el segundo progtwig que intenta enlazarse con el mismo puerto – falla. Pero no es realmente una gran idea, tal vez hay algo más ligero que esto?

(Tenga en cuenta que se espera que el progtwig falle algunas veces, es decir, segfault, por lo que cosas como “bloquear archivo” no funcionarán)

Actualización : las soluciones ofrecidas son mucho más complejas y menos confiables que el hecho de tener un puerto ocupado con un servidor inexistente, por lo que tendría que ir con ese.

El siguiente código debe hacer el trabajo, es multiplataforma y se ejecuta en Python 2.4-3.2. Lo probé en Windows, OS X y Linux.

from tendo import singleton me = singleton.SingleInstance() # will sys.exit(-1) if other instance is running 

La última versión del código está disponible singleton.py . Por favor, archiva los errores aquí .

Puedes instalarlo usando uno de los siguientes métodos:

Solución simple, multiplataforma , encontrada en otra pregunta por zgoda :

 import fcntl, sys pid_file = 'program.pid' fp = open(pid_file, 'w') try: fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: # another instance is running sys.exit(0) 

Se parece mucho a la sugerencia de S.Lott, pero con el código.

Este código es específico de Linux. Utiliza sockets de dominio UNIX ‘abstractos’, pero es simple y no deja archivos de locking obsoletos. Lo prefiero a la solución anterior porque no requiere un puerto TCP especialmente reservado.

 try: import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) ## Create an abstract socket, by prefixing it with null. s.bind( '\0postconnect_gateway_notify_lock') except socket.error as e: error_code = e.args[0] error_string = e.args[1] print "Process already running (%d:%s ). Exiting" % ( error_code, error_string) sys.exit (0) 

La cadena única postconnect_gateway_notify_lock se puede cambiar para permitir que varios progtwigs necesiten una sola instancia.

No sé si es lo suficientemente pythonic, pero en el mundo Java, escuchar en un puerto definido es una solución muy utilizada, ya que funciona en todas las plataformas principales y no tiene ningún problema con los progtwigs que fallan.

Otra ventaja de escuchar un puerto es que puede enviar un comando a la instancia en ejecución. Por ejemplo, cuando los usuarios inician el progtwig por segunda vez, puede enviar un comando a la instancia en ejecución para decirle que abra otra ventana (eso es lo que hace Firefox, por ejemplo. No sé si usan puertos TCP o canalizaciones con nombre o algo así, ‘aunque).

Utilice un archivo pid. Tienes una ubicación conocida, “/ path / to / pidfile” y al inicio haces algo como esto (parcialmente pseudocódigo porque soy pre-café y no quiero trabajar tanto):

 import os, os.path pidfilePath = """/path/to/pidfile""" if os.path.exists(pidfilePath): pidfile = open(pidfilePath,"r") pidString = pidfile.read() if : # something is real weird Sys.exit(BADCODE) else:  if : Sys.exit(ALREADAYRUNNING) else: # the previous server must have crashed   pidfile.write(os.getpid()) else:  pidfile.write(os.getpid()) 

Entonces, en otras palabras, está comprobando si existe un archivo pid; Si no, escribe tu pid a ese archivo. Si el archivo pid existe, entonces verifique si el pid es el pid de un proceso en ejecución; Si es así, entonces tienes otro proceso en vivo en ejecución, así que simplemente apaga. Si no, el proceso anterior se bloqueó, así que regístrelo y luego escriba su propio pid en el archivo en lugar del anterior. Entonces continúa.

Nunca escribí python antes, pero esto es lo que acabo de implementar en mycheckpoint, para evitar que se inicie dos veces o más con crond:

 import os import sys import fcntl fh=0 def run_once(): global fh fh=open(os.path.realpath(__file__),'r') try: fcntl.flock(fh,fcntl.LOCK_EX|fcntl.LOCK_NB) except: os._exit(0) run_once() 

Se encontró la sugerencia de Slava-N después de publicar esto en otro número (http://stackoverflow.com/questions/2959474). Este se llama como una función, bloquea el archivo de scripts en ejecución (no un archivo pid) y mantiene el locking hasta que finalice el script (normal o error).

Ya encontró la respuesta a una pregunta similar en otro hilo, así que, para completar, vea cómo lograr lo mismo en Windows uning mutex.

http://code.activestate.com/recipes/474070/

Esto puede funcionar.

  1. Intente crear un archivo PID en una ubicación conocida. Si fallas, alguien tiene el archivo bloqueado, listo.

  2. Cuando termine normalmente, cierre y elimine el archivo PID, para que otra persona pueda sobrescribirlo.

Puede envolver su progtwig en un script de shell que elimine el archivo PID incluso si su progtwig falla.

También puede usar el archivo PID para matar el progtwig si se bloquea.

Usar un archivo de locking es un enfoque bastante común en Unix. Si se bloquea, tienes que limpiar manualmente. Podría guardar el PID en el archivo y, al inicio, verificar si hay un proceso con este PID, anulando el archivo de locking si no es así. (Sin embargo, también necesita un locking alrededor del archivo read-file-check-pid-rewrite-file). Encontrará lo que necesita para obtener y verificar pid en el paquete os . La forma común de verificar si existe un proceso con un pid determinado es enviar una señal no fatal.

Otras alternativas podrían ser la combinación de esto con los semáforos de bandadas o posix.

La apertura de un socket de red, como propuso saua, probablemente sería la más fácil y la más portátil.

Para cualquier persona que use wxPython para su aplicación, puede usar la función wx.SingleInstanceChecker documentada aquí .

Personalmente, uso una subclase de wx.App que hace uso de wx.SingleInstanceChecker y devuelve False de OnInit() si hay una instancia existente de la aplicación que se está ejecutando de esta manera:

 import wx class SingleApp(wx.App): """ class that extends wx.App and only permits a single running instance. """ def OnInit(self): """ wx.App init function that returns False if the app is already running. """ self.name = "SingleApp-%s".format(wx.GetUserId()) self.instance = wx.SingleInstanceChecker(self.name) if self.instance.IsAnotherRunning(): wx.MessageBox( "An instance of the application is already running", "Error", wx.OK | wx.ICON_WARNING ) return False return True 

Este es un reemplazo wx.App para wx.App que prohíbe múltiples instancias. Para usarlo, simplemente reemplace wx.App con SingleApp en su código así:

 app = SingleApp(redirect=False) frame = wx.Frame(None, wx.ID_ANY, "Hello World") frame.Show(True) app.MainLoop() 

Estoy publicando esto como una respuesta porque soy un nuevo usuario y Stack Overflow no me deja votar todavía.

La solución de Sorin Sbarnea me funciona bajo OS X, Linux y Windows, y estoy agradecido por ello.

Sin embargo, tempfile.gettempdir () se comporta de un modo bajo OS X y Windows y otro bajo otro algunos / muchos / todos (?) * Nixes (ignorando el hecho de que OS X también es Unix!). La diferencia es importante para este código.

OS X y Windows tienen directorios temporales específicos del usuario, por lo que un archivo temporal creado por un usuario no es visible para otro usuario. Por el contrario, en muchas versiones de * nix (probé Ubuntu 9, RHEL 5, OpenSolaris 2008 y FreeBSD 8), el directorio temporal es / tmp para todos los usuarios.

Eso significa que cuando el archivo de locking se crea en una máquina multiusuario, se crea en / tmp y solo el usuario que crea el archivo de locking la primera vez podrá ejecutar la aplicación.

Una posible solución es incrustar el nombre de usuario actual en el nombre del archivo de locking.

Vale la pena señalar que la solución del OP de agarrar un puerto también se comportará mal en una máquina multiusuario.

Yo uso single_process en mi gentoo;

 pip install single_process 

ejemplo :

 from single_process import single_process @single_process def main(): print 1 if __name__ == "__main__": main() 

Consulte: https://pypi.python.org/pypi/single_process/1.0

Aquí está mi eventual solución solo para Windows. Coloque lo siguiente en un módulo, tal vez llamado ‘onlyone.py’, o lo que sea. Incluya ese módulo directamente en su __ main __ archivo de script de python.

 import win32event, win32api, winerror, time, sys, os main_path = os.path.abspath(sys.modules['__main__'].__file__).replace("\\", "/") first = True while True: mutex = win32event.CreateMutex(None, False, main_path + "_{}") if win32api.GetLastError() == 0: break win32api.CloseHandle(mutex) if first: print "Another instance of %s running, please wait for completion" % main_path first = False time.sleep(1) 

Explicación

El código intenta crear una exclusión mutua con el nombre derivado de la ruta completa al script. Utilizamos barras diagonales para evitar posibles confusiones con el sistema de archivos real.

Ventajas

  • No se necesitan identificadores de configuración o “mágicos”, utilícelos en tantos scripts diferentes como sea necesario.
  • No quedan archivos obsoletos, el mutex muere contigo.
  • Imprime un mensaje útil a la espera

ejemplo de linux

Este método se basa en la creación de un archivo temporal que se elimina automáticamente después de cerrar la aplicación. El lanzamiento del progtwig verificamos la existencia del archivo; si el archivo existe (hay una ejecución pendiente), el progtwig se cierra; De lo contrario, crea el archivo y continúa la ejecución del progtwig.

 from tempfile import * import time import os import sys f = NamedTemporaryFile( prefix='lock01_', delete=True) if not [f for f in os.listdir('/tmp') if f.find('lock01_')!=-1] else sys.exit() YOUR CODE COMES HERE 

Sigo sospechando que debería haber una buena solución POSIXy utilizando grupos de procesos, sin tener que golpear el sistema de archivos, pero no puedo concretarlo. Algo como:

En el inicio, su proceso envía un ‘kill -0’ a todos los procesos en un grupo en particular. Si alguno de tales procesos existe, sale. Entonces se une al grupo. Ningún otro proceso usa ese grupo.

Sin embargo, esto tiene una condición de carrera: todos los procesos pueden hacer esto exactamente al mismo tiempo y terminar uniéndose al grupo y ejecutándose simultáneamente. Cuando haya agregado algún tipo de exclusión mutua para que sea hermético, ya no necesita los grupos de procesos.

Esto podría ser aceptable si su proceso solo lo inicia cron, una vez por minuto o cada hora, pero me pone un poco nervioso que salga mal precisamente en el día en que no lo desee.

Supongo que esta no es una muy buena solución después de todo, a menos que alguien pueda mejorarla.

Me encontré con este problema exacto la semana pasada, y aunque encontré algunas buenas soluciones, decidí hacer un paquete de Python muy simple y limpio y lo subí a PyPI. Se diferencia de tendo en que puede bloquear cualquier nombre de recurso de cadena. Aunque ciertamente podrías bloquear __file__ para lograr el mismo efecto.

Instalar con: pip install quicklock

Su uso es extremadamente simple:

 [nate@Nates-MacBook-Pro-3 ~/live] python Python 2.7.6 (default, Sep 9 2014, 15:04:36) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from quicklock import singleton >>> # Let's create a lock so that only one instance of a script will run ... >>> singleton('hello world') >>> >>> # Let's try to do that again, this should fail ... >>> singleton('hello world') Traceback (most recent call last): File "", line 1, in  File "/Users/nate/live/gallery/env/lib/python2.7/site-packages/quicklock/quicklock.py", line 47, in singleton raise RuntimeError('Resource <{}> is currently locked by '.format(resource, other_process.pid, other_process.name())) RuntimeError: Resource  is currently locked by  >>> >>> # But if we quit this process, we release the lock automatically ... >>> ^D [nate@Nates-MacBook-Pro-3 ~/live] python Python 2.7.6 (default, Sep 9 2014, 15:04:36) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from quicklock import singleton >>> singleton('hello world') >>> >>> # No exception was thrown, we own 'hello world'! 

Eche un vistazo: https://pypi.python.org/pypi/quicklock

En un sistema Linux, también se puede pedir a pgrep -a el número de instancias; el script se encuentra en la lista de procesos (la opción -a revela la cadena completa de la línea de comandos). P.ej

 import os import sys import subprocess procOut = subprocess.check_output( "/bin/pgrep -u $UID -a python", shell=True, executable="/bin/bash", universal_newlines=True) if procOut.count( os.path.basename(__file__)) > 1 : sys.exit( ("found another instance of >{}<, quitting." ).format( os.path.basename(__file__))) 

Elimine -u $UID si la restricción debe aplicarse a todos los usuarios. Descargo de responsabilidad: a) se supone que el nombre del script (base) es único, b) puede haber condiciones de carrera.

La mejor solución para esto en Windows es usar mutexes como lo sugiere @zgoda.

 import win32event import win32api from winerror import ERROR_ALREADY_EXISTS mutex = win32event.CreateMutex(None, False, 'name') last_error = win32api.GetLastError() if last_error == ERROR_ALREADY_EXISTS: print("App instance already running") 

Algunas respuestas usan fctnl (incluido también en el paquete @sorin tendo) que no está disponible en Windows y, si intenta congelar su aplicación de Python utilizando un paquete como pyinstaller que realiza importaciones estáticas, se produce un error.

Además, al usar el método de locking de archivos, se crea un problema de read-only con los archivos de la base de datos (esto lo experimenté con sqlite3 ).

 import sys,os # start program try: # (1) os.unlink('lock') # (2) fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (3) except: try: fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (4) except: print "Another Program running !.." # (5) sys.exit() # your program ... # ... # exit program try: os.close(fd) # (6) except: pass try: os.unlink('lock') except: pass sys.exit()