diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 8d2ba242e1..31852b29a5 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -18,7 +18,7 @@
from twisted.internet import defer
from synapse.api.constants import Membership
-from synapse.api.errors import AuthError, StoreError
+from synapse.api.errors import AuthError, StoreError, Codes
from synapse.api.events.room import (RoomTopicEvent, RoomMemberEvent,
MessageEvent, FeedbackEvent)
@@ -163,4 +163,5 @@ class Auth(object):
user_id = yield self.store.get_user_by_token(token=token)
defer.returnValue(self.hs.parse_userid(user_id))
except StoreError:
- raise AuthError(403, "Unrecognised access token.")
+ raise AuthError(403, "Unrecognised access token.",
+ errcode=Codes.UNKNOWN_TOKEN)
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 8b9766fab7..21ededc5ae 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -27,6 +27,7 @@ class Codes(object):
BAD_PAGINATION = "M_BAD_PAGINATION"
UNKNOWN = "M_UNKNOWN"
NOT_FOUND = "M_NOT_FOUND"
+ UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
class CodeMessageException(Exception):
@@ -74,7 +75,10 @@ class AuthError(SynapseError):
class EventStreamError(SynapseError):
"""An error raised when there a problem with the event stream."""
- pass
+ def __init__(self, *args, **kwargs):
+ if "errcode" not in kwargs:
+ kwargs["errcode"] = Codes.BAD_PAGINATION
+ super(EventStreamError, self).__init__(*args, **kwargs)
class LoginError(SynapseError):
diff --git a/synapse/api/notifier.py b/synapse/api/notifier.py
index 105a11401b..65b5a4ebb3 100644
--- a/synapse/api/notifier.py
+++ b/synapse/api/notifier.py
@@ -56,6 +56,10 @@ class Notifier(object):
if (event.type == RoomMemberEvent.TYPE and
event.content["membership"] == Membership.INVITE):
member_list.append(event.target_user_id)
+ # similarly, LEAVEs must be sent to the person leaving
+ if (event.type == RoomMemberEvent.TYPE and
+ event.content["membership"] == Membership.LEAVE):
+ member_list.append(event.target_user_id)
for user_id in member_list:
if user_id in self.stored_event_listeners:
diff --git a/synapse/api/urls.py b/synapse/api/urls.py
new file mode 100644
index 0000000000..04970adb71
--- /dev/null
+++ b/synapse/api/urls.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 matrix.org
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Contains the URL paths to prefix various aspects of the server with. """
+
+CLIENT_PREFIX = "/matrix/client/api/v1"
+FEDERATION_PREFIX = "/matrix/federation/v1"
+WEB_CLIENT_PREFIX = "/matrix/client"
\ No newline at end of file
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index 2fd7e0ae49..3429a29a6b 100644..100755
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -21,8 +21,12 @@ from synapse.server import HomeServer
from twisted.internet import reactor
from twisted.enterprise import adbapi
from twisted.python.log import PythonLoggingObserver
-from synapse.http.server import TwistedHttpServer
+from twisted.web.resource import Resource
+from twisted.web.static import File
+from twisted.web.server import Site
+from synapse.http.server import JsonResource, RootRedirect
from synapse.http.client import TwistedHttpClient
+from synapse.api.urls import CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX
from daemonize import Daemonize
@@ -36,12 +40,19 @@ logger = logging.getLogger(__name__)
class SynapseHomeServer(HomeServer):
- def build_http_server(self):
- return TwistedHttpServer()
def build_http_client(self):
return TwistedHttpClient()
+ def build_resource_for_client(self):
+ return JsonResource()
+
+ def build_resource_for_federation(self):
+ return JsonResource()
+
+ def build_resource_for_web_client(self):
+ return File("webclient") # TODO configurable?
+
def build_db_pool(self):
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
don't have to worry about overwriting existing content.
@@ -74,6 +85,98 @@ class SynapseHomeServer(HomeServer):
return pool
+ def create_resource_tree(self, web_client, redirect_root_to_web_client):
+ """Create the resource tree for this Home Server.
+
+ This in unduly complicated because Twisted does not support putting
+ child resources more than 1 level deep at a time.
+
+ Args:
+ web_client (bool): True to enable the web client.
+ redirect_root_to_web_client (bool): True to redirect '/' to the
+ location of the web client. This does nothing if web_client is not
+ True.
+ """
+ # list containing (path_str, Resource) e.g:
+ # [ ("/aaa/bbb/cc", Resource1), ("/aaa/dummy", Resource2) ]
+ desired_tree = [
+ (CLIENT_PREFIX, self.get_resource_for_client()),
+ (FEDERATION_PREFIX, self.get_resource_for_federation())
+ ]
+ if web_client:
+ logger.info("Adding the web client.")
+ desired_tree.append((WEB_CLIENT_PREFIX,
+ self.get_resource_for_web_client()))
+
+ if web_client and redirect_root_to_web_client:
+ self.root_resource = RootRedirect(WEB_CLIENT_PREFIX)
+ else:
+ self.root_resource = Resource()
+
+ # ideally we'd just use getChild and putChild but getChild doesn't work
+ # unless you give it a Request object IN ADDITION to the name :/ So
+ # instead, we'll store a copy of this mapping so we can actually add
+ # extra resources to existing nodes. See self._resource_id for the key.
+ resource_mappings = {}
+ for (full_path, resource) in desired_tree:
+ logging.info("Attaching %s to path %s", resource, full_path)
+ last_resource = self.root_resource
+ for path_seg in full_path.split('/')[1:-1]:
+ if not path_seg in last_resource.listNames():
+ # resource doesn't exist, so make a "dummy resource"
+ child_resource = Resource()
+ last_resource.putChild(path_seg, child_resource)
+ res_id = self._resource_id(last_resource, path_seg)
+ resource_mappings[res_id] = child_resource
+ last_resource = child_resource
+ else:
+ # we have an existing Resource, use that instead.
+ res_id = self._resource_id(last_resource, path_seg)
+ last_resource = resource_mappings[res_id]
+
+ # ===========================
+ # now attach the actual desired resource
+ last_path_seg = full_path.split('/')[-1]
+
+ # if there is already a resource here, thieve its children and
+ # replace it
+ res_id = self._resource_id(last_resource, last_path_seg)
+ if res_id in resource_mappings:
+ # there is a dummy resource at this path already, which needs
+ # to be replaced with the desired resource.
+ existing_dummy_resource = resource_mappings[res_id]
+ for child_name in existing_dummy_resource.listNames():
+ child_res_id = self._resource_id(existing_dummy_resource,
+ child_name)
+ child_resource = resource_mappings[child_res_id]
+ # steal the children
+ resource.putChild(child_name, child_resource)
+
+ # finally, insert the desired resource in the right place
+ last_resource.putChild(last_path_seg, resource)
+ res_id = self._resource_id(last_resource, last_path_seg)
+ resource_mappings[res_id] = resource
+
+ return self.root_resource
+
+ def _resource_id(self, resource, path_seg):
+ """Construct an arbitrary resource ID so you can retrieve the mapping
+ later.
+
+ If you want to represent resource A putChild resource B with path C,
+ the mapping should looks like _resource_id(A,C) = B.
+
+ Args:
+ resource (Resource): The *parent* Resource
+ path_seg (str): The name of the child Resource to be attached.
+ Returns:
+ str: A unique string which can be a key to the child Resource.
+ """
+ return "%s-%s" % (resource, path_seg)
+
+ def start_listening(self, port):
+ reactor.listenTCP(port, Site(self.root_resource))
+
def setup_logging(verbosity=0, filename=None, config_path=None):
""" Sets up logging with verbosity levels.
@@ -157,7 +260,10 @@ def setup():
hs.register_servlets()
- hs.get_http_server().start_listening(args.port)
+ hs.create_resource_tree(
+ web_client=args.webclient,
+ redirect_root_to_web_client=True)
+ hs.start_listening(args.port)
hs.build_db_pool()
diff --git a/synapse/federation/__init__.py b/synapse/federation/__init__.py
index ac0c10dc33..b15e7cf941 100644
--- a/synapse/federation/__init__.py
+++ b/synapse/federation/__init__.py
@@ -23,7 +23,7 @@ from .transport import TransportLayer
def initialize_http_replication(homeserver):
transport = TransportLayer(
homeserver.hostname,
- server=homeserver.get_http_server(),
+ server=homeserver.get_resource_for_federation(),
client=homeserver.get_http_client()
)
diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py
index e09dfc2670..50c3df4a5d 100644
--- a/synapse/federation/transport.py
+++ b/synapse/federation/transport.py
@@ -23,6 +23,7 @@ over a different (albeit still reliable) protocol.
from twisted.internet import defer
+from synapse.api.urls import FEDERATION_PREFIX as PREFIX
from synapse.util.logutils import log_function
import logging
@@ -33,9 +34,6 @@ import re
logger = logging.getLogger(__name__)
-PREFIX = "/matrix/federation/v1"
-
-
class TransportLayer(object):
"""This is a basic implementation of the transport layer that translates
transactions and other requests to/from HTTP.
diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py
index df98e39f69..7c89150d99 100644
--- a/synapse/handlers/directory.py
+++ b/synapse/handlers/directory.py
@@ -20,17 +20,11 @@ from ._base import BaseHandler
from synapse.api.errors import SynapseError
import logging
-import json
-import urllib
logger = logging.getLogger(__name__)
-# TODO(erikj): This needs to be factored out somewere
-PREFIX = "/matrix/client/api/v1"
-
-
class DirectoryHandler(BaseHandler):
def __init__(self, hs):
diff --git a/synapse/handlers/login.py b/synapse/handlers/login.py
index ca69829d77..0220fa0604 100644
--- a/synapse/handlers/login.py
+++ b/synapse/handlers/login.py
@@ -16,7 +16,7 @@
from twisted.internet import defer
from ._base import BaseHandler
-from synapse.api.errors import LoginError
+from synapse.api.errors import LoginError, Codes
import bcrypt
import logging
@@ -51,7 +51,7 @@ class LoginHandler(BaseHandler):
user_info = yield self.store.get_user_by_id(user_id=user)
if not user_info:
logger.warn("Attempted to login as %s but they do not exist.", user)
- raise LoginError(403, "")
+ raise LoginError(403, "", errcode=Codes.FORBIDDEN)
stored_hash = user_info[0]["password_hash"]
if bcrypt.checkpw(password, stored_hash):
@@ -62,4 +62,4 @@ class LoginHandler(BaseHandler):
defer.returnValue(token)
else:
logger.warn("Failed password login for user %s", user)
- raise LoginError(403, "")
\ No newline at end of file
+ raise LoginError(403, "", errcode=Codes.FORBIDDEN)
\ No newline at end of file
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index 8bdb0fe5c7..351ff305dc 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -177,7 +177,9 @@ class PresenceHandler(BaseHandler):
state = self._get_or_offline_usercache(target_user).get_state()
if "mtime" in state:
- state["mtime_age"] = self.clock.time_msec() - state.pop("mtime")
+ state["mtime_age"] = int(
+ self.clock.time_msec() - state.pop("mtime")
+ )
defer.returnValue(state)
@defer.inlineCallbacks
@@ -367,7 +369,9 @@ class PresenceHandler(BaseHandler):
p["observed_user"] = observed_user
p.update(self._get_or_offline_usercache(observed_user).get_state())
if "mtime" in p:
- p["mtime_age"] = self.clock.time_msec() - p.pop("mtime")
+ p["mtime_age"] = int(
+ self.clock.time_msec() - p.pop("mtime")
+ )
defer.returnValue(presence)
@@ -560,7 +564,9 @@ class PresenceHandler(BaseHandler):
if "mtime" in state:
state = dict(state)
- state["mtime_age"] = self.clock.time_msec() - state.pop("mtime")
+ state["mtime_age"] = int(
+ self.clock.time_msec() - state.pop("mtime")
+ )
yield self.federation.send_edu(
destination=destination,
@@ -598,7 +604,9 @@ class PresenceHandler(BaseHandler):
del state["user_id"]
if "mtime_age" in state:
- state["mtime"] = self.clock.time_msec() - state.pop("mtime_age")
+ state["mtime"] = int(
+ self.clock.time_msec() - state.pop("mtime_age")
+ )
statuscache = self._get_or_make_usercache(user)
@@ -720,6 +728,8 @@ class UserPresenceCache(object):
content["user_id"] = user.to_string()
if "mtime" in content:
- content["mtime_age"] = clock.time_msec() - content.pop("mtime")
+ content["mtime_age"] = int(
+ clock.time_msec() - content.pop("mtime")
+ )
return {"type": "m.presence", "content": content}
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index 5c1b59dbc9..432d13982a 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -94,10 +94,10 @@ class MessageHandler(BaseHandler):
event.room_id
)
- yield self.hs.get_federation().handle_new_event(event)
-
self.notifier.on_new_room_event(event, store_id)
+ yield self.hs.get_federation().handle_new_event(event)
+
@defer.inlineCallbacks
def get_messages(self, user_id=None, room_id=None, pagin_config=None,
feedback=False):
diff --git a/synapse/http/server.py b/synapse/http/server.py
index d7f4b691bc..bad2738bde 100644
--- a/synapse/http/server.py
+++ b/synapse/http/server.py
@@ -22,6 +22,7 @@ from synapse.api.errors import cs_exception, CodeMessageException
from twisted.internet import defer, reactor
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
+from twisted.web.util import redirectTo
import collections
import logging
@@ -52,10 +53,9 @@ class HttpServer(object):
pass
-# The actual HTTP server impl, using twisted http server
-class TwistedHttpServer(HttpServer, resource.Resource):
- """ This wraps the twisted HTTP server, and triggers the correct callbacks
- on the transport_layer.
+class JsonResource(HttpServer, resource.Resource):
+ """ This implements the HttpServer interface and provides JSON support for
+ Resources.
Register callbacks via register_path()
"""
@@ -160,6 +160,22 @@ class TwistedHttpServer(HttpServer, resource.Resource):
return False
+class RootRedirect(resource.Resource):
+ """Redirects the root '/' path to another path."""
+
+ def __init__(self, path):
+ resource.Resource.__init__(self)
+ self.url = path
+
+ def render_GET(self, request):
+ return redirectTo(self.url, request)
+
+ def getChild(self, name, request):
+ if len(name) == 0:
+ return self # select ourselves as the child to render
+ return resource.Resource.getChild(self, name, request)
+
+
def respond_with_json_bytes(request, code, json_bytes, send_cors=False):
"""Sends encoded JSON in response to the given request.
diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py
index 74a372e2ff..da18933b63 100644
--- a/synapse/rest/__init__.py
+++ b/synapse/rest/__init__.py
@@ -15,8 +15,7 @@
from . import (
- room, events, register, login, profile, public, presence, im, directory,
- webclient
+ room, events, register, login, profile, public, presence, im, directory
)
@@ -32,19 +31,15 @@ class RestServletFactory(object):
"""
def __init__(self, hs):
- http_server = hs.get_http_server()
+ client_resource = hs.get_resource_for_client()
# TODO(erikj): There *must* be a better way of doing this.
- room.register_servlets(hs, http_server)
- events.register_servlets(hs, http_server)
- register.register_servlets(hs, http_server)
- login.register_servlets(hs, http_server)
- profile.register_servlets(hs, http_server)
- public.register_servlets(hs, http_server)
- presence.register_servlets(hs, http_server)
- im.register_servlets(hs, http_server)
- directory.register_servlets(hs, http_server)
-
- def register_web_client(self, hs):
- http_server = hs.get_http_server()
- webclient.register_servlets(hs, http_server)
+ room.register_servlets(hs, client_resource)
+ events.register_servlets(hs, client_resource)
+ register.register_servlets(hs, client_resource)
+ login.register_servlets(hs, client_resource)
+ profile.register_servlets(hs, client_resource)
+ public.register_servlets(hs, client_resource)
+ presence.register_servlets(hs, client_resource)
+ im.register_servlets(hs, client_resource)
+ directory.register_servlets(hs, client_resource)
diff --git a/synapse/rest/base.py b/synapse/rest/base.py
index 65d417f757..6a88cbe866 100644
--- a/synapse/rest/base.py
+++ b/synapse/rest/base.py
@@ -14,6 +14,7 @@
# limitations under the License.
""" This module contains base REST classes for constructing REST servlets. """
+from synapse.api.urls import CLIENT_PREFIX
import re
@@ -27,7 +28,7 @@ def client_path_pattern(path_regex):
Returns:
SRE_Pattern
"""
- return re.compile("^/matrix/client/api/v1" + path_regex)
+ return re.compile("^" + CLIENT_PREFIX + path_regex)
class RestServlet(object):
diff --git a/synapse/rest/login.py b/synapse/rest/login.py
index 88a3218332..bcf63fd2ab 100644
--- a/synapse/rest/login.py
+++ b/synapse/rest/login.py
@@ -16,6 +16,7 @@
from twisted.internet import defer
from synapse.api.errors import SynapseError
+from synapse.types import UserID
from base import RestServlet, client_path_pattern
import json
@@ -45,12 +46,17 @@ class LoginRestServlet(RestServlet):
@defer.inlineCallbacks
def do_password_login(self, login_submission):
+ if not login_submission["user"].startswith('@'):
+ login_submission["user"] = UserID.create_local(
+ login_submission["user"], self.hs).to_string()
+
handler = self.handlers.login_handler
token = yield handler.login(
user=login_submission["user"],
password=login_submission["password"])
result = {
+ "user_id": login_submission["user"], # may have changed
"access_token": token,
"home_server": self.hs.hostname,
}
diff --git a/synapse/rest/webclient.py b/synapse/rest/webclient.py
deleted file mode 100644
index 75a425c14c..0000000000
--- a/synapse/rest/webclient.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from synapse.rest.base import RestServlet
-
-import logging
-import re
-
-logger = logging.getLogger(__name__)
-
-
-class WebClientRestServlet(RestServlet):
- # No PATTERN; we have custom dispatch rules here
-
- def register(self, http_server):
- http_server.register_path("GET",
- re.compile("^/$"),
- self.on_GET_redirect)
- http_server.register_path("GET",
- re.compile("^/matrix/client$"),
- self.on_GET)
-
- def on_GET(self, request):
- return (200, "not implemented")
-
- def on_GET_redirect(self, request):
- request.setHeader("Location", request.uri + "matrix/client")
- return (302, None)
-
-
-def register_servlets(hs, http_server):
- logger.info("Registering web client.")
- WebClientRestServlet(hs).register(http_server)
\ No newline at end of file
diff --git a/synapse/server.py b/synapse/server.py
index 96830a88b1..0f7ac352ae 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -55,7 +55,6 @@ class BaseHomeServer(object):
DEPENDENCIES = [
'clock',
- 'http_server',
'http_client',
'db_pool',
'persistence_service',
@@ -70,6 +69,9 @@ class BaseHomeServer(object):
'room_lock_manager',
'notifier',
'distributor',
+ 'resource_for_client',
+ 'resource_for_federation',
+ 'resource_for_web_client',
]
def __init__(self, hostname, **kwargs):
@@ -135,7 +137,9 @@ class HomeServer(BaseHomeServer):
required.
It still requires the following to be specified by the caller:
- http_server
+ resource_for_client
+ resource_for_web_client
+ resource_for_federation
http_client
db_pool
"""
@@ -178,9 +182,6 @@ class HomeServer(BaseHomeServer):
def register_servlets(self):
""" Register all servlets associated with this HomeServer.
-
- Args:
- host_web_client (bool): True to host the web client as well.
"""
# Simply building the ServletFactory is sufficient to have it register
- factory = self.get_rest_servlet_factory()
+ self.get_rest_servlet_factory()
diff --git a/synapse/state.py b/synapse/state.py
index b081de8f4f..4f8b4d9760 100644
--- a/synapse/state.py
+++ b/synapse/state.py
@@ -157,7 +157,10 @@ class StateHandler(object):
defer.returnValue(True)
return
- if new_branch[-1] == current_branch[-1]:
+ n = new_branch[-1]
+ c = current_branch[-1]
+
+ if n.pdu_id == c.pdu_id and n.origin == c.origin:
# We have all the PDUs we need, so we can just do the conflict
# resolution.
@@ -188,10 +191,18 @@ class StateHandler(object):
key=lambda x: x.depth
)
+ pdu_id = missing_prev.prev_state_id
+ origin = missing_prev.prev_state_origin
+
+ is_missing = yield self.store.get_pdu(pdu_id, origin) is None
+
+ if not is_missing:
+ raise Exception("Conflict resolution failed.")
+
yield self._replication.get_pdu(
destination=missing_prev.origin,
- pdu_origin=missing_prev.prev_state_origin,
- pdu_id=missing_prev.prev_state_id,
+ pdu_origin=origin,
+ pdu_id=pdu_id,
outlier=True
)
diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py
index 68cdfbb4ca..b1e4196435 100644
--- a/synapse/storage/registration.py
+++ b/synapse/storage/registration.py
@@ -17,7 +17,7 @@ from twisted.internet import defer
from sqlite3 import IntegrityError
-from synapse.api.errors import StoreError
+from synapse.api.errors import StoreError, Codes
from ._base import SQLBaseStore
@@ -73,7 +73,7 @@ class RegistrationStore(SQLBaseStore):
"VALUES (?,?,?)",
[user_id, password_hash, now])
except IntegrityError:
- raise StoreError(400, "User ID already taken.")
+ raise StoreError(400, "User ID already taken.", errcode=Codes.USER_IN_USE)
# it's possible for this to get a conflict, but only for a single user
# since tokens are namespaced based on their user ID
diff --git a/synapse/util/lockutils.py b/synapse/util/lockutils.py
index 758be0b901..d0bb50d035 100644
--- a/synapse/util/lockutils.py
+++ b/synapse/util/lockutils.py
@@ -24,9 +24,10 @@ logger = logging.getLogger(__name__)
class Lock(object):
- def __init__(self, deferred):
+ def __init__(self, deferred, key):
self._deferred = deferred
self.released = False
+ self.key = key
def release(self):
self.released = True
@@ -38,9 +39,10 @@ class Lock(object):
self.release()
def __enter__(self):
- return self
+ return self
def __exit__(self, type, value, traceback):
+ logger.debug("Releasing lock for key=%r", self.key)
self.release()
@@ -63,6 +65,10 @@ class LockManager(object):
self._lock_deferreds[key] = new_deferred
if old_deferred:
+ logger.debug("Queueing on lock for key=%r", key)
yield old_deferred
+ logger.debug("Obtained lock for key=%r", key)
+ else:
+ logger.debug("Entering uncontended lock for key=%r", key)
- defer.returnValue(Lock(new_deferred))
+ defer.returnValue(Lock(new_deferred, key))
|