2012-06-14 55 views
0

我有以下資源來處理與扭曲網頁的HTTP POST請求:陸續Twisted web - write()在finish()之後調用?

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工具(從阿帕奇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被稱爲後。我究竟做錯了什麼?

+0

您可能需要您的結果記錄併發訪問:用'完成關閉它()',而其他請求試圖在它來寫。 – Mualig

+0

是的,我有併發訪問,我一次運行5個請求。我是扭曲網絡的新手,如果我有併發請求會發生什麼?我不是每次都得到一個單獨的Resource類實例嗎? – PawelRoman

+0

由於您只有1個'RootResource'鏈接到您的'reactor',它們監聽您的TCP請求。恕我直言,你只有1'創建了'資源'。嘗試評論'Finish()',並在收到所有請求後立即執行。 – Mualig

回答

0

正如Mualig在他的評論中所建議的那樣,您只有RootResource的一個實例。當您分配到render_POST中的self.requestself.d時,您將覆蓋那些屬性已有的值。如果兩個請求在同一時間到達,那麼這是一個問題。第一個RequestDeferred被丟棄,並被與第二個到達的請求關聯的新的替換。稍後,當您的數據庫操作完成時,第二個請求會獲得兩個結果,第一個請求將不會收到任何結果。

這是併發編程中一個常見錯誤的例子。您的每個請求狀態保留在多個請求之間共享的地方。當多個請求同時處理時,該共享變成一場鬥爭,並且(至少)一個請求不得不失敗。

嘗試保持您的每個請求狀態不會在多個請求之間共享。例如,嘗試保持它的遞延:

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 
相關問題