2014-06-27 2 views
5

Я пытаюсь использовать Pyro для управления ведомой машиной. Я rsync необходимые файлы python, запустить Pyro сервер, выполнить некоторые действия с помощью пульта дистанционного управления, а затем я хочу сказать Pyro сервер, чтобы закрыть.Как я могу выйти из Pyro Daemon по запросу клиента?

У меня возникли проблемы с тем, чтобы заставить прайо-демона закрыться чисто. Он либо зависает в вызове Daemon.close(), либо, если я прокомментирую эту строку, он выходит, не закрывая его сокет правильно, в результате чего socket.error: [Errno 98] Address already in use, если я перезапущу сервер слишком рано.

Не думайте, что SO_REUSEADDR - это правильное исправление, так как неактивное выключение сокета по-прежнему приводит к разрыву сокета в состоянии TIME_WAIT, что может вызвать проблемы у некоторых клиентов. Я думаю, лучшее решение - убедить Pyro Daemon правильно закрыть его гнездо.

Неправильно ли вызывать Daemon.shutdown() из самого демона?

Если я запустил сервер, а затем нажмите CTRL-C без каких-либо подключенных клиентов, у меня нет никаких проблем (нет ошибок Address already in use). Это делает чистое закрытие возможным, в большинстве случаев (при условии, что в противном случае разумный клиент и сервер).

Пример: 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' 

Пример: 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

Эй Эрик. Я никогда не пользовался «адресом, который уже используется» для Pyro-сервера, но я получаю его все время для «сервера имен». Удар CTRL + C на NameServer имеет 50% -ный шанс вызвать эту ошибку, если я снова запустил сервер имен в течение 30 секунд. У вас было это раньше? –

ответ

0

Я думаю, я близок к раствору: комбинации с использованием параметра loopCondition к requestloop() и значение конфигурации 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' 

К сожалению, есть одно условие, где она по-прежнему оставляет гнездо позади в состоянии TIME_WAIT. Если клиент закрывает свой сокет после сервера, следующая попытка запуска сервера возвращает ту же ошибку Address already in use.

Единственный способ, которым я могу найти, чтобы обойти это сделать COMMTIMEOUT сервера больше (или сон в течение нескольких секунд перед вызовом daemon.close()), и убедитесь, что клиент всегда вызывает _pyroRelease() сразу после вызова отключения:

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' 

Я полагаю, что это достаточно хорошо, но, учитывая несправедливость планирования и сетевых задержек это все еще разочаровывает иметь, что состояние гонки скрываясь.

+0

Я нашел в тестировании, что использование COMMTIMEOUT слишком агрессивно приводит к ложным отказам, поэтому мне пришлось отменить это до 5 секунд. Еще одна причина, почему это решение не совсем правильно. –

2

Я думаю, что это можно сделать, не используя тайм-аут или loopCondition, указав shutdown() на вызов daemon's shutdown. По http://pythonhosted.org/Pyro4/servercode.html#cleaning-up:

Другая возможность вызова Pyro4.core.Daemon.shutdown() на подножку объекта bdaemon. Это также вырвется из цикла запросов и позволит вашему компьютеру аккуратно очистить после себя, а также будет работать с типом сервера с потоком без каких-либо других требований.

Следующие действия относятся к Python3.4.2 на Windows. Декодер @Pyro4.oneway для shutdown здесь не нужен, но он в некоторых ситуациях.

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')