2014-06-27 9 views
5

Sto provando ad usare Pyro per controllare una macchina slave. Rsync i file python necessari, avvio un server Pyro, eseguo alcune azioni con il controllo remoto, e poi voglio dire al server Pyro di spegnersi.Come posso uscire in modo pulito da un demone Pyro su richiesta del cliente?

Ho problemi a far spegnere Pryo Daemon in modo pulito. Si blocca o nella chiamata Daemon.close(), o se commento fuori quella linea esce senza chiudere correttamente il suo socket, risultante in socket.error: [Errno 98] Address already in use se riavvio il server troppo presto.

Non credo che SO_REUSEADDR sia la soluzione corretta, poiché l'arresto di socket non pulito porta ancora a un socket sospeso nello stato TIME_WAIT, causando potenzialmente l'esistenza di problemi in alcuni client. Penso che la soluzione migliore sia convincere Pyro Daemon a chiudere correttamente il socket.

Non è corretto chiamare Daemon.shutdown() dall'interno del daemon stesso?

Se avvio un server e quindi preme CTRL-C senza alcun client connesso, non ho alcun problema (nessun errore Address already in use). Ciò rende possibile un arresto pulito, la maggior parte delle volte (assumendo un client e un server altrimenti sensati).

Esempio: server.py

import Pyro4 

class TestAPI: 
    def __init__(self, daemon): 
     self.daemon = daemon 
    def hello(self, msg): 
     print 'client said {}'.format(msg) 
     return 'hola' 
    def shutdown(self): 
     print 'shutting down...' 
     self.daemon.shutdown() 

if __name__ == '__main__': 
    daemon = Pyro4.Daemon(port=9999) 
    tapi = TestAPI(daemon) 
    uri = daemon.register(tapi, objectId='TestAPI') 
    daemon.requestLoop() 
    print 'exited requestLoop' 
    daemon.close() # this hangs 
    print 'daemon closed' 

Esempio: client.py

import Pyro4 

if __name__ == '__main__': 
     uri = 'PYRO:[email protected]:9999' 
     remote = Pyro4.Proxy(uri) 
     response = remote.hello('hello') 
     print 'server said {}'.format(response) 
     try: 
      remote.shutdown() 
     except Pyro4.errors.ConnectionClosedError: 
      pass 
     print 'client exiting' 
+0

Hey Eric. Non ho mai avuto l'indirizzo in uso per il server Pyro, ma ottengo tutto il tempo per il 'Name Server'. Premendo CTRL + C sul NameServer si ha una probabilità del 50% di provocare quell'errore se eseguo nuovamente il server dei nomi entro 30 secondi. Hai avuto questo prima? –

risposta

0

Penso di essere vicino ad una soluzione: una combinazione di utilizzare il parametro loopCondition-requestloop() e il valore di configurazione COMMTIMEOUT.

server.py

import Pyro4 
Pyro4.config.COMMTIMEOUT = 1.0 # without this daemon.close() hangs 

class TestAPI: 
    def __init__(self, daemon): 
     self.daemon = daemon 
     self.running = True 
    def hello(self, msg): 
     print 'client said {}'.format(msg) 
     return 'hola' 
    def shutdown(self): 
     print 'shutting down...' 
     self.running = False 

if __name__ == '__main__': 
    daemon = Pyro4.Daemon(port=9999) 
    tapi = TestAPI(daemon) 
    uri = daemon.register(tapi, objectId='TestAPI') 
    def checkshutdown(): 
     return tapi.running 
    daemon.requestLoop(loopCondition=checkshutdown) # permits self-shutdown 
    print 'exited requestLoop' 
    daemon.close() 
    print 'daemon closed' 

Purtroppo, v'è una condizione in cui lascia ancora una presa dietro nello stato TIME_WAIT. Se il client chiude il socket dopo il server, il successivo tentativo di avviare il server restituisce lo stesso errore Address already in use.

L'unico modo che riesco a trovare per ovviare a questo è quello di rendere il COMMTIMEOUT server di più (o dormire per diversi secondi prima di chiamare daemon.close()), e assicurarsi che il cliente chiama sempre _pyroRelease() subito dopo la chiamata di arresto:

client.py

import Pyro4 

if __name__ == '__main__': 
     uri = 'PYRO:[email protected]:9999' 
     remote = Pyro4.Proxy(uri) 
     response = remote.hello('hello') 
     print 'server said {}'.format(response) 
     remote.shutdown() 
     remote._pyroRelease() 
     print 'client exiting' 

suppongo che sia abbastanza buono, ma data l'ingiustizia di pianificazione e di rete ritardi è ancora deludente per avere quella condizione di competizione in agguato.

+0

Ho riscontrato nei test che l'utilizzo di COMMTIMEOUT porta troppo aggressivo a errori spuri, quindi ho dovuto eseguire il backup su 5 secondi. Ancora un altro motivo per cui questa soluzione non sembra esattamente giusta. –

2

Penso che questo può essere fatto senza usare timeout o loopCondition, avendo il tuo shutdown() chiama il daemon shutdown. Secondo http://pythonhosted.org/Pyro4/servercode.html#cleaning-up:

Un'altra possibilità sta chiamando Pyro4.core.Daemon.shutdown() sull'oggetto bdaemon esecuzione. Anche questo si romperà dal ciclo di richieste e consentirà al tuo codice di ripulire accuratamente dopo se stesso, e funzionerà anche sul tipo di server con thread senza altri requisiti.

I seguenti lavori su Python3.4.2 su Windows. Il decoratore @Pyro4.oneway per shutdown non è necessario qui, ma è in alcune situazioni.

server.py

import Pyro4 
# using Python3.4.2 

@Pyro4.expose 
class TestAPI: 
    def __init__(self, daemon): 
     self.daemon = daemon 
    def hello(self, msg): 
     print('client said {}'.format(msg)) 
     return 'hola' 
    @Pyro4.oneway # in case call returns much later than daemon.shutdown 
    def shutdown(self): 
     print('shutting down...') 
     self.daemon.shutdown() 

if __name__ == '__main__': 
    daemon = Pyro4.Daemon(port=9999) 
    tapi = TestAPI(daemon) 
    uri = daemon.register(tapi, objectId='TestAPI') 
    daemon.requestLoop() 
    print('exited requestLoop') 
    daemon.close() 
    print('daemon closed') 

client.py

import Pyro4 
# using Python3.4.2 

if __name__ == '__main__': 
    uri = 'PYRO:[email protected]:9999' 
    remote = Pyro4.Proxy(uri) 
    response = remote.hello('hello') 
    print('server said {}'.format(response)) 
    remote.shutdown() 
    remote._pyroRelease() 
    print('client exiting')