← Back to team overview

divmod-dev team mailing list archive

[Merge] lp:~divmod-dev/divmod.org/829879-pypy-finalization-semantics into lp:divmod.org

 

Tristan Seligmann has proposed merging lp:~divmod-dev/divmod.org/829879-pypy-finalization-semantics into lp:divmod.org.

Requested reviews:
  Laurens Van Houtven (lvh)
Related bugs:
  Bug #829879 in Divmod Axiom: "Axiom finalization cache does not work on PyPy"
  https://bugs.launchpad.net/divmod-axiom/+bug/829879

For more details, see:
https://code.launchpad.net/~divmod-dev/divmod.org/829879-pypy-finalization-semantics/+merge/172946

Resubmitting without the "optional-pycrypto" dependency, as this is only relevant to Mantissa.
-- 
https://code.launchpad.net/~divmod-dev/divmod.org/829879-pypy-finalization-semantics/+merge/172946
Your team Divmod-dev is subscribed to branch lp:divmod.org.
=== modified file 'Axiom/axiom/_fincache.py'
--- Axiom/axiom/_fincache.py	2006-04-01 19:46:29 +0000
+++ Axiom/axiom/_fincache.py	2013-07-04 03:46:28 +0000
@@ -1,4 +1,3 @@
-
 from weakref import ref
 from traceback import print_exc
 
@@ -6,11 +5,12 @@
 
 from axiom import iaxiom
 
-class CacheFault(RuntimeError):
-    """
-    A serious problem has occurred within the cache.  This error is internal
-    and should never really be trapped.
-    """
+class CacheFault(KeyError):
+    """
+    An item has fallen out of cache, but the weakref callback has not yet run.
+    """
+
+
 
 def logErrorNoMatterWhat():
     try:
@@ -27,6 +27,8 @@
             # to.  Don't bother.
             return
 
+
+
 def createCacheRemoveCallback(w, k, f):
     def remove(self):
         # Weakref callbacks cannot raise exceptions or DOOM ensues
@@ -37,12 +39,16 @@
         try:
             self = w()
             if self is not None:
-                del self.data[k]
+                try:
+                    del self.data[k]
+                except KeyError:
+                    # Already gone
+                    pass
         except:
             logErrorNoMatterWhat()
     return remove
 
-PROFILING = False
+
 
 class FinalizingCache:
     """Possibly useful for infrastructure?  This would be a nice addition (or
@@ -50,9 +56,7 @@
     """
     def __init__(self):
         self.data = {}
-        if not PROFILING:
-            # see docstring for 'has'
-            self.has = self.data.has_key
+
 
     def cache(self, key, value):
         fin = value.__finalizer__()
@@ -61,28 +65,28 @@
                 ref(self), key, fin))
         return value
 
+
     def uncache(self, key, value):
-        assert self.get(key) is value
-        del self.data[key]
-
-    def has(self, key):
-        """Does the cache have this key?
-
-        (This implementation is only used if the system is being profiled, due
-        to bugs in Python's old profiler and its interaction with weakrefs.
-        Set the module attribute PROFILING to True at startup for this.)
-        """
-        if key in self.data:
-            o = self.data[key]()
-            if o is None:
-                del self.data[key]
-                return False
-            return True
-        return False
+        """
+        Remove a key from the cache.
+
+        As a sanity check, if the specified key is present in the cache, it
+        must have the given value.
+
+        @param key: The key to remove.
+        @param value: The expected value for the key.
+        """
+        try:
+            assert self.get(key) is value
+            del self.data[key]
+        except KeyError:
+            pass
+
 
     def get(self, key):
         o = self.data[key]()
         if o is None:
+            del self.data[key]
             raise CacheFault(
                 "FinalizingCache has %r but its value is no more." % (key,))
         log.msg(interface=iaxiom.IStatEvent, stat_cache_hits=1, key=key)

=== modified file 'Axiom/axiom/store.py'
--- Axiom/axiom/store.py	2012-08-04 11:07:11 +0000
+++ Axiom/axiom/store.py	2013-07-04 03:46:28 +0000
@@ -1747,10 +1747,10 @@
             self.executeSQL(sql, insertArgs)
 
     def _loadedItem(self, itemClass, storeID, attrs):
-        if self.objectCache.has(storeID):
+        try:
             result = self.objectCache.get(storeID)
             # XXX do checks on consistency between attrs and DB object, maybe?
-        else:
+        except KeyError:
             result = itemClass.existingInStore(self, storeID, attrs)
             if not result.__legacy__:
                 self.objectCache.cache(storeID, result)
@@ -2205,8 +2205,10 @@
                     type(storeID).__name__,))
         if storeID == STORE_SELF_ID:
             return self
-        if self.objectCache.has(storeID):
+        try:
             return self.objectCache.get(storeID)
+        except KeyError:
+            pass
         log.msg(interface=iaxiom.IStatEvent, stat_cache_misses=1, key=storeID)
         results = self.querySchemaSQL(_schema.TYPEOF_QUERY, [storeID])
         assert (len(results) in [1, 0]),\

=== modified file 'Axiom/axiom/substore.py'
--- Axiom/axiom/substore.py	2007-11-15 19:58:09 +0000
+++ Axiom/axiom/substore.py	2013-07-04 03:46:28 +0000
@@ -26,6 +26,9 @@
         """
         Create a new SubStore, allocating a new file space for it.
         """
+        if isinstance(pathSegments, basestring):
+            raise ValueError(
+                'Received %s instead of a sequence' % (pathSegments,))
         if store.dbdir is None:
             self = cls(store=store, storepath=None)
         else:
@@ -37,11 +40,13 @@
 
     createNew = classmethod(createNew)
 
+
     def close(self):
         self.substore.close()
         del self.substore._openSubStore
         del self.substore
 
+
     def open(self, debug=False):
         if hasattr(self, 'substore'):
             return self.substore
@@ -51,6 +56,7 @@
                                    # store is alive!
             return s
 
+
     def createStore(self, debug):
         """
         Create the actual Store this Substore represents.
@@ -73,6 +79,7 @@
                          idInParent=self.storeID,
                          debug=debug)
 
+
     def __conform__(self, interface):
         """
         I adapt my store object to whatever interface I am adapted to.  This
@@ -84,6 +91,7 @@
         ifa = interface(self.open(debug=self.store.debug), None)
         return ifa
 
+
     def indirect(self, interface):
         """
         Like __conform__, I adapt my store to whatever interface I am asked to
@@ -94,6 +102,7 @@
         return interface(self)
 
 
+
 class SubStoreStartupService(Item, service.Service):
     """
     This class no longer exists.  It is here simply to trigger an upgrade which

=== modified file 'Axiom/axiom/test/test_batch.py'
--- Axiom/axiom/test/test_batch.py	2012-07-05 13:37:40 +0000
+++ Axiom/axiom/test/test_batch.py	2013-07-04 03:46:28 +0000
@@ -560,7 +560,7 @@
         """
         dbdir = filepath.FilePath(self.mktemp())
         s = store.Store(dbdir)
-        ss = substore.SubStore.createNew(s, 'substore')
+        ss = substore.SubStore.createNew(s, ['substore'])
         bs = iaxiom.IBatchService(ss)
         self.failUnless(iaxiom.IBatchService.providedBy(bs))
 
@@ -582,12 +582,15 @@
         """
         dbdir = filepath.FilePath(self.mktemp())
         s = store.Store(dbdir)
-        ss = substore.SubStore.createNew(s, 'substore')
+        ss = substore.SubStore.createNew(s, ['substore'])
         service.IService(s).startService()
-        d = iaxiom.IBatchService(ss).call(BatchCallTestItem(store=ss.open()).callIt)
+        d = iaxiom.IBatchService(ss).call(
+            BatchCallTestItem(store=ss.open()).callIt)
         ss.close()
         def called(ign):
-            self.failUnless(ss.open().findUnique(BatchCallTestItem).called, "Was not called")
+            self.assertTrue(
+                ss.open().findUnique(BatchCallTestItem).called,
+                "Was not called")
             return service.IService(s).stopService()
         return d.addCallback(called)
 

=== modified file 'Axiom/axiom/test/test_reference.py'
--- Axiom/axiom/test/test_reference.py	2009-07-06 12:35:46 +0000
+++ Axiom/axiom/test/test_reference.py	2013-07-04 03:46:28 +0000
@@ -186,7 +186,7 @@
         store = Store()
         t = nonUpgradedItem(store=store)
         self.assertEquals(t.__legacy__, True)
-        self.assertFalse(store.objectCache.has(t.storeID))
+        self.assertRaises(KeyError, store.objectCache.get, t.storeID)
         t2 = store.getItemByID(t.storeID)
         self.assertNotIdentical(t, t2)
         self.assertTrue(isinstance(t2, UpgradedItem))

=== modified file 'Axiom/axiom/test/test_substore.py'
--- Axiom/axiom/test/test_substore.py	2008-05-06 13:33:30 +0000
+++ Axiom/axiom/test/test_substore.py	2013-07-04 03:46:28 +0000
@@ -12,15 +12,14 @@
 
 
 class SubStored(Item):
-
     schemaVersion = 1
     typeName = 'substoredthing'
     a = text()
     b = bytes()
 
 
+
 class YouCantStartThis(Item, Service):
-
     parent = inmemory()
     running = inmemory()
     name = inmemory()
@@ -30,8 +29,9 @@
     def startService(self):
         self.started = True
 
+
+
 class YouShouldStartThis(Item, Service):
-
     parent = inmemory()
     running = inmemory()
     name = inmemory()
@@ -42,11 +42,11 @@
         self.started = True
 
 
+
 class SubStoreTest(unittest.TestCase):
     """
     Test on-disk creation of substores.
     """
-
     def testOneThing(self):
         """
         Ensure that items can be inserted into substores and
@@ -72,6 +72,7 @@
         self.assertEquals(reopenssd.a, u'hello world')
         self.assertEquals(reopenssd.b, 'what, its text')
 
+
     def test_oneThingMemory(self):
         """
         Ensure that items put into in-memory substores are retrievable.
@@ -113,6 +114,7 @@
         self.assertEquals(item.a, u'hello world')
         self.assertEquals(item.b, 'what, its text')
 
+
     def test_memorySubstoreFile(self):
         """
         In-memory substores whose stores have file directories should be able
@@ -127,6 +129,17 @@
         f.close()
         self.assertEqual(open(f.finalpath.path).read(), "yay")
 
+
+    def test_createNewStringPath(self):
+        """
+        Passing a string instead of a sequence of strings to
+        L{SubStore.createNew} results in an exception.
+        """
+        s = Store()
+        self.assertRaises(ValueError, SubStore.createNew, s, 'notasequence')
+
+
+
 class SubStoreStartupSemantics(unittest.TestCase):
     """
     These tests verify that interactions between store and substore services
@@ -134,10 +147,10 @@
     behavior.  Read the code if you are interested in how to get startup
     notifications from substore items.
     """
-
     def setUp(self):
         """
-        Set up the tests by creating a store and a substore and opening them both.
+        Set up the tests by creating a store and a substore and opening them
+        both.
         """
         self.topdb = topdb = Store(filepath.FilePath(self.mktemp()))
         self.ssitem = ssitem = SubStore.createNew(
@@ -147,8 +160,8 @@
 
     def testDontStartNormally(self):
         """
-        Substores' services are not supposed to be started when their parent stores
-        are.
+        Substores' services are not supposed to be started when their parent
+        stores are.
         """
         ss = self.ss
         ycst = YouCantStartThis(store=ss)
@@ -183,4 +196,3 @@
         """
         if self.serviceStarted:
             return IService(self.topdb).stopService()
-

=== modified file 'Axiom/axiom/test/test_xatop.py'
--- Axiom/axiom/test/test_xatop.py	2010-04-03 12:38:34 +0000
+++ Axiom/axiom/test/test_xatop.py	2013-07-04 03:46:28 +0000
@@ -203,40 +203,6 @@
                    schema[2], schema[3], schema[4])] + schema[1:]})
 
 
-    def test_inMemorySchemaCacheReset(self):
-        """
-        The global in-memory table schema cache should not change the behavior
-        of consistency checking with respect to the redefinition of in-memory
-        schemas.
-
-        This test is verifying the behavior which is granted by the use of a
-        WeakKeyDictionary for _inMemorySchemaCache.  If that cache kept strong
-        references to item types or used a (typeName, schemaVersion) key,
-        either the second C{SoonToChange} class definition in this method would
-        fail or the schema defined by the first C{SoonToChange} class would be
-        used, even after it should have been replaced by the second definition.
-        """
-        class SoonToChange(item.Item):
-            attribute = attributes.integer()
-
-        dbpath = self.mktemp()
-        s = store.Store(dbpath)
-        SoonToChange(store=s)
-        s.close()
-
-        # This causes a Store._checkTypeSchemaConsistency to cache
-        # SoonToChange.
-        s = store.Store(dbpath)
-        s.close()
-
-        del SoonToChange, s
-
-        class SoonToChange(item.Item):
-            attribute = attributes.boolean()
-
-        self.assertRaises(RuntimeError, store.Store, dbpath)
-
-
     def test_checkOutdatedTypeSchema(self):
         """
         L{Store._checkTypeSchemaConsistency} raises L{RuntimeError} if the type
@@ -270,11 +236,10 @@
         SoonToChange(store=s)
         s.close()
 
-        # Get rid of both the type and the store so that we can define a new
-        # incompatible version.  It might be nice if closed stores didn't keep
-        # references to types, but whatever.  This kind of behavior isn't
-        # really supported, only the unit tests need to do it for now.
-        del SoonToChange, s
+        # Get rid of the cached information about this type
+        from axiom.item import _typeNameToMostRecentClass
+        del _typeNameToMostRecentClass[SoonToChange.typeName]
+        del SoonToChange
 
         class SoonToChange(item.Item):
             attribute = attributes.boolean()
@@ -353,48 +318,6 @@
                  secondary._indexNameOf(TestItem, ['bar', 'baz'])]))
 
 
-    def test_inMemoryIndexCacheReset(self):
-        """
-        The global in-memory index schema cache should not change the behavior
-        of index creation with respect to the redefinition of in-memory
-        schemas.
-
-        This test is verifying the behavior which is granted by the use of a
-        WeakKeyDictionary for _requiredTableIndexes.  If that cache kept strong
-        references to item types or used a (typeName, schemaVersion) key,
-        either the second C{SoonToChange} class definition in this method would
-        fail or the indexes on the schema defined by the first C{SoonToChange}
-        class would be used, even after it should have been replaced by the
-        second definition.
-        """
-        class SoonToChange(item.Item):
-            attribute = attributes.integer()
-
-        dbpath = self.mktemp()
-        s = store.Store(dbpath)
-
-        before = s._loadExistingIndexes()
-        SoonToChange(store=s)
-        after = s._loadExistingIndexes()
-
-        # Sanity check - this version of SoonToChange has no indexes.
-        self.assertEqual(before, after)
-
-        s.close()
-        del SoonToChange, s
-
-        class SoonToChange(item.Item):
-            attribute = attributes.boolean(indexed=True)
-
-        s = store.Store()
-        before = s._loadExistingIndexes()
-        SoonToChange(store=s)
-        after = s._loadExistingIndexes()
-        self.assertEqual(
-            after - before,
-            set([s._indexNameOf(SoonToChange, ['attribute'])]))
-
-
     def test_loadPythonModuleHint(self):
         """
         If the Python definition of a type found in a Store has not yet been

=== modified file 'Mantissa/xmantissa/terminal.py'
--- Mantissa/xmantissa/terminal.py	2011-07-18 12:37:13 +0000
+++ Mantissa/xmantissa/terminal.py	2013-07-04 03:46:28 +0000
@@ -10,7 +10,10 @@
 
 from hashlib import md5
 
-from Crypto.PublicKey import RSA
+try:
+    from Crypto.PublicKey import RSA
+except ImportError:
+    RSA = None
 
 from zope.interface import implements
 
@@ -20,12 +23,16 @@
 from twisted.cred.portal import IRealm, Portal
 from twisted.cred.checkers import ICredentialsChecker
 from twisted.conch.interfaces import IConchUser, ISession
-from twisted.conch.ssh.factory import SSHFactory
-from twisted.conch.ssh.keys import Key
-from twisted.conch.manhole_ssh import TerminalUser, TerminalSession, TerminalSessionTransport
-from twisted.conch.insults.insults import ServerProtocol, TerminalProtocol
-from twisted.conch.insults.window import TopWindow, VBox, Border, Button
-from twisted.conch.manhole import ColoredManhole
+try:
+    from twisted.conch.ssh.factory import SSHFactory
+    from twisted.conch.ssh.keys import Key
+    from twisted.conch.manhole_ssh import TerminalUser, TerminalSession, TerminalSessionTransport
+    from twisted.conch.insults.insults import ServerProtocol, TerminalProtocol
+    from twisted.conch.insults.window import TopWindow, VBox, Border, Button
+    from twisted.conch.manhole import ColoredManhole
+    conch = True
+except ImportError:
+    conch = False
 
 from axiom.iaxiom import IPowerupIndirector
 from axiom.item import Item
@@ -140,156 +147,162 @@
 
 
 
-class ShellServer(TerminalProtocol):
-    """
-    A terminal protocol which finds L{ITerminalServerFactory} powerups in the
-    same store and presents the option of beginning a session with one of them.
-
-    @ivar _store: The L{Store} which will be searched for
-        L{ITerminalServerFactory} powerups.
-
-    @ivar _protocol: If an L{ITerminalServerFactory} has been selected to
-        interact with, then this attribute refers to the L{ITerminalProtocol}
-        produced by that factory's C{buildTerminalProtocol} method.  Input from
-        the terminal is delivered to this protocol.  This attribute is C{None}
-        whenever the "main menu" user interface is being displayed.
-
-    @ivar _window: A L{TopWindow} instance which contains the "main menu" user
-        interface.  Whenever the C{_protocol} attribute is C{None}, input is
-        directed to this object instead.  Whenever the C{_protocol} attribute
-        is not C{None}, this window is hidden.
-    """
-    _width = 80
-    _height = 24
-
-    _protocol = None
-
-    def __init__(self, store):
-        TerminalProtocol.__init__(self)
-        self._store = store
-
-
-    def _draw(self):
-        """
-        Call the drawing API for the main menu widget with the current known
-        terminal size and the terminal.
-        """
-        self._window.draw(self._width, self._height, self.terminal)
-
-
-    def _appButtons(self):
-        for factory in self._store.powerupsFor(ITerminalServerFactory):
-            yield Button(
-                factory.name.encode('utf-8'),
-                lambda factory=factory: self.switchTo(factory))
-
-
-    def _logoffButton(self):
-        return Button("logoff", self.logoff)
-
-
-    def _makeWindow(self):
-        buttons = VBox()
-        for button in self._appButtons():
-            buttons.addChild(Border(button))
-        buttons.addChild(Border(self._logoffButton()))
-
-        from twisted.internet import reactor
-        window = TopWindow(self._draw, lambda f: reactor.callLater(0, f))
-        window.addChild(Border(buttons))
-        return window
-
-
-    def connectionMade(self):
-        """
-        Reset the terminal and create a UI for selecting an application to use.
-        """
-        self.terminal.reset()
-        self._window = self._makeWindow()
-
-
-    def reactivate(self):
-        """
-        Called when a sub-protocol is finished.  This disconnects the
-        sub-protocol and redraws the main menu UI.
-        """
-        self._protocol.connectionLost(None)
-        self._protocol = None
-        self.terminal.reset()
-        self._window.filthy()
-        self._window.repaint()
-
-
-    def switchTo(self, app):
-        """
-        Use the given L{ITerminalServerFactory} to create a new
-        L{ITerminalProtocol} and connect it to C{self.terminal} (such that it
-        cannot actually disconnect, but can do most anything else).  Control of
-        the terminal is delegated to it until it gives up that control by
-        disconnecting itself from the terminal.
-
-        @type app: L{ITerminalServerFactory} provider
-        @param app: The factory which will be used to create a protocol
-            instance.
-        """
-        viewer = _AuthenticatedShellViewer(list(getAccountNames(self._store)))
-        self._protocol = app.buildTerminalProtocol(viewer)
-        self._protocol.makeConnection(_ReturnToMenuWrapper(self, self.terminal))
-
-
-    def keystrokeReceived(self, keyID, modifier):
-        """
-        Forward input events to the application-supplied protocol if one is
-        currently active, otherwise forward them to the main menu UI.
-        """
-        if self._protocol is not None:
-            self._protocol.keystrokeReceived(keyID, modifier)
-        else:
-            self._window.keystrokeReceived(keyID, modifier)
-
-
-    def logoff(self):
-        """
-        Disconnect from the terminal completely.
-        """
-        self.terminal.loseConnection()
-
-
-
-class _BetterTerminalSession(TerminalSession):
-    """
-    L{TerminalSession} is missing C{windowChanged} and C{eofReceived} for some
-    reason.  Add it here until it's fixed in Twisted.  See Twisted ticket
-    #3303.
-    """
-    def windowChanged(self, newWindowSize):
-        """
-        Ignore window size change events.
-        """
-
-
-    def eofReceived(self):
-        """
-        Ignore the eof event.
-        """
-
-
-class _BetterTerminalUser(TerminalUser):
-    """
-    L{TerminalUser} is missing C{conn} for some reason reason (probably the
-    reason that it's not a very great thing and generally an implementation
-    will be missing it for a while).  Add it here until it's fixed in Twisted.
-    See Twisted ticket #3863.
-    """
-    # Some code in conch will rudely rebind this attribute later.  For now,
-    # make sure that it is at least bound to something so that the object
-    # appears to fully implement IConchUser.  Most likely, TerminalUser should
-    # be taking care of this, not us.  Or even better, this attribute shouldn't
-    # be part of the interface; some better means should be provided for
-    # informing the IConchUser avatar of the connection object (I'm not even
-    # sure why the avatar would care about having a reference to the connection
-    # object).
-    conn = None
+if conch:
+    class ShellServer(TerminalProtocol):
+        """
+        A terminal protocol which finds L{ITerminalServerFactory} powerups in
+        the same store and presents the option of beginning a session with one
+        of them.
+
+        @ivar _store: The L{Store} which will be searched for
+            L{ITerminalServerFactory} powerups.
+
+        @ivar _protocol: If an L{ITerminalServerFactory} has been selected to
+            interact with, then this attribute refers to the
+            L{ITerminalProtocol} produced by that factory's
+            C{buildTerminalProtocol} method.  Input from the terminal is
+            delivered to this protocol.  This attribute is C{None} whenever the
+            "main menu" user interface is being displayed.
+
+        @ivar _window: A L{TopWindow} instance which contains the "main menu"
+            user interface.  Whenever the C{_protocol} attribute is C{None},
+            input is directed to this object instead.  Whenever the
+            C{_protocol} attribute is not C{None}, this window is hidden.
+        """
+        _width = 80
+        _height = 24
+
+        _protocol = None
+
+        def __init__(self, store):
+            TerminalProtocol.__init__(self)
+            self._store = store
+
+
+        def _draw(self):
+            """
+            Call the drawing API for the main menu widget with the current
+            known terminal size and the terminal.
+            """
+            self._window.draw(self._width, self._height, self.terminal)
+
+
+        def _appButtons(self):
+            for factory in self._store.powerupsFor(ITerminalServerFactory):
+                yield Button(
+                    factory.name.encode('utf-8'),
+                    lambda factory=factory: self.switchTo(factory))
+
+
+        def _logoffButton(self):
+            return Button("logoff", self.logoff)
+
+
+        def _makeWindow(self):
+            buttons = VBox()
+            for button in self._appButtons():
+                buttons.addChild(Border(button))
+            buttons.addChild(Border(self._logoffButton()))
+
+            from twisted.internet import reactor
+            window = TopWindow(self._draw, lambda f: reactor.callLater(0, f))
+            window.addChild(Border(buttons))
+            return window
+
+
+        def connectionMade(self):
+            """
+            Reset the terminal and create a UI for selecting an application to
+            use.
+            """
+            self.terminal.reset()
+            self._window = self._makeWindow()
+
+
+        def reactivate(self):
+            """
+            Called when a sub-protocol is finished.  This disconnects the
+            sub-protocol and redraws the main menu UI.
+            """
+            self._protocol.connectionLost(None)
+            self._protocol = None
+            self.terminal.reset()
+            self._window.filthy()
+            self._window.repaint()
+
+
+        def switchTo(self, app):
+            """
+            Use the given L{ITerminalServerFactory} to create a new
+            L{ITerminalProtocol} and connect it to C{self.terminal} (such that
+            it cannot actually disconnect, but can do most anything else).
+            Control of the terminal is delegated to it until it gives up that
+            control by disconnecting itself from the terminal.
+
+            @type app: L{ITerminalServerFactory} provider
+            @param app: The factory which will be used to create a protocol
+                instance.
+            """
+            viewer = _AuthenticatedShellViewer(
+                list(getAccountNames(self._store)))
+            self._protocol = app.buildTerminalProtocol(viewer)
+            self._protocol.makeConnection(
+                _ReturnToMenuWrapper(self, self.terminal))
+
+
+        def keystrokeReceived(self, keyID, modifier):
+            """
+            Forward input events to the application-supplied protocol if one is
+            currently active, otherwise forward them to the main menu UI.
+            """
+            if self._protocol is not None:
+                self._protocol.keystrokeReceived(keyID, modifier)
+            else:
+                self._window.keystrokeReceived(keyID, modifier)
+
+
+        def logoff(self):
+            """
+            Disconnect from the terminal completely.
+            """
+            self.terminal.loseConnection()
+
+
+
+    class _BetterTerminalSession(TerminalSession):
+        """
+        L{TerminalSession} is missing C{windowChanged} and C{eofReceived} for
+        some reason.  Add it here until it's fixed in Twisted.  See Twisted
+        ticket #3303.
+        """
+        def windowChanged(self, newWindowSize):
+            """
+            Ignore window size change events.
+            """
+
+
+        def eofReceived(self):
+            """
+            Ignore the eof event.
+            """
+
+
+    class _BetterTerminalUser(TerminalUser):
+        """
+        L{TerminalUser} is missing C{conn} for some reason reason (probably the
+        reason that it's not a very great thing and generally an implementation
+        will be missing it for a while).  Add it here until it's fixed in
+        Twisted.  See Twisted ticket #3863.
+        """
+        # Some code in conch will rudely rebind this attribute later.  For now,
+        # make sure that it is at least bound to something so that the object
+        # appears to fully implement IConchUser.  Most likely, TerminalUser
+        # should be taking care of this, not us.  Or even better, this
+        # attribute shouldn't be part of the interface; some better means
+        # should be provided for informing the IConchUser avatar of the
+        # connection object (I'm not even sure why the avatar would care about
+        # having a reference to the connection object).
+        conn = None
 
 
 class ShellAccount(Item):

=== modified file 'Mantissa/xmantissa/webadmin.py'
--- Mantissa/xmantissa/webadmin.py	2009-07-29 10:19:26 +0000
+++ Mantissa/xmantissa/webadmin.py	2013-07-04 03:46:28 +0000
@@ -34,7 +34,10 @@
 from xmantissa.scrolltable import ScrollingFragment
 from xmantissa.webapp import PrivateApplication
 from xmantissa.website import WebSite, PrefixURLMixin
-from xmantissa.terminal import TerminalManhole
+try:
+    from xmantissa.terminal import TerminalManhole
+except ImportError:
+    TerminalManhole = None
 from xmantissa.ixmantissa import (
     INavigableElement, INavigableFragment, ISessionlessSiteRootPlugin,
     IProtocolFactoryFactory)


Follow ups