We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Maximum recursion depth exceeded while calling a Python object
Noticed this exception during buildbot start. I am using Buildbot 1.1.1

2018-04-11 21:06:54-0700 [-] Unhandled error in Deferred:
2018-04-11 21:06:54-0700 [-] Unhandled Error
        Traceback (most recent call last):
          File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 1442, in gotResult
            _inlineCallbacks(r, g, deferred)
          File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 1429, in _inlineCallbacks
            deferred.callback(e.value)
          File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 459, in callback
            self._startRunCallbacks(result)
          File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 567, in _startRunCallbacks
            self._runCallbacks()
        --- <exception caught here> ---
          File "/usr/lib64/python2.7/site-packages/twisted/internet/defer.py", line 653, in _runCallbacks
            current.result = callback(current.result, *args, **kw)
          File "/usr/lib64/python2.7/site-packages/twisted/spread/pb.py", line 1117, in _sendFailureOrError
            self._sendFailure(fail, requestID)
          File "/usr/lib64/python2.7/site-packages/twisted/spread/pb.py", line 1129, in _sendFailure
            log.msg("Peer will receive following PB traceback:")
          File "/usr/lib64/python2.7/site-packages/twisted/python/threadable.py", line 53, in sync 
            return function(self, *args, **kwargs)
          File "/usr/lib64/python2.7/site-packages/twisted/python/log.py", line 286, in msg
            _publishNew(self._publishPublisher, actualEventDict, textFromEventDict)
          File "/usr/lib64/python2.7/site-packages/twisted/logger/_legacy.py", line 154, in publishToNewObserver
            observer(eventDict)
          File "/usr/lib64/python2.7/site-packages/twisted/logger/_observer.py", line 133, in __call__
            brokenObservers.append((observer, Failure()))
        exceptions.RuntimeError: maximum recursion depth exceeded while calling a Python object

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/pbmanager.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 from __future__ import absolute_import
17 from __future__ import print_function
18 
19 from twisted.application import strports
20 from twisted.cred import checkers
21 from twisted.cred import credentials
22 from twisted.cred import error
23 from twisted.cred import portal
24 from twisted.internet import defer
25 from twisted.python import log
26 from twisted.spread import pb
27 from zope.interface import implementer
28 
29 from buildbot.process.properties import Properties
30 from buildbot.util import bytes2unicode
31 from buildbot.util import service
32 from buildbot.util import unicode2bytes
33 
34 debug = False
35 
36 
37 class PBManager(service.AsyncMultiService):
38 
39     """
40     A centralized manager for PB ports and authentication on them.
41 
42     Allows various pieces of code to request a (port, username) combo, along
43     with a password and a perspective factory.
44     """
45 
46     def __init__(self):
47         service.AsyncMultiService.__init__(self)
48         self.setName('pbmanager')
49         self.dispatchers = {}
50 
51     def register(self, portstr, username, password, pfactory):
52         """
53         Register a perspective factory PFACTORY to be executed when a PB
54         connection arrives on PORTSTR with USERNAME/PASSWORD.  Returns a
55         Registration object which can be used to unregister later.
56         """
57         # do some basic normalization of portstrs
58         if isinstance(portstr, type(0)) or ':' not in portstr:
59             portstr = "tcp:%s" % portstr
60 
61         reg = Registration(self, portstr, username)
62 
63         if portstr not in self.dispatchers:
64             disp = self.dispatchers[portstr] = Dispatcher(portstr)
65             disp.setServiceParent(self)
66         else:
67             disp = self.dispatchers[portstr]
68 
69         disp.register(username, password, pfactory)
70 
71         return reg
72 
73     def _unregister(self, registration):
74         disp = self.dispatchers[registration.portstr]
75         disp.unregister(registration.username)
76         registration.username = None
77         if not disp.users:
78             disp = self.dispatchers[registration.portstr]
79             del self.dispatchers[registration.portstr]
80             return defer.maybeDeferred(disp.disownServiceParent)
81         return defer.succeed(None)
82 
83 
84 class Registration(object):
85 
86     def __init__(self, pbmanager, portstr, username):
87         self.portstr = portstr
88         "portstr this registration is active on"
89         self.username = username
90         "username of this registration"
91 
92         self.pbmanager = pbmanager
93 
94     def __repr__(self):
95         return "<pbmanager.Registration for %s on %s>" % \
96             (self.username, self.portstr)
97 
98     def unregister(self):
99         """
100         Unregister this registration, removing the username from the port, and
101         closing the port if there are no more users left.  Returns a Deferred.
102         """
103         return self.pbmanager._unregister(self)
104 
105     def getPort(self):
106         """
107         Helper method for testing; returns the TCP port used for this
108         registration, even if it was specified as 0 and thus allocated by the
109         OS.
110         """
111         disp = self.pbmanager.dispatchers[self.portstr]
112         return disp.port.getHost().port
113 
114 
115 @implementer(portal.IRealm, checkers.ICredentialsChecker)
116 class Dispatcher(service.AsyncService):
117 
118     credentialInterfaces = [credentials.IUsernamePassword,
119                             credentials.IUsernameHashedPassword]
120 
121     def __init__(self, portstr):
122         self.portstr = portstr
123         self.users = {}
124 
125         # there's lots of stuff to set up for a PB connection!
126         self.portal = portal.Portal(self)
127         self.portal.registerChecker(self)
128         self.serverFactory = pb.PBServerFactory(self.portal)
129         self.serverFactory.unsafeTracebacks = True
130         self.port = None
131 
132     def __repr__(self):
133         return "<pbmanager.Dispatcher for %s on %s>" % \
134             (", ".join(list(self.users)), self.portstr)
135 
136     def startService(self):
137         assert not self.port
138         self.port = strports.listen(self.portstr, self.serverFactory)
139         return service.AsyncService.startService(self)
140 
141     def stopService(self):
142         # stop listening on the port when shut down
143         assert self.port
144         port, self.port = self.port, None
145         d = defer.maybeDeferred(port.stopListening)
146         d.addCallback(lambda _: service.AsyncService.stopService(self))
147         return d
148 
149     def register(self, username, password, pfactory):
150         if debug:
151             log.msg("registering username '%s' on pb port %s: %s"
152                     % (username, self.portstr, pfactory))
153         if username in self.users:
154             raise KeyError("username '%s' is already registered on PB port %s"
155                            % (username, self.portstr))
156         self.users[username] = (password, pfactory)
157 
158     def unregister(self, username):
159         if debug:
160             log.msg("unregistering username '%s' on pb port %s"
161                     % (username, self.portstr))
162         del self.users[username]
163 
164     # IRealm
165 
166     def requestAvatar(self, username, mind, interface):
167         assert interface == pb.IPerspective
168         username = bytes2unicode(username)
169         if username not in self.users:
170             d = defer.succeed(None)  # no perspective
171         else:
172             _, afactory = self.users.get(username)
173             d = defer.maybeDeferred(afactory, mind, username)
174 
175         # check that we got a perspective
176         @d.addCallback
177         def check(persp):
178             if not persp:
179                 raise ValueError("no perspective for '%s'" % username)
180             return persp
181 
182         # call the perspective's attached(mind)
183         @d.addCallback
184         def call_attached(persp):
185             d = defer.maybeDeferred(persp.attached, mind)
186             d.addCallback(lambda _: persp)  # keep returning the perspective
187             return d
188 
189         # return the tuple requestAvatar is expected to return
190         @d.addCallback
191         def done(persp):
192             return (pb.IPerspective, persp, lambda: persp.detached(mind))
193 
194         return d
195 
196     # ICredentialsChecker
197 
198     @defer.inlineCallbacks
199     def requestAvatarId(self, creds):
200         p = Properties()
201         p.master = self.master
202         username = bytes2unicode(creds.username)
203         try:
204             yield self.master.initLock.acquire()
205             if username in self.users:
206                 password, _ = self.users[username]
207                 password = yield p.render(password)
208                 matched = yield defer.maybeDeferred(
209                     creds.checkPassword, unicode2bytes(password))
210                 if not matched:
211                     log.msg("invalid login from user '{}'".format(username))
212                     raise error.UnauthorizedLogin()
213                 defer.returnValue(creds.username)
214             log.msg("invalid login from unknown user '{}'".format(username))
215             raise error.UnauthorizedLogin()
216         finally:
217             yield self.master.initLock.release()
218
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/pbmanager.py b/master/buildbot/pbmanager.py
--- a/master/buildbot/pbmanager.py
+++ b/master/buildbot/pbmanager.py
@@ -30,6 +30,7 @@
 from buildbot.util import bytes2unicode
 from buildbot.util import service
 from buildbot.util import unicode2bytes
+from buildbot.util.eventual import eventually
 
 debug = False
 
@@ -214,4 +215,4 @@
             log.msg("invalid login from unknown user '%s'" % creds.username)
             raise error.UnauthorizedLogin()
         finally:
-            yield self.master.initLock.release()
+            eventually(self.master.initLock.release)

{"golden_diff": "diff --git a/master/buildbot/pbmanager.py b/master/buildbot/pbmanager.py\n--- a/master/buildbot/pbmanager.py\n+++ b/master/buildbot/pbmanager.py\n@@ -30,6 +30,7 @@\n from buildbot.util import bytes2unicode\n from buildbot.util import service\n from buildbot.util import unicode2bytes\n+from buildbot.util.eventual import eventually\n \n debug = False\n \n@@ -214,4 +215,4 @@\n             log.msg(\"invalid login from unknown user '%s'\" % creds.username)\n             raise error.UnauthorizedLogin()\n         finally:\n-            yield self.master.initLock.release()\n+            eventually(self.master.initLock.release)\n", "issue": "Maximum recursion depth exceeded while calling a Python object\nNoticed this exception during buildbot start. I am using Buildbot 1.1.1\r\n\r\n2018-04-11 21:06:54-0700 [-] Unhandled error in Deferred:\r\n2018-04-11 21:06:54-0700 [-] Unhandled Error\r\n        Traceback (most recent call last):\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/internet/defer.py\", line 1442, in gotResult\r\n            _inlineCallbacks(r, g, deferred)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/internet/defer.py\", line 1429, in _inlineCallbacks\r\n            deferred.callback(e.value)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/internet/defer.py\", line 459, in callback\r\n            self._startRunCallbacks(result)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/internet/defer.py\", line 567, in _startRunCallbacks\r\n            self._runCallbacks()\r\n        --- <exception caught here> ---\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/internet/defer.py\", line 653, in _runCallbacks\r\n            current.result = callback(current.result, *args, **kw)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/spread/pb.py\", line 1117, in _sendFailureOrError\r\n            self._sendFailure(fail, requestID)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/spread/pb.py\", line 1129, in _sendFailure\r\n            log.msg(\"Peer will receive following PB traceback:\")\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/python/threadable.py\", line 53, in sync \r\n            return function(self, *args, **kwargs)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/python/log.py\", line 286, in msg\r\n            _publishNew(self._publishPublisher, actualEventDict, textFromEventDict)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/logger/_legacy.py\", line 154, in publishToNewObserver\r\n            observer(eventDict)\r\n          File \"/usr/lib64/python2.7/site-packages/twisted/logger/_observer.py\", line 133, in __call__\r\n            brokenObservers.append((observer, Failure()))\r\n        exceptions.RuntimeError: maximum recursion depth exceeded while calling a Python object\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nfrom twisted.application import strports\nfrom twisted.cred import checkers\nfrom twisted.cred import credentials\nfrom twisted.cred import error\nfrom twisted.cred import portal\nfrom twisted.internet import defer\nfrom twisted.python import log\nfrom twisted.spread import pb\nfrom zope.interface import implementer\n\nfrom buildbot.process.properties import Properties\nfrom buildbot.util import bytes2unicode\nfrom buildbot.util import service\nfrom buildbot.util import unicode2bytes\n\ndebug = False\n\n\nclass PBManager(service.AsyncMultiService):\n\n    \"\"\"\n    A centralized manager for PB ports and authentication on them.\n\n    Allows various pieces of code to request a (port, username) combo, along\n    with a password and a perspective factory.\n    \"\"\"\n\n    def __init__(self):\n        service.AsyncMultiService.__init__(self)\n        self.setName('pbmanager')\n        self.dispatchers = {}\n\n    def register(self, portstr, username, password, pfactory):\n        \"\"\"\n        Register a perspective factory PFACTORY to be executed when a PB\n        connection arrives on PORTSTR with USERNAME/PASSWORD.  Returns a\n        Registration object which can be used to unregister later.\n        \"\"\"\n        # do some basic normalization of portstrs\n        if isinstance(portstr, type(0)) or ':' not in portstr:\n            portstr = \"tcp:%s\" % portstr\n\n        reg = Registration(self, portstr, username)\n\n        if portstr not in self.dispatchers:\n            disp = self.dispatchers[portstr] = Dispatcher(portstr)\n            disp.setServiceParent(self)\n        else:\n            disp = self.dispatchers[portstr]\n\n        disp.register(username, password, pfactory)\n\n        return reg\n\n    def _unregister(self, registration):\n        disp = self.dispatchers[registration.portstr]\n        disp.unregister(registration.username)\n        registration.username = None\n        if not disp.users:\n            disp = self.dispatchers[registration.portstr]\n            del self.dispatchers[registration.portstr]\n            return defer.maybeDeferred(disp.disownServiceParent)\n        return defer.succeed(None)\n\n\nclass Registration(object):\n\n    def __init__(self, pbmanager, portstr, username):\n        self.portstr = portstr\n        \"portstr this registration is active on\"\n        self.username = username\n        \"username of this registration\"\n\n        self.pbmanager = pbmanager\n\n    def __repr__(self):\n        return \"<pbmanager.Registration for %s on %s>\" % \\\n            (self.username, self.portstr)\n\n    def unregister(self):\n        \"\"\"\n        Unregister this registration, removing the username from the port, and\n        closing the port if there are no more users left.  Returns a Deferred.\n        \"\"\"\n        return self.pbmanager._unregister(self)\n\n    def getPort(self):\n        \"\"\"\n        Helper method for testing; returns the TCP port used for this\n        registration, even if it was specified as 0 and thus allocated by the\n        OS.\n        \"\"\"\n        disp = self.pbmanager.dispatchers[self.portstr]\n        return disp.port.getHost().port\n\n\n@implementer(portal.IRealm, checkers.ICredentialsChecker)\nclass Dispatcher(service.AsyncService):\n\n    credentialInterfaces = [credentials.IUsernamePassword,\n                            credentials.IUsernameHashedPassword]\n\n    def __init__(self, portstr):\n        self.portstr = portstr\n        self.users = {}\n\n        # there's lots of stuff to set up for a PB connection!\n        self.portal = portal.Portal(self)\n        self.portal.registerChecker(self)\n        self.serverFactory = pb.PBServerFactory(self.portal)\n        self.serverFactory.unsafeTracebacks = True\n        self.port = None\n\n    def __repr__(self):\n        return \"<pbmanager.Dispatcher for %s on %s>\" % \\\n            (\", \".join(list(self.users)), self.portstr)\n\n    def startService(self):\n        assert not self.port\n        self.port = strports.listen(self.portstr, self.serverFactory)\n        return service.AsyncService.startService(self)\n\n    def stopService(self):\n        # stop listening on the port when shut down\n        assert self.port\n        port, self.port = self.port, None\n        d = defer.maybeDeferred(port.stopListening)\n        d.addCallback(lambda _: service.AsyncService.stopService(self))\n        return d\n\n    def register(self, username, password, pfactory):\n        if debug:\n            log.msg(\"registering username '%s' on pb port %s: %s\"\n                    % (username, self.portstr, pfactory))\n        if username in self.users:\n            raise KeyError(\"username '%s' is already registered on PB port %s\"\n                           % (username, self.portstr))\n        self.users[username] = (password, pfactory)\n\n    def unregister(self, username):\n        if debug:\n            log.msg(\"unregistering username '%s' on pb port %s\"\n                    % (username, self.portstr))\n        del self.users[username]\n\n    # IRealm\n\n    def requestAvatar(self, username, mind, interface):\n        assert interface == pb.IPerspective\n        username = bytes2unicode(username)\n        if username not in self.users:\n            d = defer.succeed(None)  # no perspective\n        else:\n            _, afactory = self.users.get(username)\n            d = defer.maybeDeferred(afactory, mind, username)\n\n        # check that we got a perspective\n        @d.addCallback\n        def check(persp):\n            if not persp:\n                raise ValueError(\"no perspective for '%s'\" % username)\n            return persp\n\n        # call the perspective's attached(mind)\n        @d.addCallback\n        def call_attached(persp):\n            d = defer.maybeDeferred(persp.attached, mind)\n            d.addCallback(lambda _: persp)  # keep returning the perspective\n            return d\n\n        # return the tuple requestAvatar is expected to return\n        @d.addCallback\n        def done(persp):\n            return (pb.IPerspective, persp, lambda: persp.detached(mind))\n\n        return d\n\n    # ICredentialsChecker\n\n    @defer.inlineCallbacks\n    def requestAvatarId(self, creds):\n        p = Properties()\n        p.master = self.master\n        username = bytes2unicode(creds.username)\n        try:\n            yield self.master.initLock.acquire()\n            if username in self.users:\n                password, _ = self.users[username]\n                password = yield p.render(password)\n                matched = yield defer.maybeDeferred(\n                    creds.checkPassword, unicode2bytes(password))\n                if not matched:\n                    log.msg(\"invalid login from user '{}'\".format(username))\n                    raise error.UnauthorizedLogin()\n                defer.returnValue(creds.username)\n            log.msg(\"invalid login from unknown user '{}'\".format(username))\n            raise error.UnauthorizedLogin()\n        finally:\n            yield self.master.initLock.release()\n", "path": "master/buildbot/pbmanager.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nfrom twisted.application import strports\nfrom twisted.cred import checkers\nfrom twisted.cred import credentials\nfrom twisted.cred import error\nfrom twisted.cred import portal\nfrom twisted.internet import defer\nfrom twisted.python import log\nfrom twisted.spread import pb\nfrom zope.interface import implementer\n\nfrom buildbot.process.properties import Properties\nfrom buildbot.util import bytes2unicode\nfrom buildbot.util import service\nfrom buildbot.util import unicode2bytes\nfrom buildbot.util.eventual import eventually\n\ndebug = False\n\n\nclass PBManager(service.AsyncMultiService):\n\n    \"\"\"\n    A centralized manager for PB ports and authentication on them.\n\n    Allows various pieces of code to request a (port, username) combo, along\n    with a password and a perspective factory.\n    \"\"\"\n\n    def __init__(self):\n        service.AsyncMultiService.__init__(self)\n        self.setName('pbmanager')\n        self.dispatchers = {}\n\n    def register(self, portstr, username, password, pfactory):\n        \"\"\"\n        Register a perspective factory PFACTORY to be executed when a PB\n        connection arrives on PORTSTR with USERNAME/PASSWORD.  Returns a\n        Registration object which can be used to unregister later.\n        \"\"\"\n        # do some basic normalization of portstrs\n        if isinstance(portstr, type(0)) or ':' not in portstr:\n            portstr = \"tcp:%s\" % portstr\n\n        reg = Registration(self, portstr, username)\n\n        if portstr not in self.dispatchers:\n            disp = self.dispatchers[portstr] = Dispatcher(portstr)\n            disp.setServiceParent(self)\n        else:\n            disp = self.dispatchers[portstr]\n\n        disp.register(username, password, pfactory)\n\n        return reg\n\n    def _unregister(self, registration):\n        disp = self.dispatchers[registration.portstr]\n        disp.unregister(registration.username)\n        registration.username = None\n        if not disp.users:\n            disp = self.dispatchers[registration.portstr]\n            del self.dispatchers[registration.portstr]\n            return defer.maybeDeferred(disp.disownServiceParent)\n        return defer.succeed(None)\n\n\nclass Registration(object):\n\n    def __init__(self, pbmanager, portstr, username):\n        self.portstr = portstr\n        \"portstr this registration is active on\"\n        self.username = username\n        \"username of this registration\"\n\n        self.pbmanager = pbmanager\n\n    def __repr__(self):\n        return \"<pbmanager.Registration for %s on %s>\" % \\\n            (self.username, self.portstr)\n\n    def unregister(self):\n        \"\"\"\n        Unregister this registration, removing the username from the port, and\n        closing the port if there are no more users left.  Returns a Deferred.\n        \"\"\"\n        return self.pbmanager._unregister(self)\n\n    def getPort(self):\n        \"\"\"\n        Helper method for testing; returns the TCP port used for this\n        registration, even if it was specified as 0 and thus allocated by the\n        OS.\n        \"\"\"\n        disp = self.pbmanager.dispatchers[self.portstr]\n        return disp.port.getHost().port\n\n\n@implementer(portal.IRealm, checkers.ICredentialsChecker)\nclass Dispatcher(service.AsyncService):\n\n    credentialInterfaces = [credentials.IUsernamePassword,\n                            credentials.IUsernameHashedPassword]\n\n    def __init__(self, portstr):\n        self.portstr = portstr\n        self.users = {}\n\n        # there's lots of stuff to set up for a PB connection!\n        self.portal = portal.Portal(self)\n        self.portal.registerChecker(self)\n        self.serverFactory = pb.PBServerFactory(self.portal)\n        self.serverFactory.unsafeTracebacks = True\n        self.port = None\n\n    def __repr__(self):\n        return \"<pbmanager.Dispatcher for %s on %s>\" % \\\n            (\", \".join(list(self.users)), self.portstr)\n\n    def startService(self):\n        assert not self.port\n        self.port = strports.listen(self.portstr, self.serverFactory)\n        return service.AsyncService.startService(self)\n\n    def stopService(self):\n        # stop listening on the port when shut down\n        assert self.port\n        port, self.port = self.port, None\n        d = defer.maybeDeferred(port.stopListening)\n        d.addCallback(lambda _: service.AsyncService.stopService(self))\n        return d\n\n    def register(self, username, password, pfactory):\n        if debug:\n            log.msg(\"registering username '%s' on pb port %s: %s\"\n                    % (username, self.portstr, pfactory))\n        if username in self.users:\n            raise KeyError(\"username '%s' is already registered on PB port %s\"\n                           % (username, self.portstr))\n        self.users[username] = (password, pfactory)\n\n    def unregister(self, username):\n        if debug:\n            log.msg(\"unregistering username '%s' on pb port %s\"\n                    % (username, self.portstr))\n        del self.users[username]\n\n    # IRealm\n\n    def requestAvatar(self, username, mind, interface):\n        assert interface == pb.IPerspective\n        username = bytes2unicode(username)\n        if username not in self.users:\n            d = defer.succeed(None)  # no perspective\n        else:\n            _, afactory = self.users.get(username)\n            d = defer.maybeDeferred(afactory, mind, username)\n\n        # check that we got a perspective\n        @d.addCallback\n        def check(persp):\n            if not persp:\n                raise ValueError(\"no perspective for '%s'\" % username)\n            return persp\n\n        # call the perspective's attached(mind)\n        @d.addCallback\n        def call_attached(persp):\n            d = defer.maybeDeferred(persp.attached, mind)\n            d.addCallback(lambda _: persp)  # keep returning the perspective\n            return d\n\n        # return the tuple requestAvatar is expected to return\n        @d.addCallback\n        def done(persp):\n            return (pb.IPerspective, persp, lambda: persp.detached(mind))\n\n        return d\n\n    # ICredentialsChecker\n\n    @defer.inlineCallbacks\n    def requestAvatarId(self, creds):\n        p = Properties()\n        p.master = self.master\n        username = bytes2unicode(creds.username)\n        try:\n            yield self.master.initLock.acquire()\n            if username in self.users:\n                password, _ = self.users[username]\n                password = yield p.render(password)\n                matched = yield defer.maybeDeferred(\n                    creds.checkPassword, unicode2bytes(password))\n                if not matched:\n                    log.msg(\"invalid login from user '%s'\" % creds.username)\n                    raise error.UnauthorizedLogin()\n                defer.returnValue(creds.username)\n            log.msg(\"invalid login from unknown user '%s'\" % creds.username)\n            raise error.UnauthorizedLogin()\n        finally:\n            eventually(self.master.initLock.release)\n", "path": "master/buildbot/pbmanager.py"}]}