2014-06-27 13 views
5

Próbuję użyć Pyro do sterowania maszyną podrzędną. Zsynchronizuję niezbędne pliki Pythona, uruchomę serwer Pyro, wykonam niektóre czynności za pomocą zdalnego sterowania, a następnie chcę, aby serwer Pyro został zamknięty.Jak mogę całkowicie zamknąć demona Pyro na żądanie klienta?

Mam problem z wyjęciem demona Pryo w czysty sposób. Zawiesza się ona w wywołaniu Daemon.close() lub jeśli skomentuję, że linia ta jest zamykana bez wyłączania jego gniazda poprawnie, powodując socket.error: [Errno 98] Address already in use, jeśli zbyt szybko zrestartuję serwer.

Nie wydaje się, że SO_REUSEADDR jest poprawną instalacją, ponieważ nieczytelne zamknięcie gniazda nadal powoduje, że gniazdo wisi w stanie TIME_WAIT, potencjalnie powodując problemy z niektórymi klientami. Myślę, że lepszym rozwiązaniem jest przekonanie Pyro Daemona do właściwego zamknięcia gniazda.

Czy niewłaściwe jest wywoływanie Daemon.shutdown() z poziomu samego demona?

Jeśli uruchomię serwer, a następnie naciśnij CTRL-C bez podłączonych klientów, nie mam żadnych problemów (nie ma błędów Address already in use). To sprawia, że ​​czyste wyłączanie wydaje się możliwe, przez większość czasu (zakładając, że jest to normalny klient i serwer).

przykład: 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' 

przykład: 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

Hej Eric. Nigdy nie miałem "Adresu już w użyciu" dla serwera Pyro, ale dostaję go cały czas na "Serwer Nazw". Naciśnięcie CTRL + C na NameServer ma 50% szans na spowodowanie tego błędu, jeśli ponownie uruchomię serwer nazw w ciągu 30 sekund. Czy miałeś to już wcześniej? –

Odpowiedz

0

Chyba am blisko roztworu: połączenie za pomocą parametru loopCondition do requestloop() i wartość konfiguracyjny 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' 

Niestety, jest jeden warunek, gdzie nadal pozostawia gniazda z tyłu w stanie CZAS_OCZEKIWANIA. Jeśli klient zamknie swoje gniazdo po serwerze, następna próba uruchomienia serwera zwraca ten sam błąd Address already in use.

Jedynym sposobem mogę znaleźć obejścia tego jest, aby COMMTIMEOUT serwera dłużej (lub spać na kilka sekund przed wywołaniem daemon.close()), i upewnić się, że klient zawsze wywołuje _pyroRelease() tuż po wywołaniu zamykania:

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' 

Przypuszczam, że to wystarczająco dobre, ale biorąc pod uwagę nieuczciwego charakteru planowania i sieciowych opóźnień to nadal rozczarowujące mieć warunek wyścig czai.

+0

Znalazłem w testowaniu, że użycie COMMTIMEOUT zbyt agresywnie prowadzi do fałszywych niepowodzeń, więc musiałem wycofać to do 5 sekund. Jeszcze jeden powód, dla którego to rozwiązanie nie wydaje się właściwe. –

2

Myślę, że można to zrobić bez limitu czasu lub warunku pętli, wywołując shutdown() połączenie z demonem shutdown. Według http://pythonhosted.org/Pyro4/servercode.html#cleaning-up:

Inną możliwością jest powołanie Pyro4.core.Daemon.shutdown() na obiekcie bdaemon uruchomiony. To również wyrwie się z pętli żądania i pozwoli, aby twój kod starannie wyczyścił się po niej, i będzie także pracował na typie serwera z wątkami bez żadnych innych wymagań.

Następujące prace nad Python3.4.2 na Windows. Dekorator @Pyro4.oneway dla shutdown nie jest tu potrzebny, ale w niektórych sytuacjach.

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') 
Powiązane problemy