[CalendarServer-changes] [13696] twext/trunk/twext/enterprise
source_changes at macosforge.org
source_changes at macosforge.org
Fri Jun 27 12:22:06 PDT 2014
Revision: 13696
http://trac.calendarserver.org//changeset/13696
Author: cdaboo at apple.com
Date: 2014-06-27 12:22:06 -0700 (Fri, 27 Jun 2014)
Log Message:
-----------
Fix service termination race condition that would result in a terminated transaction continuing to sending SQL to the server.
Modified Paths:
--------------
twext/trunk/twext/enterprise/adbapi2.py
twext/trunk/twext/enterprise/fixtures.py
twext/trunk/twext/enterprise/test/test_adbapi2.py
Modified: twext/trunk/twext/enterprise/adbapi2.py
===================================================================
--- twext/trunk/twext/enterprise/adbapi2.py 2014-06-27 19:20:13 UTC (rev 13695)
+++ twext/trunk/twext/enterprise/adbapi2.py 2014-06-27 19:22:06 UTC (rev 13696)
@@ -306,6 +306,8 @@
def execSQL(self, *args, **kw):
+ if self._completed:
+ raise RuntimeError("Attempt to use {} transaction.".format(self._completed))
result = self._holder.submit(
lambda: self._reallyExecSQL(*args, **kw)
)
@@ -322,12 +324,14 @@
return result
- def _end(self, really):
+ def _end(self, really, terminate=False):
"""
Common logic for commit or abort. Executed in the main reactor thread.
@param really: the callable to execute in the cursor thread to actually
do the commit or rollback.
+ @param terminate: abort and prevent any new SQL from being executed even
+ after a reset().
@return: a L{Deferred} which fires when the database logic has
completed.
@@ -336,7 +340,7 @@
committed or aborted.
"""
if not self._completed:
- self._completed = "ended"
+ self._completed = "terminated" if terminate else "ended"
def reallySomething():
"""
@@ -346,7 +350,6 @@
if self._cursor is None or self._first:
return
really()
- self._first = True
result = self._holder.submit(reallySomething)
self._pool._repoolAfter(self, result)
@@ -363,6 +366,10 @@
return self._end(self._connection.rollback).addErrback(log.err)
+ def terminate(self):
+ return self._end(self._connection.rollback, terminate=True).addErrback(log.err)
+
+
def reset(self):
"""
Call this when placing this transaction back into the pool.
@@ -372,7 +379,9 @@
"""
if not self._completed:
raise RuntimeError("Attempt to re-set active transaction.")
- self._completed = False
+ if self._completed != "terminated":
+ self._completed = False
+ self._first = True
def _releaseConnection(self):
@@ -919,7 +928,10 @@
return d
+ def terminate(self):
+ return self.abort()
+
def _fork(x):
"""
Produce a L{Deferred} that will fire when another L{Deferred} fires without
@@ -1034,10 +1046,11 @@
while self._finishing:
yield _fork(self._finishing[0][1])
- # Phase 3: All of the busy transactions must be aborted first. As each
- # one is aborted, it will remove itself from the list.
+ # Phase 3: All of the busy transactions must be terminated first. As each
+ # one is terminated, it will remove itself from the list. Note we terminate
+ # and not abort the transaction to ensure they cannot be re-used.
while self._busy:
- yield self._busy[0].abort()
+ yield self._busy[0].terminate()
# Phase 4: All transactions should now be in the free list, since
# "abort()" will have put them there. Shut down all the associated
Modified: twext/trunk/twext/enterprise/fixtures.py
===================================================================
--- twext/trunk/twext/enterprise/fixtures.py 2014-06-27 19:20:13 UTC (rev 13695)
+++ twext/trunk/twext/enterprise/fixtures.py 2014-06-27 19:22:06 UTC (rev 13696)
@@ -458,6 +458,8 @@
def execute(self, sql, args=()):
+ if self.connection.closed:
+ raise FakeConnectionError
self.connection.executions += 1
if self.connection._executeFailQueue:
raise self.connection._executeFailQueue.pop(0)()
Modified: twext/trunk/twext/enterprise/test/test_adbapi2.py
===================================================================
--- twext/trunk/twext/enterprise/test/test_adbapi2.py 2014-06-27 19:20:13 UTC (rev 13695)
+++ twext/trunk/twext/enterprise/test/test_adbapi2.py 2014-06-27 19:22:06 UTC (rev 13696)
@@ -463,6 +463,25 @@
self.assertEquals(self.factory.connections[1].closed, True)
+ def test_partialTxnFailsDuringStopService(self):
+ """
+ Using the logic in L{ConnectionPool.stopService}, make sure that an
+ L{_ConnectedTxn} cannot continue to process SQL after L{_ConnectedTxn.abort}
+ is called and before L{_ConnectedTxn.reset} is called.
+ """
+ txn = self.createTransaction()
+ if hasattr(txn, "_baseTxn"):
+ # Send initial statement
+ txn.execSQL("maybe change something!")
+
+ # Make it look like the service is stopping
+ txn._baseTxn._connection.close()
+ txn._baseTxn.terminate()
+
+ # Try to send more SQL - must fail
+ self.failUnlessRaises(RuntimeError, txn.execSQL, "maybe change something else!")
+
+
def test_abortRecycledTransaction(self):
"""
L{ConnectionPool.stopService} will shut down if a recycled transaction
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140627/33ae70df/attachment-0001.html>
More information about the calendarserver-changes
mailing list