Store Promise<Response> instead of Response for HTTP API transactions

This fixes a race whereby:
 - User hits an endpoint.
 - No cached transaction so executes main code.
 - User hits same endpoint.
 - No cache transaction so executes main code.
 - Main code finishes executing and caches response and returns.
 - Main code finishes executing and caches response and returns.

 This race is common in the wild when Synapse is struggling under load.
 This commit fixes the race by:
  - User hits an endpoint.
  - Caches the promise to execute the main code and executes main code.
  - User hits same endpoint.
  - Yields on the same promise as the first request.
  - Main code finishes executing and returns, unblocking both requests.
This commit is contained in:
Kegan Dougal 2016-11-10 14:49:26 +00:00
parent 6cc4fcf25c
commit 2771447c29
4 changed files with 68 additions and 88 deletions

View file

@ -19,7 +19,7 @@ from twisted.internet import defer
from synapse.http import servlet
from synapse.http.servlet import parse_json_object_from_request
from synapse.rest.client.v1.transactions import HttpTransactionStore
from synapse.rest.client.v1.transactions import HttpTransactionCache
from ._base import client_v2_patterns
@ -40,18 +40,25 @@ class SendToDeviceRestServlet(servlet.RestServlet):
super(SendToDeviceRestServlet, self).__init__()
self.hs = hs
self.auth = hs.get_auth()
self.txns = HttpTransactionStore()
self.txns = HttpTransactionCache()
self.device_message_handler = hs.get_device_message_handler()
@defer.inlineCallbacks
def on_PUT(self, request, message_type, txn_id):
try:
defer.returnValue(
self.txns.get_client_transaction(request, txn_id)
)
res_deferred = self.txns.get_client_transaction(request, txn_id)
res = yield res_deferred
defer.returnValue(res)
except KeyError:
pass
res_deferred = self._put(request, message_type, txn_id)
self.txns.store_client_transaction(request, txn_id, res_deferred)
res = yield res_deferred
defer.returnValue(res)
@defer.inlineCallbacks
def _put(self, request, message_type, txn_id):
requester = yield self.auth.get_user_by_req(request)
content = parse_json_object_from_request(request)
@ -63,7 +70,6 @@ class SendToDeviceRestServlet(servlet.RestServlet):
)
response = (200, {})
self.txns.store_client_transaction(request, txn_id, response)
defer.returnValue(response)