2014-06-27 59 views
5

我試圖用Pyro來控制一臺奴隸機器。我rsync必要的python文件,啓動一個Pyro服務器,通過遠程控制執行一些操作,然後我想告訴Pyro服務器關閉。如何通過客戶端請求乾淨地退出Pyro Daemon?

我很難讓Pryo 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服務器的'Address already in'地址,但是我一直爲'Name Server'服務。如果我在30秒內再次運行名稱服務器,則NameServer上的CTRL + C有50%的機會導致該錯誤。你有過這個嗎? –

回答

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

我認爲這可以通過讓您的shutdown()調用守護進程的shutdown而不使用超時或loopCondition來完成。根據http://pythonhosted.org/Pyro4/servercode.html#cleaning-up

另一種可能性是在正在運行的bdaemon對象上調用Pyro4.core.Daemon.shutdown()。這也將跳出請求循環,並允許您的代碼自行清理乾淨,並且還可以在沒有任何其他要求的情況下使用線程服務器類型。

以下工作適用於Windows上的Python3.4.2。這裏不需要shutdown@Pyro4.oneway修飾器,但在某些情況下。

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