編輯:原來是一個蒔蘿bug,請參閱答案。Python:Numpy.min替換內建:導致Pyro4錯誤:回覆序列不同步
編輯:放眼底部我的進步錯誤後,這原來是由numpy.min更換而造成的在建min函數
我與煙火(版本4.43,Python的3.5工作.1,Windows 10),並嘗試設置一個簡單的羣集,其中服務器進程等待工作進程和工作進程請求工作併發回結果。一旦服務器收到結果,它會對它進行一些進一步的處理。
目前我只是試圖讓它在單臺計算機上工作(使用本地主機和產卵工作進程從同一臺計算機)。
到目前爲止,我能夠讓服務器進程運行,並且工作進程能夠連接到服務器以請求數據,處理該數據,但是當工作進程嘗試將結果發回時工作進程出錯到服務器。
我遇到一個奇怪的錯誤消息:
File "worker.py", line 90, in <module>
main()
File "worker.py", line 87, in main
worker.send_result()
File "worker.py", line 49, in send_result
self.server.recieve(result)
File "C:\Anaconda3\lib\site-packages\Pyro4\core.py", line 171, in __call__
return self.__send(self.__name, args, kwargs)
File "C:\Anaconda3\lib\site-packages\Pyro4\core.py", line 418, in _pyroInvoke
self.__pyroCheckSequence(msg.seq)
File "C:\Anaconda3\lib\site-packages\Pyro4\core.py", line 448, in __pyroCheckSequence
raise errors.ProtocolError(err)
Pyro4.errors.ProtocolError: invoke: reply sequence out of sync, got 0 expected 2
徹底搜索後,我只能找到一個other person who has had this error,但反應是,這是一個純粹的火焰兵的錯誤,他需要更新火焰兵,但是當我寫這篇文章時,我的版本遠遠超出了現在的版本。
更進一步,我在生成代碼之外重現此錯誤時遇到了問題。我試圖創建一個簡單的版本來縮小錯誤的來源,並且無法獲得此錯誤。我甚至從工作人員那裏發送了一個結果的確切形式的結果,該結果以生產代碼發送,沒有錯誤。
這裏是簡化的代碼,只是爲了說明我的設置的結構。 下面的代碼不會重現錯誤。我不知道下一步是如何讓它更接近生產代碼而不會過度複雜化。
Server代碼:
#simple_server.py
import Pyro4
import sys, dill
class SimpleServer:
def serve(self):
with open('served data.pkl', 'rb') as f:
data = dill.load(f) #actual data coming from production code
return data
def recieve(self, result):
print(result)
def main():
Pyro4.config.SERIALIZER = 'dill' #default serpent serializer doesn't work
dill.settings['recurse'] = True #dill won't work without this option
server = SimpleServer()
daemon = Pyro4.Daemon()
server_uri = daemon.register(server)
ns = Pyro4.locateNS()
ns.register("test", server_uri)
print('Server running.')
daemon.requestLoop()
if __name__ == '__main__':
main()
工人代碼:
#simple_worker.py
import Pyro4
import sys, dill
import numpy as np
import scipy.optimize as opt
class SimpleWorker:
def __init__(self, server):
self.server = server
def recieve_data(self):
self.data = self.server.serve()
def send_result(self):
res = opt.basinhopping(lambda x: sum(x), np.arange(11), niter=2, minimizer_kwargs={'options':{'maxiter':2}})
#This below data structure is the same that I send in production
result = ('ABCD', 'filename.csv', res, 6)
self.server.recieve(result) #creates error in production code but not here
def main():
sys.excepthook = Pyro4.util.excepthook #gives a more meaningful stack trace
Pyro4.config.SERIALIZER = 'dill' #default serpent serializer doesn't work
dill.settings['recurse'] = True #dill won't work without this option
server = Pyro4.Proxy('PYRONAME:test') #connects to pinest server
worker = SimpleWorker(server)
worker.recieve_data()
worker.send_result()
if __name__ == '__main__':
main()
的Windows CMD代碼:
#run_simple_server.bat
set PYRO_SERIALIZERS_ACCEPTED=serpent,json,marshal,pickle,dill
start cmd /C python -m Pyro4.naming
python simple_server.py
pause
#run_simple_worker.bat
python simple_worker.py
pause
注:我需要用蒔蘿使用遞歸選項發送這些類型的數據
如果我在worker main中打印Pyro4.current_context.seq
,它將返回0.如果我嘗試Pyro4.current_context.seq = 2
,它不會影響錯誤。
有誰知道如何處理這個錯誤或我應該在嘗試排除故障時做什麼?
編輯:經過對Pyro4源代碼的回顧後,似乎由於Pyro4中的編碼錯誤而引發此錯誤。在core.Daemon.handleRequest中,如果接收到消息時出錯,它將自己的消息序列設置爲零,並嘗試將該錯誤作爲消息發送。但是當core.Proxy。_pyroInvoke接收到消息,如果序列爲零,則無法將其視爲錯誤。因此,提出了應答序列不同步錯誤。
我已經想出了導致接收消息錯誤的潛在問題。 socketutil.receiveData具有一個接收循環,其中一行選擇最小值60000和剩餘大小的消息min(60000, size - msglen)
。不知何故,當它執行時,它使用numpy.min而不是內建min,並且出錯,因爲numpy.min的第二個參數應該是軸號。這是令人驚訝的,因爲我只在我的代碼中導入了numpy as np
,並且從未使用過from numpy import *
或直接導入min函數。
更令人驚訝的是,我無法通過用內置函數替換它來修復它。我嘗試import builtins
,然後min = builtins.min
,錯誤仍然存在。如果我運行inspect.getfile(builtins.min)
它指向Numpy文件。
我試圖通過切換線爲min([60000, size - msglen])
,它適用於numpy和建立在最小,但最小分配持久回到我的服務器代碼並在那裏也弄亂功能,以儘量避免該問題。
作爲一個相當hackish的修復,我不停的分功能的上述變化,而且在我的服務器類的初始化,我存儲內置函數:
#Store builtin functions as they later get replaced for some unknown reason
b = [t for t in().__class__.__base__.__subclasses__() if t.__name__ == 'Sized'][0].__len__.__globals__['__builtins__']
self.real_builtins = copy.copy(b) #copy so that dict doesn't get updated
然後每次服務器接收或發送數據,我先運行這個函數:
def fix_builtins(self):
global builtins
import builtins
__builtins__ = self.real_builtins
#These are all of [i for i in dir(builtins) if i in dir(numpy)]
builtins.abs = __builtins__['abs']
builtins.all = __builtins__['all']
builtins.any = __builtins__['any']
builtins.bool = __builtins__['bool']
builtins.complex = __builtins__['complex']
builtins.float = __builtins__['float']
builtins.int = __builtins__['int']
builtins.max = __builtins__['max']
builtins.min = __builtins__['min']
builtins.object = __builtins__['object']
builtins.round = __builtins__['round']
builtins.str = __builtins__['str']
builtins.sum = __builtins__['sum']
這似乎現在工作。但是,這顯然不是一個很好的解決問題的方法,我寧願停止它從一開始就取代內建函數......這是Pyro特有的問題嗎?
Pyro本身絕對不會取代builtins。如果另一個圖書館正在這樣做,所有投注都關閉。 所以:是什麼導致內置min被numpy.min替換?如果我輸入numpy,沒有什麼奇怪的事情發生。 builtins.min仍然是Python自己的內置。 –
@Imen感謝您的幫助。你爲什麼如此確定Pyro不會導致問題呢?我的服務器代碼在沒有Pyro的情況下工作得很好,一旦我把它連接到Pyro,我就遇到了這個問題。這是一個非常奇怪的問題,因爲即使我從'numpy import min',它並不會取代'builtins.min',只有名字min。 –
我的意思是說Pyro代碼庫本身沒有任何東西與內置函數搞混了。現在,*序列化程序*可能會做什麼,被Pyro(pickle,dill)調用,我不知道,但也許他們正在搞亂它。這仍然可以解釋爲什麼你的代碼不使用Pyro,因爲我不認爲你是通過pickle/dill傳遞數據結構。 –