SocketServer.ThreadingMixin を使用する SimpleXMLRPCServers のチェーンを使用しているときに、断続的に httplib.CannotSendRequest 例外を受け取ります。
「チェーン」とは、次のことを意味します。
xmlrpclib を使用して SimpleXMLRPCServer の関数を呼び出すクライアント スクリプトがあります。そのサーバーは、別の SimpleXMLRPCServer を呼び出します。複雑に聞こえるかもしれませんが、このアーキテクチャが選択されたのには十分な理由があり、それが可能であってはならない理由がわかりません。
(testclient)client_script ---calls-->
(middleserver)SimpleXMLRPCServer ---calls--->
(finalserver)SimpleXMLRPCServer --- does something
- SocketServer.ThreadingMixin を使用しない場合、この問題は発生しません (ただし、リクエストをマルチスレッドにする必要があるため、これは役に立ちません)。
- サービスのレベルが 1 つしかない場合 (つまり、最終サーバーを直接呼び出すクライアント スクリプトのみ)、これは発生しません。
以下の簡単なテスト コードで問題を再現できました。3 つのスニペットがあります。
最終サーバー:
import SocketServer
import time
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass
# Create server
server = AsyncXMLRPCServer(('', 9999), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()
def waste_time():
time.sleep(10)
return True
server.register_function(waste_time, 'waste_time')
server.serve_forever()
ミドルサーバー:
import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import xmlrpclib
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass
# Create server
server = AsyncXMLRPCServer(('', 8888), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()
s = xmlrpclib.ServerProxy('http://localhost:9999')
def call_waste():
s.waste_time()
return True
server.register_function(call_waste, 'call_waste')
server.serve_forever()
テストクライアント:
import xmlrpclib
s = xmlrpclib.ServerProxy('http://localhost:8888')
print s.call_waste()
再現するには、次の手順を使用する必要があります。
- python finalserver.py を実行します
- python middleserver.py を実行します
- python testclient.py を実行します
- (3) の実行中に、python testclient.py の別のインスタンスを実行します。
かなりの頻度で (ほとんどの場合)、最初にステップ 4 を実行しようとすると、以下のエラーが発生します。興味深いことに、すぐにステップ (4) を再度実行しようとすると、エラーは発生しません。
Traceback (most recent call last):
File "testclient.py", line 6, in <module>
print s.call_waste()
File "/usr/lib64/python2.7/xmlrpclib.py", line 1224, in __call__
return self.__send(self.__name, args)
File "/usr/lib64/python2.7/xmlrpclib.py", line 1578, in __request
verbose=self.__verbose
File "/usr/lib64/python2.7/xmlrpclib.py", line 1264, in request
return self.single_request(host, handler, request_body, verbose)
File "/usr/lib64/python2.7/xmlrpclib.py", line 1297, in single_request
return self.parse_response(response)
File "/usr/lib64/python2.7/xmlrpclib.py", line 1473, in parse_response
return u.close()
File "/usr/lib64/python2.7/xmlrpclib.py", line 793, in close
raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: "<class 'httplib.CannotSendRequest'>:">
インターネットは、この例外は、getresponse 呼び出しを介在させずに httplib.HTTPConnection.request を複数回呼び出すことによって発生する可能性があると言っているようです。ただし、インターネットでは、SimpleXMLRPCServer のコンテキストではこれについて議論されていません。httplib.CannotSendRequest の問題を解決する方向へのポインタをいただければ幸いです。
================================================== ========================================= 答え:
わかりました、私は少しばかです。私はあまりにも長い間コードを見つめていたので、目の前にある明らかな解決策を見逃していたと思います (文字通り、答えは実際の質問にあるためです)。
基本的に、CannotSendRequest は、介在する「要求」操作によって httplib.HTTPConnection が中断されたときに発生します。各 httplib.HTTPConnection.request は、.getresponse() 呼び出しと組み合わせる必要があります。そのペアリングが別の要求操作によって中断された場合、2 番目の要求は CannotSendRequest エラーを生成します。それで:
connection = httplib.HTTPConnection(...)
connection.request(...)
connection.request(...)
getresponse が呼び出される前に同じ接続で 2 つの要求があるため、失敗します。
それを私の質問にリンクする:
- このような接続が行われている 3 つのプログラムの唯一の場所は、serverproxy 呼び出しです。
- 問題はスレッド化中にのみ発生するため、競合状態である可能性があります。
- serverproxy 呼び出しが共有される唯一の場所は middleserver.py です。
その場合の解決策は、明らかに、各スレッドに独自のサーバープロキシを作成させることです。middleserver の修正版は以下のとおりで、動作します。
import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import xmlrpclib
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass
# Create server
server = AsyncXMLRPCServer(('', 8888), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()
def call_waste():
# Each call to this function creates its own serverproxy.
# If this function is called by concurrent threads, each thread
# will safely have its own serverproxy.
s = xmlrpclib.ServerProxy('http://localhost:9999')
s.waste_time()
return True
server.register_function(call_waste, 'call_waste')
server.serve_forever()
このバージョンでは、各スレッドが独自の xmlrpclib.serverproxy を持つことになるため、serverproxy の同じインスタンスが HTTPConnection.request を連続して 2 回以上呼び出すリスクはありません。プログラムは意図したとおりに動作します。
ご迷惑をおかけして申し訳ありません。