• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.14
/exist-core/src/main/java/org/exist/storage/lock/LockManager.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 */
24
package org.exist.storage.lock;
25

26
import org.apache.logging.log4j.LogManager;
27
import org.apache.logging.log4j.Logger;
28
import org.exist.storage.lock.Lock.LockType;
29
import org.exist.util.Configuration;
30
import org.exist.util.LockException;
31
import org.exist.util.WeakLazyStripes;
32
import org.exist.xmldb.XmldbURI;
33
import uk.ac.ic.doc.slurp.multilock.MultiLock;
34

35
import java.util.concurrent.locks.ReentrantLock;
36
import java.util.function.Consumer;
37

38
/**
39
 * A Lock Manager for Locks that are used across
40
 * database instance functions.
41
 *
42
 * There is a unique lock for each ID, and calls with the same
43
 * ID will always return the same lock. Different IDs will always
44
 * receive different locks.
45
 *
46
 * The locking protocol for Collection locks is taken from the paper:
47
 *     <a href="https://pdfs.semanticscholar.org/5acd/43c51fa5e677b0c242b065a64f5948af022c.pdf">Granularity of Locks in a Shared Data Base - Gray, Lorie and Putzolu 1975</a>.
48
 * specifically we have adopted the acquisition algorithm from Section 3.2 of the paper.
49
 *
50
 * Our adaptions enable us to specify either a multi-writer/multi-reader approach between Collection
51
 * sub-trees or a single-writer/multi-reader approach on the entire Collection tree.
52
 *
53
 * The uptake is that locking a Collection, also implicitly implies locking all descendant
54
 * Collections with the same mode. This reduces the amount of locks required for
55
 * manipulating Collection sub-trees.
56
 *
57
 * The locking protocol for Documents is entirely flat, and is unrelated to Collection locking.
58
 * Deadlocks can still occur between Collections and Documents (as they could in the past).
59
 * If it becomes necessary to eliminate such Collection/Document deadlock scenarios, Document locks
60
 * could be acquired using the same protocol as Collection locks (as really they are all just URI paths in a hierarchy)!
61
 *
62
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
63
 */
64
public class LockManager {
65

66
    // org.exist.util.Configuration properties
67
    public final static String CONFIGURATION_UPGRADE_CHECK = "lock-manager.upgrade-check";
68
    public final static String CONFIGURATION_WARN_WAIT_ON_READ_FOR_WRITE = "lock-manager.warn-wait-on-read-for-write";
69
    public final static String CONFIGURATION_PATH_LOCKS_FOR_DOCUMENTS = "lock-manager.document.use-path-locks";
70
    public final static String CONFIGURATION_PATHS_MULTI_WRITER = "lock-manager.paths-multi-writer";
71

72
    //TODO(AR) remove eventually!
73
    // legacy properties for overriding the config
74
    public final static String PROP_ENABLE_PATHS_MULTI_WRITER = "exist.lockmanager.paths-multiwriter";
75
    public final static String PROP_UPGRADE_CHECK = "exist.lockmanager.upgrade.check";
76
    public final static String PROP_WARN_WAIT_ON_READ_FOR_WRITE = "exist.lockmanager.warn.waitonreadforwrite";
77

78
    private static final Logger LOG = LogManager.getLogger(LockManager.class);
1✔
79

80
    /**
81
     * Set to true to use the path Hierarchy for document locks
82
     * as opposed to separating Collection and Document locks
83
     */
84
    private final boolean usePathLocksForDocuments;
85

86
    /**
87
     * Set to true to enable Multi-Writer/Multi-Reader semantics for
88
     * the path Hierarchy as opposed to the default Single-Writer/Multi-Reader
89
     */
90
    private final boolean pathsMultiWriter;
91

92
    /**
93
     * Set to true to enable checking for lock upgrading within the same
94
     * thread, i.e. READ_LOCK -> WRITE_LOCK
95
     */
96
    private final boolean upgradeCheck;
97

98
    /**
99
     * Set to true to enable warning when a thread wants to acquire the WRITE_LOCK
100
     * but another thread holds the READ_LOCK
101
     */
102
    private final boolean warnWaitOnReadForWrite;
103

104

105
    private final LockTable lockTable;
106
    private final WeakLazyStripes<String, MultiLock> pathLocks;
107
    private final WeakLazyStripes<String, MultiLock> documentLocks;
108
    private final WeakLazyStripes<String, ReentrantLock> btreeLocks;
109

110
    /**
111
     * @param configuration database configuration
112
     * @param concurrencyLevel Concurrency Level of the lock table.
113
     */
114
    public LockManager(final Configuration configuration, final int concurrencyLevel) {
1✔
115
        // set configuration
116
        this.usePathLocksForDocuments = getConfigPropertyBool(configuration, CONFIGURATION_PATH_LOCKS_FOR_DOCUMENTS, false);
1✔
117
        this.pathsMultiWriter = getLegacySystemPropertyOrConfigPropertyBool(PROP_ENABLE_PATHS_MULTI_WRITER, configuration, CONFIGURATION_PATHS_MULTI_WRITER, false);
1✔
118
        this.upgradeCheck = getLegacySystemPropertyOrConfigPropertyBool(PROP_UPGRADE_CHECK, configuration, CONFIGURATION_UPGRADE_CHECK, false);
1✔
119
        this.warnWaitOnReadForWrite = getLegacySystemPropertyOrConfigPropertyBool(PROP_WARN_WAIT_ON_READ_FOR_WRITE, configuration, CONFIGURATION_WARN_WAIT_ON_READ_FOR_WRITE, false);
1✔
120

121
        this.lockTable = new LockTable(configuration);
1✔
122
        this.pathLocks = new WeakLazyStripes<>(concurrencyLevel, LockManager::createCollectionLock);
1✔
123
        if (!usePathLocksForDocuments) {
1!
124
            this.documentLocks = new WeakLazyStripes<>(concurrencyLevel, LockManager::createDocumentLock);
1✔
125
        } else {
1✔
126
            this.documentLocks = null;
×
127
        }
128
        this.btreeLocks = new WeakLazyStripes<>(concurrencyLevel, LockManager::createBtreeLock);
1✔
129

130
        LOG.info("Configured LockManager with concurrencyLevel={} use-path-locks-for-documents={} paths-multi-writer={}", concurrencyLevel, usePathLocksForDocuments, pathsMultiWriter);
1✔
131
    }
1✔
132

133
    /**
134
     * Reserved for testing!
135
     *
136
     * @param concurrencyLevel Concurrency Level of the lock table.
137
     */
138
    LockManager(final int concurrencyLevel) {
139
        this(null, concurrencyLevel);
1✔
140
    }
1✔
141

142
    /**
143
     * Get the lock table.
144
     *
145
     * @return the lock table.
146
     */
147
    public LockTable getLockTable() {
148
        return lockTable;
1✔
149
    }
150

151
    /**
152
     * Creates a new lock for a Collection
153
     * will be Striped by the collectionPath.
154
     *
155
     * @param collectionPath the collection path
156
     *
157
     * @return the document lock
158
     */
159
    private static MultiLock createCollectionLock(final String collectionPath) {
160
        return new MultiLock();
1✔
161
    }
162

163
    /**
164
     * Creates a new MultiLock lock for a Document
165
     * will be Striped by the documentPath.
166
     *
167
     * @param documentPath the document path
168
     *
169
     * @return the document lock
170
     */
171
    private static MultiLock createDocumentLock(final String documentPath) {
172
        return new MultiLock();
1✔
173
    }
174

175
    /**
176
     * Creates a new lock for a {@link org.exist.storage.btree.BTree}
177
     * will be Striped by the btreeFileName
178
     */
179
    private static ReentrantLock createBtreeLock(final String btreeFileName) {
180
        return new ReentrantLock();
1✔
181
    }
182

183
    /**
184
     * Retrieves a lock for a Path
185
     *
186
     * This function is concerned with just the lock object
187
     * and has no knowledge of the state of the lock. The only
188
     * guarantee is that if this lock has not been requested before
189
     * then it will be provided in the unlocked state
190
     *
191
     * @param path The path for which a lock is requested
192
     *
193
     * @return A lock for the path
194
     */
195
    MultiLock getPathLock(final String path) {
196
        return pathLocks.get(path);
1✔
197
    }
198

199
    /**
200
     * Acquires a READ_LOCK on a Collection (and implicitly all descendant Collections).
201
     *
202
     * @param collectionPath The path of the Collection for which a lock is requested.
203
     *
204
     * @return A READ_LOCK on the Collection.
205
     * @throws LockException if a lock error occurs
206
     */
207
    public ManagedCollectionLock acquireCollectionReadLock(final XmldbURI collectionPath) throws LockException {
208
        final LockGroup lockGroup = acquirePathReadLock(LockType.COLLECTION, collectionPath);
1✔
209
        return new ManagedCollectionLock(
1✔
210
                collectionPath,
1✔
211
                lockGroup,
1✔
212
                lockTable);
1✔
213
    }
214

215
    /**
216
     * Acquires a READ_LOCK on a database path (and implicitly all descendant paths).
217
     *
218
     * @param lockType The type of the lock
219
     * @param path The path for which a lock is requested.
220
     *
221
     * @return A READ_LOCK on the Collection.
222
     *
223
     * @throws LockException if a lock error occurs
224
     */
225
    public LockGroup acquirePathReadLock(final LockType lockType, final XmldbURI path) throws LockException {
226
        final XmldbURI[] segments = path.getPathSegments();
1✔
227

228
        final long groupId = System.nanoTime();
1✔
229

230
        String pathStr = "";
1✔
231
        final LockedPath[] locked = new LockedPath[segments.length];
1✔
232
        for (int i = 0; i < segments.length; i++) {
1✔
233
            pathStr += '/' + segments[i].toString();
1✔
234

235
            final Lock.LockMode lockMode;
236
            if (i + 1 == segments.length) {
1✔
237
                lockMode = Lock.LockMode.READ_LOCK; //leaf
1✔
238
            } else {
1✔
239
                lockMode = Lock.LockMode.INTENTION_READ; //ancestor
1✔
240
            }
241

242
            final MultiLock lock = getPathLock(pathStr);
1✔
243

244
            lockTable.attempt(groupId, pathStr, lockType, lockMode);
1✔
245
            if (lock(lock, lockMode)) {
1!
246
                locked[i] = new LockedPath(lock, lockMode, pathStr);
1✔
247
                lockTable.acquired(groupId, pathStr, lockType, lockMode);
1✔
248
            } else {
1✔
249
                lockTable.attemptFailed(groupId, pathStr, lockType, lockMode);
×
250

251
                unlockAll(locked, l -> lockTable.released(groupId, l.path, lockType, l.mode));
×
252

253
                throw new LockException("Unable to acquire " + lockType + " " + lockMode + " for: " + pathStr);
×
254
            }
255
        }
256

257
        return new LockGroup(groupId, locked);
1✔
258
    }
259

260
    /**
261
     * Locks a lock object.
262
     *
263
     * @param lock the lock object to lock.
264
     * @param lockMode the mode of the {@code lock} to acquire.
265
     *
266
     * @return true, if we were able to lock with the mode.
267
     */
268
    private static boolean lock(final MultiLock lock, final Lock.LockMode lockMode) {
269
        switch (lockMode) {
1!
270
            case INTENTION_READ:
271
                lock.intentionReadLock();
1✔
272
                break;
1✔
273

274
            case INTENTION_WRITE:
275
                lock.intentionWriteLock();
1✔
276
                break;
1✔
277

278
            case READ_LOCK:
279
                lock.readLock();
1✔
280
                break;
1✔
281

282
            case WRITE_LOCK:
283
                lock.writeLock();
1✔
284
                break;
1✔
285

286
            default:
287
                throw new UnsupportedOperationException(); // TODO(AR) implement the other modes
×
288
        }
289

290
        return true;  //TODO(AR) switch to lock interruptibly above!
1✔
291
    }
292

293
    /**
294
     * Releases an array of locked locks for the modes with which they were locked
295
     *
296
     * Locks are released in the opposite to their acquisition order
297
     *
298
     * @param locked An array of locks in acquisition order
299
     */
300
    static void unlockAll(final LockedPath[] locked, final Consumer<LockedPath> unlockListener) {
301
        for (int i = locked.length - 1; i >= 0; i--) {
1✔
302
            final LockedPath lock = locked[i];
1✔
303
            unlock(lock.lock, lock.mode);
1✔
304
            unlockListener.accept(lock);
1✔
305
        }
306
    }
1✔
307

308
    /**
309
     * Unlocks a lock object.
310
     *
311
     * @param lock The lock object to unlock.
312
     * @param lockMode The mode of the {@code lock} to release.
313
     */
314
    static void unlock(final MultiLock lock, final Lock.LockMode lockMode) {
315
        switch (lockMode) {
1!
316
            case INTENTION_READ:
317
                lock.unlockIntentionRead();
1✔
318
                break;
1✔
319

320
            case INTENTION_WRITE:
321
                lock.unlockIntentionWrite();
1✔
322
                break;
1✔
323

324
            case READ_LOCK:
325
                lock.unlockRead();
1✔
326
                break;
1✔
327

328
            case WRITE_LOCK:
329
                lock.unlockWrite();
1✔
330
                break;
1✔
331

332
            default:
333
                throw new UnsupportedOperationException(); // TODO(AR) implement the other modes
×
334
        }
335
    }
1✔
336

337
    /**
338
     * Acquires a WRITE_LOCK on a Collection (and implicitly all descendant Collections).
339
     *
340
     * @param collectionPath The path of the Collection for which a lock is requested.
341
     *
342
     * @return A WRITE_LOCK on the Collection.
343
     * @throws LockException if a lock error occurs
344
     */
345
    public ManagedCollectionLock acquireCollectionWriteLock(final XmldbURI collectionPath) throws LockException {
346
        return acquireCollectionWriteLock(collectionPath, false);
1✔
347
    }
348

349
    /**
350
     * Acquires a WRITE_LOCK on a Collection (and implicitly all descendant Collections).
351
     *
352
     * @param collectionPath The path of the Collection for which a lock is requested.
353
     * @param lockParent true if we should also explicitly write lock the parent Collection.
354
     *
355
     * @return A WRITE_LOCK on the Collection.
356
     */
357
    ManagedCollectionLock acquireCollectionWriteLock(final XmldbURI collectionPath, final boolean lockParent) throws LockException {
358
        final LockGroup lockGroup = acquirePathWriteLock(LockType.COLLECTION, collectionPath, lockParent);
1✔
359
        return new ManagedCollectionLock(
1✔
360
                collectionPath,
1✔
361
                lockGroup,
1✔
362
                lockTable);
1✔
363
    }
364

365
    /**
366
     * Acquires a WRITE_LOCK on a path (and implicitly all descendant paths).
367
     *
368
     * @param path The path for which a lock is requested.
369
     * @param lockParent true if we should also explicitly write lock the parent path.
370
     *
371
     * @return A WRITE_LOCK on the path.
372
     */
373
    LockGroup acquirePathWriteLock(final LockType lockType, final XmldbURI path,
374
            final boolean lockParent) throws LockException {
375
        final XmldbURI[] segments = path.getPathSegments();
1✔
376

377
        final long groupId = System.nanoTime();
1✔
378

379
        String pathStr = "";
1✔
380
        final LockedPath[] locked = new LockedPath[segments.length];
1✔
381
        for (int i = 0; i < segments.length; i++) {
1✔
382
            pathStr += '/' + segments[i].toString();
1✔
383

384
            final Lock.LockMode lockMode;
385
            if (lockParent && i + 2 == segments.length) {
1✔
386
                lockMode = Lock.LockMode.WRITE_LOCK;    // parent
1✔
387
            } else if(i + 1 == segments.length) {
1✔
388
                lockMode = Lock.LockMode.WRITE_LOCK;    // leaf
1✔
389
            } else {
1✔
390
                // ancestor
391

392
                if (!pathsMultiWriter) {
1✔
393
                    // single-writer/multi-reader
394
                    lockMode = Lock.LockMode.WRITE_LOCK;
1✔
395
                } else {
1✔
396
                    // multi-writer/multi-reader
397
                    lockMode = Lock.LockMode.INTENTION_WRITE;
1✔
398
                }
399
            }
400

401
            final MultiLock lock = getPathLock(pathStr);
1✔
402

403
            if (upgradeCheck && lockMode == Lock.LockMode.WRITE_LOCK && (lock.getIntentionReadHoldCount() > 0  || lock.getReadHoldCount() > 0)) {
1!
404
                throw new LockException("Lock upgrading would lead to a self-deadlock: " + pathStr);
×
405
            }
406

407
            if (warnWaitOnReadForWrite && lockMode == Lock.LockMode.WRITE_LOCK) {
1!
408
                if (lock.getIntentionReadLockCount() > 0) {
×
409
                    LOG.warn("About to acquire WRITE_LOCK for: {}, but INTENTION_READ_LOCK held by other thread(s): ", pathStr);
×
410
                } else if(lock.getReadLockCount() > 0) {
×
411
                    LOG.warn("About to acquire WRITE_LOCK for: {}, but READ_LOCK held by other thread(s): ", pathStr);
×
412
                }
413
            }
414

415
            lockTable.attempt(groupId, pathStr, lockType, lockMode);
1✔
416
            if (lock(lock, lockMode)) {
1!
417
                locked[i] = new LockedPath(lock, lockMode, pathStr);
1✔
418
                lockTable.acquired(groupId, pathStr, lockType, lockMode);
1✔
419
            } else {
1✔
420
                lockTable.attemptFailed(groupId, pathStr, lockType, lockMode);
×
421

422
                unlockAll(locked, l -> lockTable.released(groupId, l.path, lockType, l.mode));
×
423

424
                throw new LockException("Unable to acquire " + lockType + " " + lockMode + " for: " + pathStr);
×
425
            }
426
        }
427

428
        return new LockGroup(groupId, locked);
1✔
429
    }
430

431
    /**
432
     * Returns true if a WRITE_LOCK is held for a Collection
433
     *
434
     * @param collectionPath The URI of the Collection within the database
435
     *
436
     * @return true if a WRITE_LOCK is held
437
     */
438
    public boolean isCollectionLockedForWrite(final XmldbURI collectionPath) {
439
        final MultiLock existingLock = getPathLock(collectionPath.toString());
×
440
        return existingLock.getWriteLockCount() > 0;
×
441
    }
442

443
    /**
444
     * Returns true if a READ_LOCK is held for a Collection
445
     *
446
     * @param collectionPath The URI of the Collection within the database
447
     *
448
     * @return true if a READ_LOCK is held
449
     */
450
    public boolean isCollectionLockedForRead(final XmldbURI collectionPath) {
451
        final MultiLock existingLock = getPathLock(collectionPath.toString());
×
452
        return existingLock.getReadLockCount() > 0;
×
453
    }
454

455
    /**
456
     * Retrieves a lock for a Document
457
     *
458
     * This function is concerned with just the lock object
459
     * and has no knowledge of the state of the lock. The only
460
     * guarantee is that if this lock has not been requested before
461
     * then it will be provided in the unlocked state
462
     *
463
     * @param documentPath The path of the Document for which a lock is requested
464
     *
465
     * @return A lock for the Document
466
     */
467
    MultiLock getDocumentLock(final String documentPath) {
468
        return documentLocks.get(documentPath);
1✔
469
    }
470

471
    /**
472
     * Acquire a READ_LOCK on a Document
473
     *
474
     * @param documentPath The URI of the Document within the database
475
     *
476
     * @return the lock for the Document
477
     *
478
     * @throws LockException if the lock could not be acquired
479
     */
480
    public ManagedDocumentLock acquireDocumentReadLock(final XmldbURI documentPath) throws LockException {
481
        if (usePathLocksForDocuments) {
1!
482
            final LockGroup lockGroup = acquirePathReadLock(LockType.DOCUMENT, documentPath);
×
483
            return new ManagedLockGroupDocumentLock(documentPath, lockGroup, lockTable);
×
484
        } else {
485
            final long groupId = System.nanoTime();
1✔
486
            final String path = documentPath.toString();
1✔
487

488
            final MultiLock lock = getDocumentLock(path);
1✔
489
            lockTable.attempt(groupId, path, LockType.DOCUMENT, Lock.LockMode.READ_LOCK);
1✔
490

491
            if (lock(lock, Lock.LockMode.READ_LOCK)) {
1!
492

493
                lockTable.acquired(groupId, path, LockType.DOCUMENT, Lock.LockMode.READ_LOCK);
1✔
494
            } else {
1✔
495
                lockTable.attemptFailed(groupId, path, LockType.DOCUMENT, Lock.LockMode.READ_LOCK);
×
496
                throw new LockException("Unable to acquire READ_LOCK for: " + path);
×
497
            }
498

499
            return new ManagedSingleLockDocumentLock(documentPath, groupId, lock, Lock.LockMode.READ_LOCK, lockTable);
1✔
500
        }
501
    }
502

503
    /**
504
     * Acquire a WRITE_LOCK on a Document
505
     *
506
     * @param documentPath The URI of the Document within the database
507
     *
508
     * @return the lock for the Document
509
     *
510
     * @throws LockException if the lock could not be acquired
511
     */
512
    public ManagedDocumentLock acquireDocumentWriteLock(final XmldbURI documentPath) throws LockException {
513
        if (usePathLocksForDocuments) {
1!
514
            final LockGroup lockGroup = acquirePathWriteLock(LockType.DOCUMENT, documentPath, false);
×
515
            return new ManagedLockGroupDocumentLock(documentPath, lockGroup, lockTable);
×
516
        } else {
517
            final long groupId = System.nanoTime();
1✔
518
            final String path = documentPath.toString();
1✔
519

520
            final MultiLock lock = getDocumentLock(path);
1✔
521
            lockTable.attempt(groupId, path, LockType.DOCUMENT, Lock.LockMode.WRITE_LOCK);
1✔
522

523
            if (lock(lock, Lock.LockMode.WRITE_LOCK)) {
1!
524
                lockTable.acquired(groupId, path, LockType.DOCUMENT, Lock.LockMode.WRITE_LOCK);
1✔
525
            } else {
1✔
526
                lockTable.attemptFailed(groupId, path, LockType.DOCUMENT, Lock.LockMode.WRITE_LOCK);
×
527
                throw new LockException("Unable to acquire WRITE_LOCK for: " + path);
×
528
            }
529

530
            return new ManagedSingleLockDocumentLock(documentPath, groupId, lock, Lock.LockMode.WRITE_LOCK, lockTable);
1✔
531
        }
532
    }
533

534
    /**
535
     * Returns true if a WRITE_LOCK is held for a Document
536
     *
537
     * @param documentPath The URI of the Document within the database
538
     *
539
     * @return true if a WRITE_LOCK is held
540
     */
541
    public boolean isDocumentLockedForWrite(final XmldbURI documentPath) {
542
        final MultiLock existingLock;
543
        if (!usePathLocksForDocuments) {
1!
544
            existingLock = getDocumentLock(documentPath.toString());
1✔
545
        } else {
1✔
546
            existingLock = getPathLock(documentPath.toString());
×
547
        }
548
        return existingLock.getWriteLockCount() > 0;
1✔
549
    }
550

551
    /**
552
     * Returns true if a READ_LOCK is held for a Document
553
     *
554
     * @param documentPath The URI of the Document within the database
555
     *
556
     * @return true if a READ_LOCK is held
557
     */
558
    public boolean isDocumentLockedForRead(final XmldbURI documentPath) {
559
        final MultiLock existingLock;
560
        if (!usePathLocksForDocuments) {
1!
561
            existingLock = getDocumentLock(documentPath.toString());
1✔
562
        } else {
1✔
563
            existingLock = getPathLock(documentPath.toString());
×
564
        }
565
        return existingLock.getReadLockCount() > 0;
1!
566
    }
567

568
    /**
569
     * Returns the LockMode that should be used for accessing
570
     * a Collection when that access is just for the purposes
571
     * or accessing a document(s) within the Collection.
572
     *
573
     * When Path Locks are enabled for Documents, both Collections and
574
     * Documents share the same lock domain, a path based tree hierarchy.
575
     * With a shared lock-hierarchy, if we were to READ_LOCK a Collection
576
     * and then request a WRITE_LOCK for a Document in that Collection we
577
     * would reach a dead-lock situation. To avoid this, if this method is
578
     * called like
579
     * {@code relativeCollectionLockMode(LockMode.READ_LOCK, LockMode.WRITE_LOCK)}
580
     * it will return a WRITE_LOCK. That is to say that to aid dealock-avoidance,
581
     * this function may return a stricter locking mode than the {@code desiredCollectionLockMode}.
582
     *
583
     * When Path Locks are disabled (the default) for Documents, Collection and Documents
584
     * have independent locking domains. In this case this function will always return
585
     * the {@code desiredCollectionLockMode}.
586
     *
587
     * @param desiredCollectionLockMode The desired lock mode for the Collection.
588
     * @param documentLockMode The lock mode that will be used for subsequent document operations in the Collection.
589
     *
590
     * @return The lock mode that should be used for accessing the Collection.
591
     */
592
    public Lock.LockMode relativeCollectionLockMode(final Lock.LockMode desiredCollectionLockMode,
593
            final Lock.LockMode documentLockMode) {
594
        if (!usePathLocksForDocuments) {
1!
595
            return desiredCollectionLockMode;
1✔
596

597
        } else {
598
            return switch (documentLockMode) {
×
599
                case NO_LOCK, INTENTION_READ, READ_LOCK -> Lock.LockMode.READ_LOCK;
×
600
                default -> Lock.LockMode.WRITE_LOCK;
×
601
            };
602
        }
603
    }
604

605
    /**
606
     * Retrieves a lock for a {@link org.exist.storage.dom.DOMFile}
607
     *
608
     * This function is concerned with just the lock object
609
     * and has no knowledge of the state of the lock. The only
610
     * guarantee is that if this lock has not been requested before
611
     * then it will be provided in the unlocked state
612
     *
613
     * @param domFileName The path of the Document for which a lock is requested
614
     *
615
     * @return A lock for the DOMFile
616
     */
617
    ReentrantLock getBTreeLock(final String domFileName) {
618
        return btreeLocks.get(domFileName);
1✔
619
    }
620

621
    /**
622
     * Acquire a WRITE_LOCK on a {@link org.exist.storage.btree.BTree}
623
     *
624
     * @param btreeFileName the filename of the BTree
625
     *
626
     * @return the lock for the BTree
627
     *
628
     * @throws LockException if the lock could not be acquired
629
     */
630
    public ManagedLock<ReentrantLock> acquireBtreeReadLock(final String btreeFileName) throws LockException {
631
        final long groupId = System.nanoTime();
1✔
632

633
        final ReentrantLock lock = getBTreeLock(btreeFileName);
1✔
634
        try {
635
            lockTable.attempt(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.READ_LOCK);
1✔
636

637
            lock.lockInterruptibly();
1✔
638

639
            lockTable.acquired(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.READ_LOCK);
1✔
640
        } catch(final InterruptedException e) {
1✔
641
            lockTable.attemptFailed(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.READ_LOCK);
×
642
            throw new LockException("Unable to acquire READ_LOCK for: " + btreeFileName, e);
×
643
        }
644

645
        return new ManagedLock(lock, () -> {
1✔
646
            lock.unlock();
1✔
647
            lockTable.released(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.READ_LOCK);
1✔
648
        });
1✔
649
    }
650

651
    /**
652
     * Acquire a WRITE_LOCK on a {@link org.exist.storage.btree.BTree}
653
     *
654
     * @param btreeFileName the filename of the BTree
655
     *
656
     * @return the lock for the BTree
657
     *
658
     * @throws LockException if the lock could not be acquired
659
     */
660
    public ManagedLock<ReentrantLock> acquireBtreeWriteLock(final String btreeFileName) throws LockException {
661
        final long groupId = System.nanoTime();
1✔
662

663
        final ReentrantLock lock = getBTreeLock(btreeFileName);
1✔
664
        try {
665
            lockTable.attempt(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.WRITE_LOCK);
1✔
666

667
            lock.lockInterruptibly();
1✔
668

669
            lockTable.acquired(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.WRITE_LOCK);
1✔
670
        } catch(final InterruptedException e) {
1✔
671
            lockTable.attemptFailed(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.WRITE_LOCK);
×
672
            throw new LockException("Unable to acquire WRITE_LOCK for: " + btreeFileName, e);
×
673
        }
674

675
        return new ManagedLock(lock, () -> {
1✔
676
            lock.unlock();
1✔
677
            lockTable.released(groupId, btreeFileName, LockType.BTREE, Lock.LockMode.WRITE_LOCK);
1✔
678
        });
1✔
679
    }
680

681
    /**
682
     * Returns true if the BTree for the file name is locked.
683
     *
684
     * @param btreeFileName The name of the .dbx file.
685
     *
686
     * @return true if the Btree is locked.
687
     *
688
     * @deprecated Just a place holder until we can make the BTree reader/writer safe
689
     */
690
    @Deprecated
691
    public boolean isBtreeLocked(final String btreeFileName) {
692
        final ReentrantLock lock = getBTreeLock(btreeFileName);
×
693
        return lock.isLocked();
×
694
    }
695

696
    /**
697
     * Returns true if the BTree for the file name is locked for writes.
698
     *
699
     * @param btreeFileName The name of the .dbx file.
700
     *
701
     * @return true if the Btree is locked for writes.
702
     */
703
    public boolean isBtreeLockedForWrite(final String btreeFileName) {
704
        return isBtreeLocked(btreeFileName);
×
705
    }
706

707
    /**
708
     * Gets a configuration option from a (legacy) System Property
709
     * or if that is not set, then from a Configuration file
710
     * property.
711
     *
712
     * @param legacyPropertyName name of the legacy system property
713
     * @param configuration configuration
714
     * @param configProperty name of a configuration property
715
     * @param defaultValue the default value if no system property of config property is found.
716
     *
717
     * @return the value of the property
718
     */
719
    static boolean getLegacySystemPropertyOrConfigPropertyBool(final String legacyPropertyName,
720
            final Configuration configuration, final String configProperty, final boolean defaultValue) {
721
        final String legacyPropertyValue = System.getProperty(legacyPropertyName);
1✔
722
        if (legacyPropertyValue != null && !legacyPropertyValue.isEmpty()) {
1!
723
            return Boolean.getBoolean(legacyPropertyName);
1✔
724
        } else {
725
            return getConfigPropertyBool(configuration, configProperty, defaultValue);
1✔
726
        }
727
    }
728

729
    /**
730
     * Gets a configuration option from the Configuration file property.
731
     *
732
     * @param configuration configuration
733
     * @param configProperty name of a configuration property
734
     * @param defaultValue the default value if no system property of config property is found.
735
     *
736
     * @return the value of the property
737
     */
738
    static boolean getConfigPropertyBool(final Configuration configuration, final String configProperty,
739
            final boolean defaultValue) {
740
        if (configuration != null) {
1✔
741
            return configuration.getProperty(configProperty, defaultValue);
1✔
742
        } else {
743
            return defaultValue;
1✔
744
        }
745
    }
746

747
    /**
748
     * Gets a configuration option from a (legacy) System Property
749
     * or if that is not set, then from a Configuration file
750
     * property.
751
     *
752
     * @param legacyPropertyName name of the legacy system property
753
     * @param configuration configuration
754
     * @param configProperty name of a configuration property
755
     * @param defaultValue the default value if no system property of config property is found.
756
     *
757
     * @return the value of the property
758
     */
759
    static int getLegacySystemPropertyOrConfigPropertyInt(final String legacyPropertyName,
760
            final Configuration configuration, final String configProperty, final int defaultValue) {
761
        final String legacyPropertyValue = System.getProperty(legacyPropertyName);
1✔
762
        if (legacyPropertyValue != null && !legacyPropertyValue.isEmpty()) {
1!
763
            return Integer.getInteger(legacyPropertyName);
×
764
        } else if (configuration != null) {
1✔
765
            return configuration.getProperty(configProperty, defaultValue);
1✔
766
        } else {
767
            return defaultValue;
1✔
768
        }
769
    }
770
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc