0

ねじれた 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 が呼び出されました。私は何を間違っていますか?

4

1 に答える 1

1

Mualig がコメントで示唆したように、 のインスタンスは 1 つしかありませんRootResource。および に代入するself.requestself.drender_POSTそれらの属性がすでに持っていた値が上書きされます。2 つの要求がほぼ同時に到着した場合、これは問題です。最初のRequestDeferredは破棄され、2 番目に到着した要求に関連付けられた新しいものに置き換えられます。後でデータベース操作が終了すると、2 番目のリクエストは両方の結果を取得し、最初のリクエストはまったく結果を取得しません。

これは、並行プログラミングにおける一般的な間違いの例です。リクエストごとの状態は、複数のリクエスト間で共有される場所に保持されます。複数のリクエストが同時に処理されると、その共有が争いになり、(少なくとも) 1 つのリクエストが負ける必要があります。

複数のリクエスト間で共有されないリクエストごとの状態を維持してみてください。たとえば、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 に答える