Pregunta

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

Estoy buscando algo que se lanzará automáticamente si la aplicación falla inesperadamente. No quiero tener que cargar a mis usuarios con tener que eliminar manualmente los archivos bloqueados porque me caí.

¿Fue útil?

Solución

Hay varias técnicas comunes que incluyen el uso de semáforos. El que veo que se usa con más frecuencia es crear un & Quot; pid lock file & Quot; en el inicio que contiene el pid del proceso en ejecución. Si el archivo ya existe cuando se inicia el programa, ábralo y tome el pid dentro, verifique si se está ejecutando un proceso con ese pid, si está, verifique el valor de cmdline 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.

Otros consejos

Lo correcto es el bloqueo de aviso mediante flock(LOCK_EX); en Python, esto se encuentra en el fcntl módulo .

A diferencia de los archivos de solicitud, estos bloqueos siempre se liberan automáticamente cuando su proceso muere por cualquier motivo, no existen condiciones de carrera relacionadas con la eliminación del archivo (ya que el archivo no necesita ser eliminado para liberar el bloqueo), y no hay posibilidad de que un proceso diferente herede el PID y parezca validar un bloqueo obsoleto.

Si desea la detección de apagado no limpio, 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 el bloqueo está siendo retenido); por lo tanto, si el bloqueo no se mantiene y el archivo no está vacío, se indica un cierre no limpio.

Solución de bloqueo completa utilizando 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)

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

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

Esto se basa en la respuesta del usuario zgoda . Principalmente trata una cuestión difícil que tiene que ver con el acceso de escritura al archivo de bloqueo. En particular, si el archivo de bloqueo fue creado por root, otro usuario foo ya no puede intentar reescribir este archivo con éxito debido a la ausencia de permisos de escritura para el usuario /var/run/<appname>/. La solución obvia parece ser crear el archivo con permisos de escritura para todos. Esta solución también se basa en una respuesta diferente por mí, que tiene que crear un archivo con dichos permisos personalizados. Esta preocupación es importante en el mundo real donde cualquier usuario puede ejecutar su programa, incluso <=>.

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, esos permisos no se corregirán.

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

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

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)

Busque un módulo de Python que interactúe con los semáforos SYSV en Unix. Los semáforos tienen un indicador SEM_UNDO que hará que los recursos que posee un proceso se liberen si el proceso falla.

De lo contrario, como sugirió Bernard, puede usar

import os
os.getpid()

Y escríbalo en / var / run / application_name .pid. Cuando se inicia el proceso, debe verificar si el pid en /var/run/application_name.pid aparece en la tabla ps y salir si lo está, de lo contrario, escriba su propio pid en / var / run / nombre_aplicación .pid. En el siguiente var_run_pid está el pid que lees 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

El conjunto de funciones definidas en semaphore.h - sem_open(), sem_trywait(), etc. - son el equivalente POSIX, creo.

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

No he hecho esto personalmente, así que tómalo con la cantidad adecuada de sal. : p

¿Puedes usar la utilidad 'pidof'? Si su aplicación se está ejecutando, pidof escribirá la 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 bash, por simplicidad):

linux# pidof myapp
8947
linux# pidof nonexistent_app

linux#

Con mucho, el método más común es colocar un archivo en / var / run / llamado [application] .pid que contiene solo el PID del proceso en ejecución, o proceso padre. Como alternativa, puede crear una tubería con nombre en el mismo directorio para poder enviar mensajes al proceso activo, p. para abrir un nuevo archivo.

He creado un marco básico para ejecutar este tipo de aplicaciones cuando desea poder pasar los argumentos de la línea de comandos de instancias intentadas posteriores a la primera. 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.

código con explicación

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top