2010-05-29 14 views
10

me gustaría evitar que múltiples instancias de la misma pitón-carreras de larga secuencia de comandos de línea de comandos se ejecuten al mismo tiempo, y me gustaría que la nueva instancia para ser capaz de enviar datos a la instancia original antes de la nueva instancia se suicida ¿Cómo puedo hacer esto de forma multiplataforma?¿puede un script de python saber que se está ejecutando otra instancia del mismo script ... y luego hablar con él?

En concreto, me gustaría que el siguiente comportamiento:

  1. "foo.py" se inicia desde la línea de comandos, y se mantendrá funcionando durante mucho tiempo-- días o semanas hasta que la máquina es reiniciado o el proceso principal lo mata.
  2. cada pocos minutos, el mismo script se inicia de nuevo, pero con diferentes parámetros de línea de comandos
  3. cuando se inicia, el script debería ver si se están ejecutando otras instancias.
  4. si otros casos se están ejecutando, entonces ejemplo # 2 debería enviar sus parámetros de línea de comandos de ejemplo # 1, y después ejemplo # 2 debe salir.
  5. ejemplo # 1, si recibe parámetros de línea de comando desde otro script, debe girar un nuevo hilo y (utilizando los parámetros de línea de comandos enviados en el paso anterior) comenzar a realizar el trabajo de esa instancia nº 2 se va a realizar .

Así que estoy buscando dos cosas: ¿cómo puede un programa de python saber que se está ejecutando otra instancia de sí mismo, y cómo puede un programa de línea de comando de python comunicarse con otro?

Haciendo esto más complicado, el mismo guión necesita para funcionar en Windows y Linux, por lo que idealmente la solución sería utilizar solamente la biblioteca estándar de Python y no ninguna llamada específicos de OS. Aunque si necesito tener una ruta de código de Windows y una ruta de código * nix (y una gran declaración if en mi código para elegir una u otra), eso está bien si no es posible una solución de "mismo código".

Me doy cuenta de que probablemente podría encontrar un enfoque basado en archivos (por ejemplo, la instancia # 1 mira un directorio para ver los cambios y cada instancia suelta un archivo cuando quiere hacer un trabajo) pero estoy un poco preocupado limpiar esos archivos después de un apagado no gracioso de la máquina. Idealmente, podría usar una solución en memoria. Pero de nuevo soy flexible, si un enfoque basado en archivos persistentes es la única forma de hacerlo, estoy abierto a esa opción.

Más detalles: Estoy tratando de hacer esto porque nuestros servidores están utilizando una herramienta de supervisión que permite ejecutar secuencias de comandos python para recopilar datos de supervisión (por ejemplo, resultados de una consulta de base de datos o llamada de servicio web). uso posterior Algunas de estas secuencias de comandos son muy costosas de iniciar, pero son baratas para ejecutarse después del inicio (por ejemplo, hacer una conexión de base de datos o ejecutar una consulta). Así que hemos elegido mantenerlos en funcionamiento en un ciclo infinito hasta que el proceso principal los mate.

Esto funciona muy bien, pero en los servidores de mayor tamaño 100 casos del mismo script puede estar en ejecución, incluso si sólo están reuniendo datos cada 20 minutos cada uno. Esto causa estragos en la memoria RAM, los límites de conexión de DB, etc. Queremos cambiar de 100 procesos con 1 hilo a un proceso con 100 hilos, cada uno ejecutando el trabajo que, previamente, estaba haciendo un script.

Pero cambiar la forma en los guiones son invocados por la herramienta de monitorización no es posible. Necesitamos mantener la invocación igual (iniciar un proceso con diferentes parámetros de línea de comandos) pero cambiar los scripts para reconocer que otro está activo, y hacer que el script "nuevo" envíe sus instrucciones de trabajo (desde los parámetros de línea de comando) a a la "vieja" secuencia de comandos.

Por cierto, esto no es algo que quiera hacer en una sola secuencia de comandos. En su lugar, quiero incluir este comportamiento en una biblioteca que muchos autores de scripts pueden aprovechar: mi objetivo es permitir a los autores de scripts escribir scripts sencillos de un solo subproceso que no tengan en cuenta los problemas de varias instancias y manejar el multi-threading. y de una sola instancia bajo las sábanas.

+0

¿Por qué te pegas a que el script de trabajo sea el mismo que el de los scripts de invocación de comando? El script de trabajador podría ser un proceso de servidor que recibe comandos, enviados por clientes de retransmisión de comandos, llamados por su marco de supervisión, que solo tienen una tarea: decirle al servidor lo que debe hacer. – Bernd

Respuesta

9

El enfoque de Alex Martelli para configurar un canal de comunicaciones es el adecuado. Utilizaría un multiprocesador.connection.Listener para crear un oyente, en su elección. Documentación en: http://docs.python.org/library/multiprocessing.html#multiprocessing-listeners-clients

En lugar de utilizar AF_INET (sockets), puede optar por utilizar AF_UNIX para Linux y AF_PIPE para Windows. Esperemos que un pequeño "si" no duela.

Editar: Supongo que un ejemplo no haría daño. Aunque es básico.

#!/usr/bin/env python 

from multiprocessing.connection import Listener, Client 
import socket 
from array import array 
from sys import argv 

def myloop(address): 
    try: 
     listener = Listener(*address) 
     conn = listener.accept() 
     serve(conn) 
    except socket.error, e: 
     conn = Client(*address) 
     conn.send('this is a client') 
     conn.send('close') 

def serve(conn): 
    while True: 
     msg = conn.recv() 
     if msg.upper() == 'CLOSE': 
      break 
     print msg 
    conn.close() 

if __name__ == '__main__': 
    address = ('/tmp/testipc', 'AF_UNIX') 
    myloop(address) 

Esto funciona en OS X, por lo que necesita pruebas con Linux y (después de sustituir la dirección de la derecha) de Windows. Existen muchas advertencias desde un punto de seguridad, la principal es que conn.recv deshace sus datos, por lo que casi siempre es mejor con recv_bytes.

+0

¡Gran respuesta! Ser capaz de usar una tubería con nombre (windows) o fifo (unix), ya que puedo nombrar la tubería/fifo después de la secuencia de comandos que será único, parece mucho más fácil que tener que mantener una asignación en su lugar entre los scripts y los números de puerto. –

1

¿Quizás intente usar enchufes para la comunicación?

9

El enfoque general es tener la secuencia de comandos, en el inicio, configurar un canal de comunicación de una manera que se garantiza que es exclusiva (otros intentos de configurar el mismo canal fallan de manera predecible) para que otras instancias de la la secuencia de comandos puede detectar que la primera ejecuta y hablar con ella.

Sus requisitos para la funcionalidad multiplataforma apuntan fuertemente hacia el uso de un socket como canal de comunicación en cuestión: puede designar un "puerto conocido" reservado para su script, digamos 12345, y abrir un socket en ese puerto a localhost solamente (127.0.0.1). Si el intento de abrir ese socket falla, porque el puerto en cuestión está "tomado", entonces puede conectarse a ese número de puerto y eso le permitirá comunicarse con el script existente.

Si no está familiarizado con la programación de socket, hay un buen HOWTO doc here. También puede consultar el capítulo correspondiente en Python in a Nutshell (estoy parcial, por supuesto ;-).

+0

Hola Alex, gracias por la respuesta rápida. Mi principal preocupación con un enfoque de puerto conocido sería la posibilidad de conflictos (no poseemos los servidores para que otros programas puedan usar esos puertos) y la gestión de números de puertos (ya que aplicaremos el truco de instancia única) a muchos guiones mantenidos por diferentes autores de guiones). ¿Hay formas de solucionar los problemas mencionados anteriormente o estaré mejor con un mecanismo de "IPC designado"? Sospecho que las canalizaciones con nombre en Windows y los sockets de dominio en * nix podrían hacer esto, pero no sé cuán fácil sería usar Python. –

+0

@Justin, no estoy seguro de cómo usaría mecanismos como named pipes y sockets de dominio de Unix de forma multiplataforma y de manera "intrínsecamente mutuamente exclusiva". Para respaldar las necesidades específicas que identifica, puede hacer que las secuencias de comandos graben qué "puerto no tan conocido" se supone que usa una secuencia de comandos de nombre X, accediendo y actualizando un archivo '.dbm' (o sqlite etc.) manteniendo el nombre a la correspondencia del puerto (si una secuencia de comandos al inicio no encuentra su nombre allí, obtiene un puerto nuevo del sistema operativo y lo registra), tal vez con algún mecanismo de bloqueo de archivos para evitar condiciones de carrera. –

+0

La respuesta de @Muhammad Alkarouri a continuación (use el paquete de multiprocesamiento) parece una solución viable multiplataforma, al tiempo que se evita la complejidad de asignar scripts a los números de los puertos. ¿Alguna desventaja de usar 'multiprocesamiento'? –

0

Parece que la mejor opción es quedarse con un archivo pid pero no solo contener el Id del proceso, sino también incluir el número de puerto que está escuchando la instancia anterior. Por lo tanto, cuando inicie la verificación, busque el archivo pid y, si está presente, vea si se está ejecutando un proceso con ese Id; de ser así, envíe sus datos y salga; de lo contrario, sobrescriba el archivo pid con la información del proceso actual.

Cuestiones relacionadas