summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2014-08-15 11:50:14 +0100
committerErik Johnston <erik@matrix.org>2014-08-15 11:50:14 +0100
commitd72f897f078fa72548522440149369293f0d12b2 (patch)
tree4e8d692713f73389b89dea4fdba719b696c9f3af /synapse
parentReimplement the get public rooms api to work with new DB schema (diff)
parentAdd a check to make sure that during state conflict res we only request a PDU... (diff)
downloadsynapse-d72f897f078fa72548522440149369293f0d12b2.tar.xz
Merge branch 'master' of github.com:matrix-org/synapse into sql_refactor
Conflicts:
	synapse/storage/stream.py
Diffstat (limited to 'synapse')
-rw-r--r--synapse/api/auth.py5
-rw-r--r--synapse/api/errors.py6
-rw-r--r--synapse/api/notifier.py4
-rw-r--r--synapse/api/urls.py20
-rwxr-xr-x[-rw-r--r--]synapse/app/homeserver.py114
-rw-r--r--synapse/federation/__init__.py2
-rw-r--r--synapse/federation/transport.py4
-rw-r--r--synapse/handlers/directory.py6
-rw-r--r--synapse/handlers/login.py6
-rw-r--r--synapse/handlers/presence.py20
-rw-r--r--synapse/handlers/room.py4
-rw-r--r--synapse/http/server.py24
-rw-r--r--synapse/rest/__init__.py27
-rw-r--r--synapse/rest/base.py3
-rw-r--r--synapse/rest/login.py6
-rw-r--r--synapse/rest/webclient.py45
-rw-r--r--synapse/server.py13
-rw-r--r--synapse/state.py17
-rw-r--r--synapse/storage/registration.py4
-rw-r--r--synapse/util/lockutils.py12
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))