diff options
Diffstat (limited to 'synapse')
-rw-r--r-- | synapse/api/auth.py | 5 | ||||
-rw-r--r-- | synapse/api/errors.py | 6 | ||||
-rw-r--r-- | synapse/api/notifier.py | 4 | ||||
-rw-r--r-- | synapse/api/urls.py | 20 | ||||
-rwxr-xr-x[-rw-r--r--] | synapse/app/homeserver.py | 114 | ||||
-rw-r--r-- | synapse/federation/__init__.py | 2 | ||||
-rw-r--r-- | synapse/federation/transport.py | 4 | ||||
-rw-r--r-- | synapse/handlers/directory.py | 6 | ||||
-rw-r--r-- | synapse/handlers/login.py | 6 | ||||
-rw-r--r-- | synapse/handlers/presence.py | 20 | ||||
-rw-r--r-- | synapse/handlers/room.py | 4 | ||||
-rw-r--r-- | synapse/http/server.py | 24 | ||||
-rw-r--r-- | synapse/rest/__init__.py | 27 | ||||
-rw-r--r-- | synapse/rest/base.py | 3 | ||||
-rw-r--r-- | synapse/rest/login.py | 6 | ||||
-rw-r--r-- | synapse/rest/webclient.py | 45 | ||||
-rw-r--r-- | synapse/server.py | 13 | ||||
-rw-r--r-- | synapse/state.py | 17 | ||||
-rw-r--r-- | synapse/storage/registration.py | 4 | ||||
-rw-r--r-- | synapse/util/lockutils.py | 12 |
20 files changed, 235 insertions, 107 deletions
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)) |