<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[11399] CalendarServer/trunk/txdav</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/11399">11399</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-06-25 07:08:23 -0700 (Tue, 25 Jun 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Fix issue with deadlock on common home child revisions table change.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresqlpy">CalendarServer/trunk/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_sqlpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoresqlpy">CalendarServer/trunk/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoretestcommonpy">CalendarServer/trunk/txdav/carddav/datastore/test/common.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoretesttest_sqlpy">CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresqlpy">CalendarServer/trunk/txdav/common/datastore/sql.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunktxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py (11398 => 11399)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/sql.py        2013-06-25 13:48:14 UTC (rev 11398)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py        2013-06-25 14:08:23 UTC (rev 11399)
</span><span class="lines">@@ -15,9 +15,6 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-from twext.enterprise.locking import NamedLock
-from urlparse import urlparse, urlunparse
-from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> SQL backend for CalDAV storage.
</span><span class="lines">@@ -36,9 +33,8 @@
</span><span class="cx"> from twext.enterprise.dal.syntax import Select, Count, ColumnSyntax
</span><span class="cx"> from twext.enterprise.dal.syntax import Update
</span><span class="cx"> from twext.enterprise.dal.syntax import utcNowSQL
</span><del>-
</del><ins>+from twext.enterprise.locking import NamedLock
</ins><span class="cx"> from twext.enterprise.util import parseSQLTimestamp
</span><del>-
</del><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> from twext.python.filepath import CachingFilePath
</span><span class="cx"> from twext.python.log import Logger
</span><span class="lines">@@ -49,6 +45,7 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><span class="cx"> from twisted.python import hashlib
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> from twistedcaldav import caldavxml, customxml
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="lines">@@ -57,8 +54,8 @@
</span><span class="cx"> from twistedcaldav.ical import Component, InvalidICalendarDataError, Property
</span><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><del>-
</del><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><ins>+from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</ins><span class="cx"> from txdav.caldav.datastore.util import AttachmentRetrievalTransport, \
</span><span class="cx">     normalizationLookup
</span><span class="cx"> from txdav.caldav.datastore.util import CalendarObjectBase
</span><span class="lines">@@ -96,6 +93,7 @@
</span><span class="cx"> 
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx"> 
</span><ins>+from urlparse import urlparse, urlunparse
</ins><span class="cx"> import collections
</span><span class="cx"> import os
</span><span class="cx"> import tempfile
</span><span class="lines">@@ -706,7 +704,7 @@
</span><span class="cx">         # CalDAV stores the default calendar properties on the inbox so we also need to send a changed notification on that
</span><span class="cx">         inbox = (yield self.calendarWithName(&quot;inbox&quot;))
</span><span class="cx">         if inbox is not None:
</span><del>-            yield inbox.notifyChanged()
</del><ins>+            yield inbox.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -859,7 +857,12 @@
</span><span class="cx">         yield self.invalidateQueryCache()
</span><span class="cx">         yield self.notifyChanged()
</span><span class="cx"> 
</span><ins>+        # CalDAV stores the availability properties on the inbox so we also need to send a changed notification on that
+        inbox = (yield self.calendarWithName(&quot;inbox&quot;))
+        if inbox is not None:
+            yield inbox.notifyPropertyChanged()
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> CalendarHome._register(ECALENDARTYPE)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1040,7 +1043,7 @@
</span><span class="cx">         ).on(self._txn)
</span><span class="cx">         self._supportedComponents = supported_components
</span><span class="cx">         yield self.invalidateQueryCache()
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getTimezone(self):
</span><span class="lines">@@ -1073,7 +1076,7 @@
</span><span class="cx">             Where=(cal.CALENDAR_HOME_RESOURCE_ID == self.viewerHome()._resourceID).And(cal.CALENDAR_RESOURCE_ID == self._resourceID)
</span><span class="cx">         ).on(self._txn)
</span><span class="cx">         yield self.invalidateQueryCache()
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx">     ALARM_DETAILS = {
</span><span class="cx">         (True, True): (_bindSchema.ALARM_VEVENT_TIMED, &quot;_alarm_vevent_timed&quot;),
</span><span class="lines">@@ -1120,7 +1123,7 @@
</span><span class="cx">             Where=(cal.CALENDAR_HOME_RESOURCE_ID == self.viewerHome()._resourceID).And(cal.CALENDAR_RESOURCE_ID == self._resourceID)
</span><span class="cx">         ).on(self._txn)
</span><span class="cx">         yield self.invalidateQueryCache()
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def isInbox(self):
</span><span class="lines">@@ -1160,7 +1163,7 @@
</span><span class="cx">             Where=(cal.CALENDAR_HOME_RESOURCE_ID == self.viewerHome()._resourceID).And(cal.CALENDAR_RESOURCE_ID == self._resourceID)
</span><span class="cx">         ).on(self._txn)
</span><span class="cx">         yield self.invalidateQueryCache()
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def initPropertyStore(self, props):
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py (11398 => 11399)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py        2013-06-25 13:48:14 UTC (rev 11398)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py        2013-06-25 14:08:23 UTC (rev 11399)
</span><span class="lines">@@ -26,7 +26,7 @@
</span><span class="cx"> from twext.python.vcomponent import VComponent
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet import reactor
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, DeferredList
</ins><span class="cx"> from twisted.internet.task import deferLater
</span><span class="cx"> from twisted.trial import unittest
</span><span class="cx"> 
</span><span class="lines">@@ -607,7 +607,7 @@
</span><span class="cx">             yield cal1.createObjectResourceWithName(&quot;1.ics&quot;, VComponent.fromString(
</span><span class="cx"> &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><del>-PRODID:-//Apple Inc.//iCal 4.0.1//EN
</del><ins>+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</ins><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VTIMEZONE
</span><span class="cx"> TZID:US/Pacific
</span><span class="lines">@@ -653,7 +653,7 @@
</span><span class="cx">             yield cal2.createObjectResourceWithName(&quot;2.ics&quot;, VComponent.fromString(
</span><span class="cx"> &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><del>-PRODID:-//Apple Inc.//iCal 4.0.1//EN
</del><ins>+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</ins><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VTIMEZONE
</span><span class="cx"> TZID:US/Pacific
</span><span class="lines">@@ -1557,7 +1557,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_objectResourceWithID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        L{ICalendarHome.objectResourceWithID} will return the calendar object..
</del><ins>+        L{ICalendarHome.objectResourceWithID} will return the calendar object.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         home = yield self.homeUnderTest()
</span><span class="cx">         calendarObject = (yield home.objectResourceWithID(9999))
</span><span class="lines">@@ -1571,7 +1571,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_defaultAlarms(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        L{ICalendarHome.objectResourceWithID} will return the calendar object..
</del><ins>+        L{ICalendarHome.objectResourceWithID} will return the calendar object.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         alarmhome1 = &quot;&quot;&quot;BEGIN:VALARM
</span><span class="lines">@@ -1859,3 +1859,80 @@
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><span class="cx">         self.assertEqual(cal.getTimezone(), None)
</span><span class="cx">         yield self.commit()
</span><ins>+
+
+    @inlineCallbacks
+    def test_calendarRevisionChangeConcurrency(self):
+        &quot;&quot;&quot;
+        Test that two concurrent attempts to add resources in two separate
+        calendar homes does not deadlock on the revision table update.
+        &quot;&quot;&quot;
+
+        calendarStore = self._sqlCalendarStore
+
+        # Make sure homes are provisioned
+        txn = self.transactionUnderTest()
+        home_uid1 = yield txn.homeWithUID(ECALENDARTYPE, &quot;user01&quot;, create=True)
+        home_uid2 = yield txn.homeWithUID(ECALENDARTYPE, &quot;user02&quot;, create=True)
+        self.assertNotEqual(home_uid1, None)
+        self.assertNotEqual(home_uid2, None)
+        yield self.commit()
+
+        # Create first events in different calendar homes
+        txn1 = calendarStore.newTransaction()
+        txn2 = calendarStore.newTransaction()
+
+        calendar_uid1_in_txn1 = yield self.calendarUnderTest(txn1, &quot;calendar&quot;, &quot;user01&quot;)
+        calendar_uid2_in_txn2 = yield self.calendarUnderTest(txn2, &quot;calendar&quot;, &quot;user02&quot;)
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:data%(ctr)s
+DTSTART:20130102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:data%(ctr)s
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        component = Component.fromString(data % {&quot;ctr&quot;: 1})
+        yield calendar_uid1_in_txn1.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
+
+        component = Component.fromString(data % {&quot;ctr&quot;: 2})
+        yield calendar_uid2_in_txn2.createCalendarObjectWithName(&quot;data2.ics&quot;, component)
+
+        # Setup deferreds to run concurrently and create second events in the calendar homes
+        # previously used by the other transaction - this could create the deadlock.
+        @inlineCallbacks
+        def _defer_uid3():
+            calendar_uid1_in_txn2 = yield self.calendarUnderTest(txn2, &quot;calendar&quot;, &quot;user01&quot;)
+            component = Component.fromString(data % {&quot;ctr&quot;: 3})
+            yield calendar_uid1_in_txn2.createCalendarObjectWithName(&quot;data3.ics&quot;, component)
+            yield txn2.commit()
+        d1 = _defer_uid3()
+
+        @inlineCallbacks
+        def _defer_uid4():
+            calendar_uid2_in_txn1 = yield self.calendarUnderTest(txn1, &quot;calendar&quot;, &quot;user02&quot;)
+            component = Component.fromString(data % {&quot;ctr&quot;: 4})
+            yield calendar_uid2_in_txn1.createCalendarObjectWithName(&quot;data4.ics&quot;, component)
+            yield txn1.commit()
+        d2 = _defer_uid4()
+
+        # Now do the concurrent provision attempt
+        yield DeferredList([d1, d2])
+
+        # Verify we did not have a deadlock and all resources have been created.
+        caldata1 = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        caldata2 = yield self.calendarObjectUnderTest(name=&quot;data2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
+        caldata3 = yield self.calendarObjectUnderTest(name=&quot;data3.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        caldata4 = yield self.calendarObjectUnderTest(name=&quot;data4.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
+        self.assertNotEqual(caldata1, None)
+        self.assertNotEqual(caldata2, None)
+        self.assertNotEqual(caldata3, None)
+        self.assertNotEqual(caldata4, None)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py (11398 => 11399)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/sql.py        2013-06-25 13:48:14 UTC (rev 11398)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py        2013-06-25 14:08:23 UTC (rev 11399)
</span><span class="lines">@@ -25,8 +25,6 @@
</span><span class="cx">     &quot;AddressBookObject&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-from uuid import uuid4
-
</del><span class="cx"> from twext.enterprise.dal.syntax import Delete, Insert, Len, Parameter, \
</span><span class="cx">     Update, Union, Max, Select, utcNowSQL
</span><span class="cx"> from twext.enterprise.locking import NamedLock
</span><span class="lines">@@ -37,6 +35,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.python import hashlib
</span><ins>+
</ins><span class="cx"> from twistedcaldav import carddavxml, customxml
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="lines">@@ -60,6 +59,8 @@
</span><span class="cx">     InvalidObjectResourceError, InvalidComponentForStoreError, \
</span><span class="cx">     AllRetriesFailed
</span><span class="cx"> 
</span><ins>+from uuid import uuid4
+
</ins><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -100,7 +101,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _resourceIDAndHomeResourceIDFromOwnerQuery(cls):  #@NoSelf
</del><ins>+    def _resourceIDAndHomeResourceIDFromOwnerQuery(cls): #@NoSelf
</ins><span class="cx">         home = cls._homeSchema
</span><span class="cx">         return Select([home.RESOURCE_ID, home.ADDRESSBOOK_PROPERTY_STORE_ID],
</span><span class="cx">                       From=home, Where=home.OWNER_UID == Parameter(&quot;ownerUID&quot;))
</span><span class="lines">@@ -264,7 +265,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _syncTokenQuery(cls):  #@NoSelf
</del><ins>+    def _syncTokenQuery(cls): #@NoSelf
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DAL Select statement to find the sync token.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -308,7 +309,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _changesQuery(cls):  #@NoSelf
</del><ins>+    def _changesQuery(cls): #@NoSelf
</ins><span class="cx">         rev = cls._revisionsSchema
</span><span class="cx">         return Select(
</span><span class="cx">             [rev.COLLECTION_NAME,
</span><span class="lines">@@ -425,7 +426,7 @@
</span><span class="cx">                 self.viewerHome().uid(),
</span><span class="cx">                 self._txn,
</span><span class="cx">                 self.ownerHome()._addressbookPropertyStoreID,  # not ._resourceID as in CommonHomeChild._loadPropertyStore()
</span><del>-                notifyCallback=self.notifyChanged
</del><ins>+                notifyCallback=self.notifyPropertyChanged
</ins><span class="cx">             )
</span><span class="cx">         super(AddressBook, self)._loadPropertyStore(props)
</span><span class="cx"> 
</span><span class="lines">@@ -476,7 +477,7 @@
</span><span class="cx">             yield self.properties()._removeResource()
</span><span class="cx">             yield self._loadPropertyStore()
</span><span class="cx"> 
</span><del>-            yield self.notifyChanged()
</del><ins>+            yield self.notifyPropertyChanged()
</ins><span class="cx">             yield self._home.notifyChanged()
</span><span class="cx">         else:
</span><span class="cx">             returnValue((yield super(AddressBook, self).remove()))
</span><span class="lines">@@ -551,7 +552,7 @@
</span><span class="cx">                       Where=obj.ADDRESSBOOK_HOME_RESOURCE_ID == Parameter(&quot;addressbookResourceID&quot;),)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _fullySharedAddressBookGroupRow(self):  #@NoSelf
</del><ins>+    def _fullySharedAddressBookGroupRow(self): #@NoSelf
</ins><span class="cx">         return [
</span><span class="cx">             self._resourceID,  # obj.ADDRESSBOOK_HOME_RESOURCE_ID,
</span><span class="cx">             self._resourceID,  # obj.RESOURCE_ID,
</span><span class="lines">@@ -646,7 +647,7 @@
</span><span class="cx">         )
</span><span class="cx">         # get ownerHomeIDs
</span><span class="cx">         for dataRow in dataRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerHome = yield home.ownerHomeWithChildID(resourceID)
</span><span class="cx">             ownerHomeToDataRowMap[ownerHome] = dataRow
</span><span class="cx"> 
</span><span class="lines">@@ -655,7 +656,7 @@
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         for groupBindRow in groupBindRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:cls.bindColumnCount]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
</span><span class="cx">             ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
</span><span class="cx">             if ownerHome not in ownerHomeToDataRowMap:
</span><span class="lines">@@ -678,7 +679,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Create the actual objects merging in properties
</span><span class="cx">             for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
</span><del>-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount]  #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</span><span class="cx">                 metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</span><span class="cx"> 
</span><span class="lines">@@ -772,7 +773,7 @@
</span><span class="cx">         if not rows:
</span><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><del>-        bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage, ownerAddressBookID, cachedBindStatus = rows[0]  #@UnusedVariable
</del><ins>+        bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage, ownerAddressBookID, cachedBindStatus = rows[0] #@UnusedVariable
</ins><span class="cx"> 
</span><span class="cx">         # if wrong status, exit here.  Item is in queryCache
</span><span class="cx">         if (cachedBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="lines">@@ -808,7 +809,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         bindRows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
</span><span class="cx">         if bindRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0] #@UnusedVariable
</ins><span class="cx">             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><span class="lines">@@ -824,7 +825,7 @@
</span><span class="cx">             home._txn, name=name, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         if groupBindRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0] #@UnusedVariable
</ins><span class="cx">             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><span class="lines">@@ -865,7 +866,7 @@
</span><span class="cx">             home._txn, resourceID=resourceID, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         if bindRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0] #@UnusedVariable
</ins><span class="cx">             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><span class="lines">@@ -879,7 +880,7 @@
</span><span class="cx">                     home._txn, homeID=home._resourceID, addressbookID=resourceID
</span><span class="cx">         )
</span><span class="cx">         if groupBindRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0] #@UnusedVariable
</ins><span class="cx">             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><span class="lines">@@ -928,7 +929,7 @@
</span><span class="cx">         ).on(self._txn, resourceID=self._resourceID, homeID=self.viewerHome()._resourceID)
</span><span class="cx"> 
</span><span class="cx">         yield self.invalidateQueryCache()
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx">         '''
</span><span class="cx">         yield None
</span><span class="cx"> 
</span><span class="lines">@@ -947,7 +948,7 @@
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         for row in rows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerHome = yield home._txn.homeWithResourceID(home._homeType, resourceID, create=True)
</span><span class="cx">             names |= set([ownerHome.shareeAddressBookName()])
</span><span class="cx"> 
</span><span class="lines">@@ -955,7 +956,7 @@
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         for groupRow in groupRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
</span><span class="cx">             ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerAddressBookID, create=True)
</span><span class="cx">             names |= set([ownerHome.shareeAddressBookName()])
</span><span class="lines">@@ -1026,7 +1027,7 @@
</span><span class="cx">             readWriteGroupIDs = []
</span><span class="cx">             readOnlyGroupIDs = []
</span><span class="cx">             for groupBindRow in groupBindRows:
</span><del>-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 if bindMode == _BIND_MODE_WRITE:
</span><span class="cx">                     readWriteGroupIDs.append(resourceID)
</span><span class="cx">                 else:
</span><span class="lines">@@ -1074,7 +1075,7 @@
</span><span class="cx">         readWriteGroupIDs = []
</span><span class="cx">         readOnlyGroupIDs = []
</span><span class="cx">         for groupBindRow in groupBindRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             if bindMode == _BIND_MODE_WRITE:
</span><span class="cx">                 readWriteGroupIDs.append(resourceID)
</span><span class="cx">             else:
</span><span class="lines">@@ -1189,7 +1190,7 @@
</span><span class="cx">             shareeView._name = sharedname[0][0]
</span><span class="cx"> 
</span><span class="cx">             # Must send notification to ensure cache invalidation occurs
</span><del>-            yield self.notifyChanged()
</del><ins>+            yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx">         returnValue(shareeView._name)
</span><span class="cx"> 
</span><span class="lines">@@ -1213,7 +1214,7 @@
</span><span class="cx">                 self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
</span><span class="cx">             )
</span><span class="cx">             for bindRow in bindRows:
</span><del>-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
</span><span class="cx">                 new = yield home.childWithName(self.shareeAddressBookName())
</span><span class="cx">                 result.append(new)
</span><span class="lines">@@ -1240,7 +1241,7 @@
</span><span class="cx">                 self._txn, resourceID=self._resourceID
</span><span class="cx">             )
</span><span class="cx">             for bindRow in bindRows:
</span><del>-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
</span><span class="cx">                 new = yield self.objectWithName(home, self.shareeAddressBookName(), accepted=False)
</span><span class="cx">                 result.append(new)
</span><span class="lines">@@ -1277,7 +1278,7 @@
</span><span class="cx">                 self._objectNames = None
</span><span class="cx"> 
</span><span class="cx">             # Must send notification to ensure cache invalidation occurs
</span><del>-            yield self.notifyChanged()
</del><ins>+            yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx">         # delete binds including invites
</span><span class="cx">         deletedBindNameRows = yield self._deleteBindForResourceIDAndHomeID.on(self._txn, resourceID=self._resourceID,
</span><span class="lines">@@ -1308,7 +1309,7 @@
</span><span class="cx">     #_homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, addressbook, name, uid, resourceID=None, options=None):  #@UnusedVariable
</del><ins>+    def __init__(self, addressbook, name, uid, resourceID=None, options=None): #@UnusedVariable
</ins><span class="cx"> 
</span><span class="cx">         self._kind = None
</span><span class="cx">         self._ownerAddressBookResourceID = None
</span><span class="lines">@@ -1443,7 +1444,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _allColumnsWithResourceID(cls):  #@NoSelf
</del><ins>+    def _allColumnsWithResourceID(cls): #@NoSelf
</ins><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><span class="cx">             cls._allColumns, From=obj,
</span><span class="lines">@@ -1542,7 +1543,7 @@
</span><span class="cx"> 
</span><span class="cx">                 if groupBindRows:
</span><span class="cx">                     groupBindRow = groupBindRows[0]
</span><del>-                    bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+                    bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                     self._bindMode = bindMode
</span><span class="cx">                     self._bindStatus = bindStatus
</span><span class="cx">                     self._bindMessage = bindMessage
</span><span class="lines">@@ -1559,7 +1560,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _allColumns(cls):  #@NoSelf
</del><ins>+    def _allColumns(cls): #@NoSelf
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Full set of columns in the object table that need to be loaded to
</span><span class="cx">         initialize the object resource state.
</span><span class="lines">@@ -1673,7 +1674,7 @@
</span><span class="cx">         self.validAddressDataCheck(component, inserting)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def validAddressDataCheck(self, component, inserting):  #@UnusedVariable
</del><ins>+    def validAddressDataCheck(self, component, inserting): #@UnusedVariable
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Check that the calendar data is valid iCalendar.
</span><span class="cx">         @return:         tuple: (True/False if the calendar data is valid,
</span><span class="lines">@@ -1779,7 +1780,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _insertABObject(cls):  #@NoSelf
</del><ins>+    def _insertABObject(cls): #@NoSelf
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DAL statement to create an addressbook object with all default values.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1799,7 +1800,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def updateDatabase(self, component, expand_until=None, reCreate=False,  #@UnusedVariable
</del><ins>+    def updateDatabase(self, component, expand_until=None, reCreate=False, #@UnusedVariable
</ins><span class="cx">                        inserting=False):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Update the database tables for the new data being written.
</span><span class="lines">@@ -2150,7 +2151,7 @@
</span><span class="cx"> 
</span><span class="cx">     # same as CommonHomeChild._childrenAndMetadataForHomeID() w/o metadata join
</span><span class="cx">     @classproperty
</span><del>-    def _childrenAndMetadataForHomeID(cls):  #@NoSelf
</del><ins>+    def _childrenAndMetadataForHomeID(cls): #@NoSelf
</ins><span class="cx">         bind = cls._bindSchema
</span><span class="cx">         child = cls._objectSchema
</span><span class="cx">         columns = cls.bindColumns() + cls.additionalBindColumns() + cls.metadataColumns()
</span><span class="lines">@@ -2186,7 +2187,7 @@
</span><span class="cx">                 self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
</span><span class="cx">             )
</span><span class="cx">             for groupBindRow in groupBindRows:
</span><del>-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
</span><span class="cx">                 addressbook = yield home.childWithName(self._home.shareeAddressBookName())
</span><span class="cx">                 new = yield addressbook.objectResourceWithID(resourceID)
</span><span class="lines">@@ -2215,7 +2216,7 @@
</span><span class="cx">                 self._txn, resourceID=self._resourceID
</span><span class="cx">             )
</span><span class="cx">             for groupBindRow in groupBindRows:
</span><del>-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
</span><span class="cx">                 addressbook = yield home.childWithName(self._home.shareeAddressBookName())
</span><span class="cx">                 if not addressbook:
</span><span class="lines">@@ -2227,7 +2228,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _addressbookIDForResourceID(cls):  #@NoSelf
</del><ins>+    def _addressbookIDForResourceID(cls): #@NoSelf
</ins><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select([obj.PARENT_RESOURCE_ID],
</span><span class="cx">                       From=obj,
</span><span class="lines">@@ -2352,7 +2353,7 @@
</span><span class="cx">                 self._txn, resourceID=self._resourceID, homeID=shareeHome._resourceID
</span><span class="cx">             )
</span><span class="cx">             groupBindRow = groupBindRows[0]
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             if bindStatus == _BIND_STATUS_ACCEPTED:
</span><span class="cx">                 group = yield shareeHome.objectWithShareUID(bindName)
</span><span class="cx">             else:
</span><span class="lines">@@ -2465,7 +2466,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _acceptedBindForHomeIDAndAddressBookID(cls):  #@NoSelf
</del><ins>+    def _acceptedBindForHomeIDAndAddressBookID(cls): #@NoSelf
</ins><span class="cx">         bind = cls._bindSchema
</span><span class="cx">         abo = cls._objectSchema
</span><span class="cx">         return Select(
</span><span class="lines">@@ -2479,7 +2480,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _unacceptedBindForHomeIDAndAddressBookID(cls):  #@NoSelf
</del><ins>+    def _unacceptedBindForHomeIDAndAddressBookID(cls): #@NoSelf
</ins><span class="cx">         bind = cls._bindSchema
</span><span class="cx">         abo = cls._objectSchema
</span><span class="cx">         return Select(
</span><span class="lines">@@ -2493,7 +2494,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _bindForHomeIDAndAddressBookID(cls):  #@NoSelf
</del><ins>+    def _bindForHomeIDAndAddressBookID(cls): #@NoSelf
</ins><span class="cx">         bind = cls._bindSchema
</span><span class="cx">         abo = cls._objectSchema
</span><span class="cx">         return Select(
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py (11398 => 11399)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py        2013-06-25 13:48:14 UTC (rev 11398)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py        2013-06-25 14:08:23 UTC (rev 11399)
</span><span class="lines">@@ -188,21 +188,21 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def addressbookUnderTest(self, txn=None, name=None):
</del><ins>+    def addressbookUnderTest(self, txn=None, name=None, home=&quot;home1&quot;):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the addressbook detailed by C{requirements['home1']['addressbook']}.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        returnValue((yield (yield self.homeUnderTest(txn=txn))
</del><ins>+        returnValue((yield (yield self.homeUnderTest(txn=txn, name=home))
</ins><span class="cx">             .addressbookWithName(name if name else &quot;addressbook&quot;)))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def addressbookObjectUnderTest(self, txn=None, name=None):
</del><ins>+    def addressbookObjectUnderTest(self, txn=None, name=None, addressbook_name=&quot;addressbook&quot;, home=&quot;home1&quot;):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the addressbook detailed by
</span><span class="cx">         C{requirements['home1']['addressbook']['1.vcf']}.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        returnValue((yield (yield self.addressbookUnderTest(txn=txn))
</del><ins>+        returnValue((yield (yield self.addressbookUnderTest(txn=txn, name=addressbook_name, home=home))
</ins><span class="cx">                     .addressbookObjectWithName(name if name else &quot;1.vcf&quot;)))
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py (11398 => 11399)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py        2013-06-25 13:48:14 UTC (rev 11398)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py        2013-06-25 14:08:23 UTC (rev 11399)
</span><span class="lines">@@ -22,7 +22,7 @@
</span><span class="cx"> from twext.enterprise.dal.syntax import Select, Parameter
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet import reactor
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, DeferredList
</ins><span class="cx"> from twisted.internet.task import deferLater
</span><span class="cx"> 
</span><span class="cx"> from twisted.trial import unittest
</span><span class="lines">@@ -472,7 +472,7 @@
</span><span class="cx">         group = VCard.fromString(
</span><span class="cx">             &quot;&quot;&quot;BEGIN:VCARD
</span><span class="cx"> VERSION:3.0
</span><del>-PRODID:-//Apple Inc.//AddressBook 6.1//EN
</del><ins>+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</ins><span class="cx"> UID:uid2
</span><span class="cx"> FN:Top Group
</span><span class="cx"> N:Top Group;;;;
</span><span class="lines">@@ -489,7 +489,7 @@
</span><span class="cx">         badgroup = VCard.fromString(
</span><span class="cx">             &quot;&quot;&quot;BEGIN:VCARD
</span><span class="cx"> VERSION:3.0
</span><del>-PRODID:-//Apple Inc.//AddressBook 6.1//EN
</del><ins>+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</ins><span class="cx"> UID:uid3
</span><span class="cx"> FN:Bad Group
</span><span class="cx"> N:Bad Group;;;;
</span><span class="lines">@@ -562,7 +562,7 @@
</span><span class="cx">         group = VCard.fromString(
</span><span class="cx">             &quot;&quot;&quot;BEGIN:VCARD
</span><span class="cx"> VERSION:3.0
</span><del>-PRODID:-//Apple Inc.//AddressBook 6.1//EN
</del><ins>+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</ins><span class="cx"> UID:uid2
</span><span class="cx"> FN:Top Group
</span><span class="cx"> N:Top Group;;;;
</span><span class="lines">@@ -585,7 +585,7 @@
</span><span class="cx">         subgroup = VCard.fromString(
</span><span class="cx">             &quot;&quot;&quot;BEGIN:VCARD
</span><span class="cx"> VERSION:3.0
</span><del>-PRODID:-//Apple Inc.//AddressBook 6.1//EN
</del><ins>+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</ins><span class="cx"> UID:uid3
</span><span class="cx"> FN:Sub Group
</span><span class="cx"> N:Sub Group;;;;
</span><span class="lines">@@ -949,3 +949,74 @@
</span><span class="cx">             changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
</span><span class="cx">             self.assertEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+
+
+    @inlineCallbacks
+    def test_addressbookRevisionChangeConcurrency(self):
+        &quot;&quot;&quot;
+        Test that two concurrent attempts to add resources in two separate
+        calendar homes does not deadlock on the revision table update.
+        &quot;&quot;&quot;
+
+        # Make sure homes are provisioned
+        txn = self.transactionUnderTest()
+        home_uid1 = yield txn.homeWithUID(EADDRESSBOOKTYPE, &quot;user01&quot;, create=True)
+        home_uid2 = yield txn.homeWithUID(EADDRESSBOOKTYPE, &quot;user02&quot;, create=True)
+        self.assertNotEqual(home_uid1, None)
+        self.assertNotEqual(home_uid2, None)
+        yield self.commit()
+
+        # Create first events in different calendar homes
+        txn1 = self._sqlStore.newTransaction()
+        txn2 = self._sqlStore.newTransaction()
+
+        addressbook_uid1_in_txn1 = yield self.addressbookUnderTest(txn1, &quot;addressbook&quot;, &quot;user01&quot;)
+        addressbook_uid2_in_txn2 = yield self.addressbookUnderTest(txn2, &quot;addressbook&quot;, &quot;user02&quot;)
+
+        data = &quot;&quot;&quot;BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:data%(ctr)s
+FN:Data %(ctr)s
+N:Sub Group;;;;
+REV:20120503T194243Z
+END:VCARD
+
+&quot;&quot;&quot;
+
+        component = VComponent.fromString(data % {&quot;ctr&quot;: 1})
+        yield addressbook_uid1_in_txn1.createAddressBookObjectWithName(&quot;data1.ics&quot;, component)
+
+        component = VComponent.fromString(data % {&quot;ctr&quot;: 2})
+        yield addressbook_uid2_in_txn2.createAddressBookObjectWithName(&quot;data2.ics&quot;, component)
+
+        # Setup deferreds to run concurrently and create second events in the calendar homes
+        # previously used by the other transaction - this could create the deadlock.
+        @inlineCallbacks
+        def _defer_uid3():
+            addressbook_uid1_in_txn2 = yield self.addressbookUnderTest(txn2, &quot;addressbook&quot;, &quot;user01&quot;)
+            component = VComponent.fromString(data % {&quot;ctr&quot;: 3})
+            yield addressbook_uid1_in_txn2.createAddressBookObjectWithName(&quot;data3.ics&quot;, component)
+            yield txn2.commit()
+        d1 = _defer_uid3()
+
+        @inlineCallbacks
+        def _defer_uid4():
+            addressbook_uid2_in_txn1 = yield self.addressbookUnderTest(txn1, &quot;addressbook&quot;, &quot;user02&quot;)
+            component = VComponent.fromString(data % {&quot;ctr&quot;: 4})
+            yield addressbook_uid2_in_txn1.createAddressBookObjectWithName(&quot;data4.ics&quot;, component)
+            yield txn1.commit()
+        d2 = _defer_uid4()
+
+        # Now do the concurrent provision attempt
+        yield DeferredList([d1, d2])
+
+        # Verify we did not have a deadlock and all resources have been created.
+        vcarddata1 = yield self.addressbookObjectUnderTest(name=&quot;data1.ics&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
+        vcarddata2 = yield self.addressbookObjectUnderTest(name=&quot;data2.ics&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user02&quot;)
+        vcarddata3 = yield self.addressbookObjectUnderTest(name=&quot;data3.ics&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
+        vcarddata4 = yield self.addressbookObjectUnderTest(name=&quot;data4.ics&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user02&quot;)
+        self.assertNotEqual(vcarddata1, None)
+        self.assertNotEqual(vcarddata2, None)
+        self.assertNotEqual(vcarddata3, None)
+        self.assertNotEqual(vcarddata4, None)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql.py (11398 => 11399)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql.py        2013-06-25 13:48:14 UTC (rev 11398)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py        2013-06-25 14:08:23 UTC (rev 11399)
</span><span class="lines">@@ -25,39 +25,44 @@
</span><span class="cx">     &quot;CommonHome&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-import sys
</del><ins>+from cStringIO import StringIO
</ins><span class="cx"> 
</span><del>-from uuid import uuid4, UUID
</del><ins>+from pycalendar.datetime import PyCalendarDateTime
</ins><span class="cx"> 
</span><del>-from zope.interface import implements, directlyProvides
-
</del><ins>+from twext.enterprise.dal.syntax import \
+    Delete, utcNowSQL, Union, Insert, Len, Max, Parameter, SavepointAction, \
+    Select, Update, ColumnSyntax, TableSyntax, Upper, Count, ALL_COLUMNS, Sum
+from twext.enterprise.ienterprise import AlreadyFinishedError
+from twext.enterprise.queue import LocalQueuer
+from twext.enterprise.util import parseSQLTimestamp
+from twext.internet.decorate import memoizedKey, Memoizable
+from twext.python.clsprop import classproperty
</ins><span class="cx"> from twext.python.log import Logger
</span><del>-
-from txdav.xml.parser import WebDAVDocument
</del><span class="cx"> from twext.web2.http_headers import MimeType
</span><span class="cx"> 
</span><ins>+from twisted.application.service import Service
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</ins><span class="cx"> from twisted.python import hashlib
</span><ins>+from twisted.python.failure import Failure
</ins><span class="cx"> from twisted.python.modules import getModule
</span><span class="cx"> from twisted.python.util import FancyEqMixin
</span><del>-from twisted.python.failure import Failure
</del><span class="cx"> 
</span><del>-from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</del><ins>+from twistedcaldav.config import config
+from twistedcaldav.customxml import NotificationType
+from twistedcaldav.dateops import datetimeMktime, pyCalendarTodatetime
</ins><span class="cx"> 
</span><del>-from twisted.application.service import Service
-
</del><span class="cx"> from txdav.base.datastore.util import QueryCacher
</span><del>-
-from twext.internet.decorate import memoizedKey, Memoizable
-
</del><ins>+from txdav.base.datastore.util import normalizeUUIDOrNot
+from txdav.base.propertystore.none import PropertyStore as NonePropertyStore
+from txdav.base.propertystore.sql import PropertyStore
</ins><span class="cx"> from txdav.caldav.icalendarstore import ICalendarTransaction, ICalendarStore
</span><del>-
</del><span class="cx"> from txdav.carddav.iaddressbookstore import IAddressBookTransaction
</span><del>-
</del><span class="cx"> from txdav.common.datastore.common import HomeChildBase
</span><del>-from txdav.common.datastore.sql_tables import schema, splitSQLString
</del><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
</span><span class="cx">     _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED
</span><ins>+from txdav.common.datastore.sql_tables import schema, splitSQLString
+from txdav.common.icommondatastore import ConcurrentModification
</ins><span class="cx"> from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
</span><span class="cx">     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
</span><span class="lines">@@ -66,30 +71,13 @@
</span><span class="cx"> from txdav.common.idirectoryservice import IStoreDirectoryService
</span><span class="cx"> from txdav.common.inotifications import INotificationCollection, \
</span><span class="cx">     INotificationObject
</span><ins>+from txdav.xml.parser import WebDAVDocument
</ins><span class="cx"> 
</span><del>-from twext.python.clsprop import classproperty
-from twext.enterprise.ienterprise import AlreadyFinishedError
</del><ins>+from uuid import uuid4, UUID
</ins><span class="cx"> 
</span><del>-from twext.enterprise.dal.syntax import \
-    Delete, utcNowSQL, Union, Insert, Len, Max, Parameter, SavepointAction, \
-    Select, Update, ColumnSyntax, TableSyntax, Upper, Count, ALL_COLUMNS, Sum
</del><ins>+from zope.interface import implements, directlyProvides
</ins><span class="cx"> 
</span><del>-from twistedcaldav.config import config
-
-from txdav.base.propertystore.none import PropertyStore as NonePropertyStore
-from txdav.base.propertystore.sql import PropertyStore
-
-from txdav.common.icommondatastore import ConcurrentModification
-from twistedcaldav.customxml import NotificationType
-from twistedcaldav.dateops import datetimeMktime, pyCalendarTodatetime
-
-from txdav.base.datastore.util import normalizeUUIDOrNot
-from twext.enterprise.queue import LocalQueuer
-from twext.enterprise.util import parseSQLTimestamp
-
-from pycalendar.datetime import PyCalendarDateTime
-
-from cStringIO import StringIO
</del><ins>+import sys
</ins><span class="cx"> import time
</span><span class="cx"> 
</span><span class="cx"> current_sql_schema = getModule(__name__).filePath.sibling(&quot;sql_schema&quot;).child(&quot;current.sql&quot;).getContent()
</span><span class="lines">@@ -1970,7 +1958,12 @@
</span><span class="cx">                                 deleted.add(&quot;%s/%s&quot; % (path, name,))
</span><span class="cx"> 
</span><span class="cx">                 for path, name, wasdeleted in results:
</span><del>-                    changed.add(&quot;%s/%s&quot; % (path, &quot;&quot; if depth == &quot;1&quot; else name,))
</del><ins>+                    # Always report collection as changed
+                    changed.add(&quot;%s/&quot; % (path,))
+                    if name:
+                        # Resource changed - for depth &quot;infinity&quot; report resource as changed
+                        if depth != &quot;1&quot;:
+                            changed.add(&quot;%s/%s&quot; % (path, name,))
</ins><span class="cx"> 
</span><span class="cx">         changed = sorted(changed)
</span><span class="cx">         deleted = sorted(deleted)
</span><span class="lines">@@ -2783,7 +2776,7 @@
</span><span class="cx">                 yield shareeView._initBindRevision()
</span><span class="cx"> 
</span><span class="cx">         # Must send notification to ensure cache invalidation occurs
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx">         yield shareeHome.notifyChanged()
</span><span class="cx"> 
</span><span class="cx">         returnValue(bindName)
</span><span class="lines">@@ -2862,7 +2855,7 @@
</span><span class="cx">             shareeView._name = sharedname[0][0]
</span><span class="cx"> 
</span><span class="cx">             # Must send notification to ensure cache invalidation occurs
</span><del>-            yield self.notifyChanged()
</del><ins>+            yield self.notifyPropertyChanged()
</ins><span class="cx">             yield shareeView.viewerHome().notifyChanged()
</span><span class="cx"> 
</span><span class="cx">         returnValue(shareeView._name)
</span><span class="lines">@@ -2890,7 +2883,7 @@
</span><span class="cx">                 shareeHome._children.pop(shareeChild._resourceID, None)
</span><span class="cx"> 
</span><span class="cx">                 # Must send notification to ensure cache invalidation occurs
</span><del>-                yield self.notifyChanged()
</del><ins>+                yield self.notifyPropertyChanged()
</ins><span class="cx">                 yield shareeHome.notifyChanged()
</span><span class="cx">                 break
</span><span class="cx"> 
</span><span class="lines">@@ -3048,7 +3041,7 @@
</span><span class="cx">         ).on(self._txn, resourceID=self._resourceID, homeID=self.viewerHome()._resourceID)
</span><span class="cx"> 
</span><span class="cx">         yield self.invalidateQueryCache()
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def shareStatus(self):
</span><span class="lines">@@ -3471,7 +3464,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Change notification for a create is on the home collection
</span><span class="cx">         yield home.notifyChanged()
</span><del>-        yield child.notifyChanged()
</del><ins>+        yield child.notifyPropertyChanged()
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3591,7 +3584,7 @@
</span><span class="cx">         self._home._children[name] = self
</span><span class="cx">         yield self._renameSyncToken()
</span><span class="cx"> 
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx">         yield self._home.notifyChanged()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3608,7 +3601,7 @@
</span><span class="cx">     def remove(self):
</span><span class="cx"> 
</span><span class="cx">         # Do before setting _resourceID making changes
</span><del>-        yield self.notifyChanged()
</del><ins>+        yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx">         queryCacher = self._home._txn._queryCacher
</span><span class="cx">         if queryCacher:
</span><span class="lines">@@ -3997,7 +3990,7 @@
</span><span class="cx">                 self.viewerHome().uid(),
</span><span class="cx">                 self._txn,
</span><span class="cx">                 self._resourceID,
</span><del>-                notifyCallback=self.notifyChanged
</del><ins>+                notifyCallback=self.notifyPropertyChanged
</ins><span class="cx">             )
</span><span class="cx">         self.initPropertyStore(props)
</span><span class="cx">         self._properties = props
</span><span class="lines">@@ -4056,12 +4049,35 @@
</span><span class="cx">         return self.ownerHome().notifierID()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
</del><span class="cx">     def notifyChanged(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        Send notifications when a child resource is changed.
+        &quot;&quot;&quot;
+        return self._notifyChanged(property_change=False)
+
+
+    def notifyPropertyChanged(self):
+        &quot;&quot;&quot;
+        Send notifications when properties on this object change.
+        &quot;&quot;&quot;
+        return self._notifyChanged(property_change=True)
+
+
+    @inlineCallbacks
+    def _notifyChanged(self, property_change=False):
+        &quot;&quot;&quot;
</ins><span class="cx">         Send notifications, change sync token and bump last modified because
</span><span class="cx">         the resource has changed.  We ensure we only do this once per object
</span><span class="cx">         per transaction.
</span><ins>+
+        Note that we distinguish between property changes and child resource changes. For property
+        changes we need to bump the collections revision token, but we must not do that for child
+        changes because that can cause a deadlock (plus it is not needed as the overall revision
+        token includes the child token via the max() construct in the query).
+
+        @param property_change: indicates whether this is the result of a property change as opposed to
+            a child resource being added, changed or removed.
+        @type property_change: C{bool}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if self._txn.isNotifiedAlready(self):
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -4071,8 +4087,9 @@
</span><span class="cx">         if self._resourceID:
</span><span class="cx">             yield self.bumpModified()
</span><span class="cx"> 
</span><del>-            # We now also bump the collection level sync token on any change
-            yield self._bumpSyncToken()
</del><ins>+            # Bump the collection level sync token only for property change
+            if property_change:
+                yield self._bumpSyncToken()
</ins><span class="cx"> 
</span><span class="cx">         # Send notifications
</span><span class="cx">         if self._notifiers:
</span></span></pre>
</div>
</div>

</body>
</html>