[CalendarServer-changes] [6575] CalendarServer/branches/users/glyph/conn-limit/txdav/base/datastore/ test/test_asyncsqlpool.py

source_changes at macosforge.org source_changes at macosforge.org
Mon Nov 8 09:13:46 PST 2010


Revision: 6575
          http://trac.macosforge.org/projects/calendarserver/changeset/6575
Author:   glyph at apple.com
Date:     2010-11-08 09:13:41 -0800 (Mon, 08 Nov 2010)
Log Message:
-----------
Clean failure for too many connections.

Added Paths:
-----------
    CalendarServer/branches/users/glyph/conn-limit/txdav/base/datastore/test/test_asyncsqlpool.py

Added: CalendarServer/branches/users/glyph/conn-limit/txdav/base/datastore/test/test_asyncsqlpool.py
===================================================================
--- CalendarServer/branches/users/glyph/conn-limit/txdav/base/datastore/test/test_asyncsqlpool.py	                        (rev 0)
+++ CalendarServer/branches/users/glyph/conn-limit/txdav/base/datastore/test/test_asyncsqlpool.py	2010-11-08 17:13:41 UTC (rev 6575)
@@ -0,0 +1,174 @@
+# -*- test-case-name: txdav.caldav.datastore -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# 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.
+##
+
+"""
+Tests for L{txdav.base.datastore.asyncsqlpool}.
+"""
+
+from itertools import count
+from twisted.trial.unittest import TestCase
+
+from txdav.base.datastore.asyncsqlpool import ConnectionPool
+from twisted.internet.defer import inlineCallbacks
+
+
+class Child(object):
+    def __init__(self, parent):
+        self.parent = parent
+        self.parent.children.append(self)
+
+    def close(self):
+        self.parent.children.remove(self)
+
+
+
+class Parent(object):
+
+    def __init__(self):
+        self.children = []
+
+
+
+class FakeConnection(Parent, Child):
+    """
+    Fake Stand-in for DB-API 2.0 connection.
+    """
+
+    def __init__(self, factory):
+        """
+        Initialize list of cursors
+        """
+        Parent.__init__(self)
+        Child.__init__(self, factory)
+        self.id = factory.idcounter.next()
+
+
+    @property
+    def cursors(self):
+        "Alias to make tests more readable."
+        return self.children
+
+
+    def cursor(self):
+        return FakeCursor(self)
+
+
+    def commit(self):
+        return
+
+
+    def rollback(self):
+        return
+
+
+
+class FakeCursor(Child):
+    """
+    Fake stand-in for a DB-API 2.0 cursor.
+    """
+    def __init__(self, connection):
+        Child.__init__(self, connection)
+        self.rowcount = 0
+        # not entirely correct, but all we care about is its truth value.
+        self.description = False
+
+
+    @property
+    def connection(self):
+        "Alias to make tests more readable."
+        return self.parent
+
+
+    def execute(self, sql, args=()):
+        self.sql = sql
+        self.description = True
+        self.rowcount = 1
+        return
+
+
+    def fetchall(self):
+        """
+        Just echo the SQL that was executed in the last query.
+        """
+        return [[self.connection.id, self.sql]]
+
+
+
+class ConnectionFactory(Parent):
+    def __init__(self):
+        Parent.__init__(self)
+        self.idcounter = count(1)
+
+    @property
+    def connections(self):
+        "Alias to make tests more readable."
+        return self.children
+
+
+    def connect(self):
+        return FakeConnection(self)
+
+
+
+class ConnectionPoolTests(TestCase):
+
+    @inlineCallbacks
+    def test_tooManyConnections(self):
+        """
+        When the number of outstanding busy transactions exceeds the number of
+        slots specified by L{ConnectionPool.maxConnections},
+        L{ConnectionPool.connection} will return a L{PooledSqlTxn} that is not
+        backed by any L{BaseSqlTxn}; this object will queue its SQL statements
+        until an existing connection becomes available.
+        """
+        cf = ConnectionFactory()
+        cp = ConnectionPool(cf.connect)
+        cp.startService()
+        self.addCleanup(cp.stopService)
+        a = cp.connection()
+        [[counter, echo]] = yield a.execSQL("alpha")
+        b = cp.connection()
+        [[bcounter, becho]] = yield b.execSQL("beta")
+
+        # both 'a' and 'b' are holding open a connection now; let's try to open
+        # a third one.  (The ordering will be deterministic even if this fails,
+        # because those threads are already busy.)
+        c = cp.connection()
+        enqueue = c.execSQL("gamma")
+        x = []
+        def addtox(it):
+            x.append(it)
+            return it
+        enqueue.addCallback(addtox)
+
+        # Did 'c' open a connection?  Let's hope not...
+        self.assertEquals(len(cf.connections), 2)
+        # This assertion is _not_ deterministic, unfortunately; it's unlikely
+        # that the implementation could be adjusted such that this assertion
+        # would fail and the others would succeed.  However, if it does fail,
+        # that's really bad, so I am leaving it regardless.
+        self.failIf(bool(x), "SQL executed too soon!")
+        yield b.commit()
+
+        # Now that 'b' has committed, 'c' should be able to complete.
+        [[ccounter, cecho]] = yield enqueue
+
+        # The connection for 'a' ought to be busy, so let's make sure we're
+        # using the one for 'c'.
+        self.assertEquals(ccounter, bcounter)
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101108/11a3c353/attachment.html>


More information about the calendarserver-changes mailing list