Skip to content

Commit f659675

Browse files
authored
Merge pull request #3622 from adamretter/hotfix/document-use-path-locks
Fix NPE and Deadlock when Path Locks are used for Documents
2 parents 9ad24bd + 03c3503 commit f659675

File tree

16 files changed

+1343
-296
lines changed

16 files changed

+1343
-296
lines changed

exist-core/src/main/java/org/exist/storage/NativeBroker.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2209,7 +2209,8 @@ public LockedDocument getXMLResource(XmldbURI fileName, final LockMode lockMode)
22092209
//TODO : resolve URIs !
22102210
final XmldbURI collUri = fileName.removeLastSegment();
22112211
final XmldbURI docUri = fileName.lastSegment();
2212-
try(final Collection collection = openCollection(collUri, LockMode.READ_LOCK)) {
2212+
final LockMode collectionLockMode = lockManager.relativeCollectionLockMode(LockMode.READ_LOCK, lockMode);
2213+
try(final Collection collection = openCollection(collUri, collectionLockMode)) {
22132214
if (collection == null) {
22142215
LOG.debug("Collection '{}' not found!", collUri);
22152216
return null;

exist-core/src/main/java/org/exist/storage/lock/LockManager.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,12 @@ public ManagedDocumentLock acquireDocumentWriteLock(final XmldbURI documentPath)
576576
* @return true if a WRITE_LOCK is held
577577
*/
578578
public boolean isDocumentLockedForWrite(final XmldbURI documentPath) {
579-
final MultiLock existingLock = getDocumentLock(documentPath.toString());
579+
final MultiLock existingLock;
580+
if (!usePathLocksForDocuments) {
581+
existingLock = getDocumentLock(documentPath.toString());
582+
} else {
583+
existingLock = getPathLock(documentPath.toString());
584+
}
580585
return existingLock.getWriteLockCount() > 0;
581586
}
582587

@@ -588,10 +593,57 @@ public boolean isDocumentLockedForWrite(final XmldbURI documentPath) {
588593
* @return true if a READ_LOCK is held
589594
*/
590595
public boolean isDocumentLockedForRead(final XmldbURI documentPath) {
591-
final MultiLock existingLock = getDocumentLock(documentPath.toString());
596+
final MultiLock existingLock;
597+
if (!usePathLocksForDocuments) {
598+
existingLock = getDocumentLock(documentPath.toString());
599+
} else {
600+
existingLock = getPathLock(documentPath.toString());
601+
}
592602
return existingLock.getReadLockCount() > 0;
593603
}
594604

605+
/**
606+
* Returns the LockMode that should be used for accessing
607+
* a Collection when that access is just for the purposes
608+
* or accessing a document(s) within the Collection.
609+
*
610+
* When Path Locks are enabled for Documents, both Collections and
611+
* Documents share the same lock domain, a path based tree hierarchy.
612+
* With a shared lock-hierarchy, if we were to READ_LOCK a Collection
613+
* and then request a WRITE_LOCK for a Document in that Collection we
614+
* would reach a dead-lock situation. To avoid this, if this method is
615+
* called like
616+
* {@code relativeCollectionLockMode(LockMode.READ_LOCK, LockMode.WRITE_LOCK)}
617+
* it will return a WRITE_LOCK. That is to say that to aid dealock-avoidance,
618+
* this function may return a stricter locking mode than the {@code desiredCollectionLockMode}.
619+
*
620+
* When Path Locks are disabled (the default) for Documents, Collection and Documents
621+
* have independent locking domains. In this case this function will always return
622+
* the {@code desiredCollectionLockMode}.
623+
*
624+
* @param desiredCollectionLockMode The desired lock mode for the Collection.
625+
* @param documentLockMode The lock mode that will be used for subsequent document operations in the Collection.
626+
*
627+
* @return The lock mode that should be used for accessing the Collection.
628+
*/
629+
public Lock.LockMode relativeCollectionLockMode(final Lock.LockMode desiredCollectionLockMode,
630+
final Lock.LockMode documentLockMode) {
631+
if (!usePathLocksForDocuments) {
632+
return desiredCollectionLockMode;
633+
634+
} else {
635+
switch (documentLockMode) {
636+
case NO_LOCK:
637+
case INTENTION_READ:
638+
case READ_LOCK:
639+
return Lock.LockMode.READ_LOCK;
640+
641+
default:
642+
return Lock.LockMode.WRITE_LOCK;
643+
}
644+
}
645+
}
646+
595647
/**
596648
* Retrieves a lock for a {@link org.exist.storage.dom.DOMFile}
597649
*

exist-core/src/main/java/org/exist/xmlrpc/RpcConnection.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,11 @@ protected LockedDocumentMap beginProtected(final DBBroker broker, final Map<Stri
275275
do {
276276
MutableDocumentSet docs = null;
277277
final LockedDocumentMap lockedDocuments = new LockedDocumentMap();
278-
try(final Collection coll = broker.openCollection(XmldbURI.createInternal(protectColl), LockMode.READ_LOCK)) {
278+
final LockMode documentLockMode = LockMode.WRITE_LOCK;
279+
final LockMode collectionLockMode = broker.getBrokerPool().getLockManager().relativeCollectionLockMode(LockMode.READ_LOCK, documentLockMode);
280+
try (final Collection coll = broker.openCollection(XmldbURI.createInternal(protectColl), collectionLockMode)) {
279281
docs = new DefaultDocumentSet();
280-
coll.allDocs(broker, docs, true, lockedDocuments, LockMode.WRITE_LOCK);
282+
coll.allDocs(broker, docs, true, lockedDocuments, documentLockMode);
281283
return lockedDocuments;
282284
} catch (final LockException e) {
283285
LOG.warn("Deadlock detected. Starting over again. Docs: {}; locked: {}. Cause: {}", docs.getDocumentCount(), lockedDocuments.size(), e.getMessage());

exist-core/src/test/java/org/exist/collections/CollectionRemovalTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private void removeCollection(final String user, final String password, final Xm
134134
private void retrieveDoc(final XmldbURI uri) throws EXistException, PermissionDeniedException, SAXException, LockException {
135135
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
136136
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()));
137-
final Collection test = broker.openCollection(uri, LockMode.WRITE_LOCK)) {
137+
final Collection test = broker.openCollection(uri, LockMode.READ_LOCK)) {
138138
assertNotNull(test);
139139

140140
try(final LockedDocument lockedDoc = test.getDocumentWithLock(broker, XmldbURI.createInternal("document.xml"), LockMode.READ_LOCK)) {

exist-core/src/test/java/org/exist/collections/triggers/HistoryTriggerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public void storeAndOverwriteByCopy() throws EXistException, PermissionDeniedExc
132132
storeInTestCollection(transaction, broker, testDoc2Name, testDoc2Content);
133133

134134
// overwrite the first document by copying the second over it (and make sure we don't get a StackOverflow exception)
135-
try(final Collection testCollection = broker.openCollection(TEST_COLLECTION_URI, Lock.LockMode.WRITE_LOCK);) {
135+
try(final Collection testCollection = broker.openCollection(TEST_COLLECTION_URI, Lock.LockMode.WRITE_LOCK)) {
136136
assertNotNull(testCollection);
137137

138138
try(final LockedDocument lockedDoc2 = testCollection.getDocumentWithLock(broker, testDoc2Name, Lock.LockMode.READ_LOCK)) {

exist-core/src/test/java/org/exist/config/TwoDatabasesTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private Collection storeBin(final DBBroker broker, final Txn txn, String suffix)
143143
return top;
144144
}
145145

146-
private boolean getBin(final DBBroker broker, final String suffix) throws PermissionDeniedException, IOException, LockException {
146+
private boolean getBin(final DBBroker broker, final String suffix) throws PermissionDeniedException, IOException {
147147
BinaryDocument binDoc = null;
148148
try(final Collection top = broker.openCollection(XmldbURI.create("xmldb:exist:///db"), LockMode.READ_LOCK)) {
149149
binDoc = (BinaryDocument) top.getDocument(broker, XmldbURI.create("bin"));

exist-core/src/test/java/org/exist/util/XMLReaderSecurityTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public void expandExternalEntities() throws EXistException, IOException, Permiss
159159

160160
try (final Collection testCollection = broker.openCollection(TEST_COLLECTION, Lock.LockMode.READ_LOCK)) {
161161

162-
try (final LockedDocument testDoc = testCollection.getDocumentWithLock(broker, docName, Lock.LockMode.READ_LOCK);){
162+
try (final LockedDocument testDoc = testCollection.getDocumentWithLock(broker, docName, Lock.LockMode.READ_LOCK)) {
163163

164164
// release the collection lock early inline with asymmetrical locking
165165
testCollection.close();
@@ -192,7 +192,7 @@ public void cannotExpandExternalEntitiesWhenDisabled() throws EXistException, IO
192192
try (final DBBroker broker = brokerPool.get(Optional.of(brokerPool.getSecurityManager().getSystemSubject()));
193193
final Txn transaction = brokerPool.getTransactionManager().beginTransaction()) {
194194

195-
try (final Collection testCollection = broker.openCollection(TEST_COLLECTION, Lock.LockMode.WRITE_LOCK);){
195+
try (final Collection testCollection = broker.openCollection(TEST_COLLECTION, Lock.LockMode.WRITE_LOCK)) {
196196

197197
//debugReader("cannotExpandExternalEntitiesWhenDisabled", broker, testCollection);
198198

exist-core/src/test/java/org/exist/xquery/functions/securitymanager/PermissionsFunctionChownTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1602,7 +1602,7 @@ private static void assertDocumentSetUidSetGid(final Subject execAsUser, final X
16021602
private static void assertCollectionSetUidSetGid(final Subject execAsUser, final XmldbURI uri, final boolean isSet) throws EXistException, PermissionDeniedException {
16031603
final BrokerPool pool = existWebServer.getBrokerPool();
16041604
try (final DBBroker broker = pool.get(Optional.of(execAsUser))) {
1605-
try (final Collection col = broker.openCollection(uri, Lock.LockMode.READ_LOCK);) {
1605+
try (final Collection col = broker.openCollection(uri, Lock.LockMode.READ_LOCK)) {
16061606

16071607
if (isSet) {
16081608
assertTrue(col.getPermissions().isSetUid());

0 commit comments

Comments
 (0)