Twisted web - 在finish()之后调用write()?

时间:2012-06-14 15:00:09

标签: python twisted twisted.web

我有以下资源来处理带有扭曲网络的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。我究竟做错了什么?

1 个答案:

答案 0 :(得分:1)

正如Mualig在他的评论中所说,你只有RootResource的一个实例。当您在self.request中分配给self.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