2008-10-21 11 views
30

Estoy trabajando en una aplicación GUI en WxPython, y no estoy seguro de cómo puedo asegurar que solo se ejecute una copia de mi aplicación en un momento dado en la máquina. Debido a la naturaleza de la aplicación, ejecutar más de una vez no tiene sentido y fallará rápidamente. En Win32, puedo simplemente crear un mutex con nombre y verificarlo al inicio. Desafortunadamente, no conozco ninguna instalación en Linux que pueda hacer esto.Asegurar una sola instancia de una aplicación en Linux

Estoy buscando algo que se liberará automáticamente si la aplicación se bloquea inesperadamente. No quiero tener que cargar a mis usuarios con tener que eliminar manualmente los archivos de bloqueo porque me he caído.

Respuesta

23

Existen varias técnicas comunes que incluyen el uso de semáforos. El que veo más utilizado es crear un "archivo de bloqueo pid" en el inicio que contiene el pid del proceso en ejecución. Si el archivo ya existe cuando se inicia el programa, ábralo y agarre el pid interno, verifique si se está ejecutando un proceso con ese pid, si se trata de verificar el valor de la línea de comando en/proc/pid para ver si es una instancia de su programa, si se cierra, de lo contrario sobrescriba el archivo con su pid. El nombre habitual para el archivo pid es application_name.pid.

+1

Por convención, esto debería ir bajo/var/run /, ¿correcto? –

+0

Por curiosidad, ¿no simplemente abrir el archivo para acceso exclusivo hace el trabajo de un mutex? – Menkboy

+0

Menkboy, si el archivo se cierra correctamente en caso de un bloqueo, entonces creo que funcionará perfectamente y simplificará aún más las cosas. Gracias. –

0

Si crea un archivo de bloqueo y coloca el pid en él, puede verificar el ID de su proceso y decir si se bloqueó, ¿no?

No he hecho esto personalmente, así que tómelo con las cantidades adecuadas de sal. : p

+2

utilizando el archivo PID es común, pero no está exenta de problemas. Por un lado, puede haber condiciones de carrera. En segundo lugar, es posible que no se limpie si se mata la aplicación. –

0

¿Se puede utilizar la utilidad 'pidof'? Si su aplicación se está ejecutando, pidof escribirá el ID de proceso de su aplicación en stdout. De lo contrario, imprimirá una nueva línea (LF) y devolverá un código de error.

Ejemplo (de golpe, por simplicidad):

linux# pidof myapp 
8947 
linux# pidof nonexistent_app 

linux# 
+0

Esto es ordenado, se requiere menos código que el método de archivo de bloqueo. Lo malo es que no es exactamente atómico, pero para detección de instancia única, eso puede no ser necesario. –

+0

Esto no funcionará si está ejecutando "aplicación pidof" desde el interior de la aplicación – MattSmith

+3

. Tampoco funcionará si hay un programa en ejecución diferente con el mismo nombre o si otro usuario también está ejecutando el programa. – CesarB

0

Con mucho, el método más común es dejar caer un archivo en/var/run/llamado [Aplicación] .pid que contiene sólo el PID del proceso en ejecución, o proceso principal. Como alternativa, puede crear un conducto con nombre en el mismo directorio para poder enviar mensajes al proceso activo, p. para abrir un nuevo archivo

+0

El uso del archivo PID es común, pero no está exento de problemas. Por un lado, puede haber condiciones de carrera. En segundo lugar, es posible que no se limpie si se mata la aplicación. –

1

Busque un módulo python que interactúe con los semáforos SYSV en Unix. Los semáforos tienen una bandera SEM_UNDO que hará que se liberen los recursos mantenidos por el proceso a si el proceso falla.

De lo contrario como se sugiere Bernard, puede utilizar

import os 
os.getpid() 

Y escribirlo en/var/run/application_name .pid. Cuando se inicia el proceso, debe verificar si el pid en/var/run/nombre_aplicación .pid aparece en la tabla ps y salir si lo está; de lo contrario, escriba su propio pid en/var/run/application_name .pid . En la siguiente var_run_pid es el pid se lee de/var/run/application_name .pid

cmd = "ps -p %s -o comm=" % var_run_pid 
app_name = os.popen(cmd).read().strip() 
if len(app_name) > 0: 
    Already running 
+0

+1 para sugerir semáforos SYSV, -1 para sugerir llamar 'ps' en lugar de algo más eficiente (por ejemplo,' kill -0' - a través de la señal de llamada en lugar del comando si se prefiere evitar un fork/exec extra) y sugiriendo pidfiles sin bloqueo de aviso (es decir, rebaño) para evitar condiciones de carrera y colisiones de PID. –

58

es lo correcto bloqueo de asesoramiento utilizando flock(LOCK_EX); en Python, esto se encuentra en el fcntl module.

A diferencia pidfiles, estos bloqueos se liberan de forma automática cuando el proceso muere por cualquier razón, no tienen condiciones de carrera existen en relación con la eliminación de archivos (como el archivo no se necesidad a borrar para liberar el bloqueo), y no hay posibilidad de que un proceso diferente herede el PID y parezca que valide un bloqueo obsoleto.

Si desea una detección de cierre defectuosa, puede escribir un marcador (como su PID, para los tradicionalistas) en el archivo después de agarrar el bloqueo, y luego truncar el archivo al estado de 0 bytes antes de un apagado limpio (mientras la cerradura está siendo retenida); por lo tanto, si no se mantiene el bloqueo y el archivo no está vacío, se indica un cierre no limpio.

+1

Estoy de acuerdo, este es probablemente el mejor método en la mayoría de los casos. –

+1

Este es el método correcto para bloquear, sin embargo también es bueno escribir un archivo PID que se limpia en la salida normal, así como también crear una entrada en/var/lock/subsys (si existe). Esto le permite a su programa darse cuenta si se reinicia desde un bloqueo, entre otras cosas. Entonces, hacer ambas cosas ayuda. –

+0

@tinkertim - No es una mala sugerencia, aunque tiene sentido agrupar() el archivo pid en lugar de tener más de uno. –

24

solución de bloqueo y completa con el módulo fcntl:

import fcntl 
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(1) 
+2

Suponiendo que el archivo de bloqueo es el mismo para todos los usuarios, como debería ser para que el bloqueo sea útil, esto puede crear un problema de permisos de escritura. He descrito y abordado este problema en una respuesta. –

+0

No sé por qué, pero esto no funciona en Python 3.4.1. Dos instancias se ejecutan sin ningún error. – boreq

+0

@boreq, ¿podría ser un poco más explícito sobre exactamente cómo está probando (sistema de archivos en uso, comportamiento esperado, comportamiento real, etc.)? –

8

wxWidgets ofrece una clase wxSingleInstanceChecker para este propósito: wxPython doc, o wxWidgets doc. El documento wxWidgets tiene código de ejemplo en C++, pero el pitón equivalente debería ser algo como esto (no probado):

name = "MyApp-%s" % wx.GetUserId() 
    checker = wx.SingleInstanceChecker(name) 
    if checker.IsAnotherRunning(): 
     return False 
0

He hecho un marco básico para el funcionamiento de este tipo de aplicaciones cuando se desea ser capaz de pasar los argumentos de línea de comando de las instancias subsiguientes intentadas al primero. Una instancia comenzará a escuchar en un puerto predefinido si no encuentra una instancia que ya esté escuchando allí. Si ya existe una instancia, envía sus argumentos de línea de comando sobre el socket y sale.

code w/ explanation

6

Se basa en la answer por el usuario zgoda. Principalmente aborda una complicada preocupación que tiene que ver con el acceso de escritura al archivo de bloqueo. En particular, si el archivo de bloqueo se creó primero por root, otro usuario foo no podrá intentar reescribir este archivo debido a la ausencia de permisos de escritura para el usuario foo. La solución obvia parece ser crear el archivo con permisos de escritura para todos. Esta solución también se basa en un answer diferente por mí, teniendo que hacer la creación de un archivo con dichos permisos personalizados. Esta preocupación es importante en el mundo real donde su programa puede ser ejecutado por cualquier usuario, incluido root.

import fcntl, os, stat, tempfile 

app_name = 'myapp' # <-- Customize this value 

# Establish lock file settings 
lf_name = '.{}.lock'.format(app_name) 
lf_path = os.path.join(tempfile.gettempdir(), lf_name) 
lf_flags = os.O_WRONLY | os.O_CREAT 
lf_mode = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH # This is 0o222, i.e. 146 

# Create lock file 
# Regarding umask, see https://stackoverflow.com/a/15015748/832230 
umask_original = os.umask(0) 
try: 
    lf_fd = os.open(lf_path, lf_flags, lf_mode) 
finally: 
    os.umask(umask_original) 

# Try locking the file 
try: 
    fcntl.lockf(lf_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) 
except IOError: 
    msg = ('Error: {} may already be running. Only one instance of it ' 
      'can run at a time.' 
      ).format('appname') 
    exit(msg) 

Una limitación del código anterior es que si el archivo de bloqueo ya existía con permisos inesperados, no serán corregidos esos permisos.

Me hubiera gustado usar /var/run/<appname>/ como el directorio para el archivo de bloqueo, pero crear este directorio requiere root permisos. Puede tomar su propia decisión sobre qué directorio usar.

Tenga en cuenta que no es necesario abrir un identificador de archivo para el archivo de bloqueo.

4

Aquí está la solución basada en el puerto TCP:

# Use a listening socket as a mutex against multiple invocations 
import socket 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.bind(('127.0.0.1', 5080)) 
s.listen(1) 
Cuestiones relacionadas