我有以下资源来处理带有扭曲网络的http POST请求:
class RootResource(Resource):
isLeaf = True
def errback(self, failure):
print "Request finished with error: %s"%str(failure.value)
return failure
def write_response_happy(self, result):
self.request.write('HAPPY!')
self.request.finish()
def write_response_unhappy(self, result):
self.request.write('UNHAPPY!')
self.request.finish()
@defer.inlineCallbacks
def method_1(self):
#IRL I have many more queries to mySQL, cassandra and memcache to get final result, this is why I use inlineCallbacks to keep the code clean.
res = yield dbpool.runQuery('SELECT something FROM table')
#Now I make a decision based on result of the queries:
if res: #Doesn't make much sense but that's only an example
self.d.addCallback(self.write_response_happy) #self.d is already available after yield, so this looks OK?
else:
self.d.addCallback(self.write_response_unhappy)
returnValue(None)
def render_POST(self, request):
self.request = request
self.d = self.method_1()
self.d.addErrback(self.errback)
return server.NOT_DONE_YET
root = RootResource()
site = server.Site(root)
reactor.listenTCP(8002, site)
dbpool = adbapi.ConnectionPool('MySQLdb', host='localhost', db='mydb', user='myuser', passwd='mypass', cp_reconnect=True)
print "Serving on 8002"
reactor.run()
我使用ab工具(来自apache utils)一个接一个地测试5个POST请求:
ab -n 5 -p sample_post.txt http://127.0.0.1:8002/
工作正常!
然后我尝试同时运行相同的5个POST请求:
ab -n 5 -c 5 -p sample_post.txt http://127.0.0.1:8002/
这里我收到错误:exceptions.RuntimeError:在调用Request.finish之后调用请求时调用Request.write。我究竟做错了什么?
答案 0 :(得分:1)
正如Mualig在他的评论中所说,你只有RootResource
的一个实例。当您在self.request
中分配给self.d
和render_POST
时,您将覆盖这些属性已有的任何值。如果两个请求大约在同一时间到达,则这是一个问题。第一个Request
和Deferred
将被丢弃,并替换为与第二个到达的请求相关联的新值。稍后,当您的数据库操作完成时,第二个请求获得两个结果,第一个请求完全没有。
这是并发编程中一般错误的一个例子。您的每请求状态保留在多个请求之间共享的位置。当同时处理多个请求时,该共享变成了一场战斗,并且(至少)一个请求必须丢失。
尝试将每个请求状态保持在多个请求之间不会共享的位置。例如,尝试将其保留在Deferred:
上class RootResource(Resource):
isLeaf = True
def errback(self, failure):
print "Request finished with error: %s"%str(failure.value)
# You just handled the error, don't return the failure.
# Nothing later in the callback chain is doing anything with it.
# return failure
def write_response(self, result, request):
# No "self.request" anymore, just use the argument
request.write(result)
request.finish()
@defer.inlineCallbacks
def method_1(self):
#IRL I have many more queries to mySQL, cassandra and memcache to get final result, this is why I use inlineCallbacks to keep the code clean.
res = yield dbpool.runQuery('SELECT something FROM table')
#Now I make a decision based on result of the queries:
if res: #Doesn't make much sense but that's only an example
# No "self.d" anymore, just produce a result. No shared state to confuse.
returnValue("HAPPY!")
else:
returnValue("UNHAPPY!")
def render_POST(self, request):
# No more attributes on self. Just start the operation.
d = self.method_1()
# Push the request object into the Deferred. It'll be passed to response,
# which is what needs it. Each call to method_1 returns a new Deferred,
# so no shared state here.
d.addCallback(self.write_response, request)
d.addErrback(self.errback)
return server.NOT_DONE_YET