0

我有以下资源来处理带有扭曲网络的 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.write 在调用 Request.finish 后调用请求。我究竟做错了什么?

4

1 回答 1

1

正如 Mualig 在他的评论中所建议的那样,您只有一个RootResource. 当您分配给self.requestself.drender_POST,您会覆盖这些属性已经拥有的任何值。如果两个请求大约同时到达,那么这是一个问题。第一个RequestDeferred被丢弃并被与第二个到达的请求相关联的新的替换。稍后,当您的数据库操作完成时,第二个请求会同时获得结果,而第一个请求则根本没有结果。

这是并发编程中普遍错误的一个例子。您的每个请求状态保存在多个请求之间共享的位置。当同时处理多个请求时,共享就会变成一场战斗,并且(至少)一个请求必须失败。

尝试保持您的每个请求状态不会在多个请求之间共享。例如,尝试将其保留在 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
于 2012-06-15T13:09:31.700 回答