Skip to content

Commit 52b4106

Browse files
authored
GH-4784 switched approach for deadlock prevention (#5001)
* simplify deadlock detection * switch away from ReentrantLock * new lock manager with a reentrant lock that supports unlocking from seperate thread * improved tests
1 parent 8e9043b commit 52b4106

8 files changed

Lines changed: 649 additions & 176 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
******************************************************************************/
11+
package org.eclipse.rdf4j.common.concurrent.locks;
12+
13+
import java.util.concurrent.atomic.AtomicLong;
14+
import java.util.concurrent.atomic.AtomicReference;
15+
16+
import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockCleaner;
17+
import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockMonitoring;
18+
import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockTracking;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
/**
23+
* A simple reentrant lock that allows other threads to unlock the lock.
24+
*
25+
* @author Håvard M. Ottestad
26+
*/
27+
public class ExclusiveReentrantLockManager {
28+
29+
private final static Logger logger = LoggerFactory.getLogger(ExclusiveReentrantLockManager.class);
30+
31+
// the underlying lock object
32+
final AtomicLong activeLocks = new AtomicLong();
33+
final AtomicReference<Thread> owner = new AtomicReference<>();
34+
35+
private final int waitToCollect;
36+
37+
LockMonitoring<ExclusiveReentrantLock> lockMonitoring;
38+
39+
public ExclusiveReentrantLockManager() {
40+
this(false);
41+
}
42+
43+
public ExclusiveReentrantLockManager(boolean trackLocks) {
44+
this(trackLocks, LockMonitoring.INITIAL_WAIT_TO_COLLECT);
45+
}
46+
47+
public ExclusiveReentrantLockManager(boolean trackLocks, int collectionFrequency) {
48+
49+
this.waitToCollect = collectionFrequency;
50+
51+
if (trackLocks || Properties.lockTrackingEnabled()) {
52+
53+
lockMonitoring = new LockTracking(
54+
true,
55+
"ExclusiveReentrantLockManager",
56+
LoggerFactory.getLogger(this.getClass()),
57+
waitToCollect,
58+
Lock.ExtendedSupplier.wrap(this::getExclusiveLockInner, this::tryExclusiveLockInner)
59+
);
60+
61+
} else {
62+
lockMonitoring = new LockCleaner(
63+
false,
64+
"ExclusiveReentrantLockManager",
65+
LoggerFactory.getLogger(this.getClass()),
66+
Lock.ExtendedSupplier.wrap(this::getExclusiveLockInner, this::tryExclusiveLockInner)
67+
);
68+
}
69+
70+
}
71+
72+
private Lock tryExclusiveLockInner() {
73+
74+
synchronized (owner) {
75+
if (owner.get() == Thread.currentThread()) {
76+
activeLocks.incrementAndGet();
77+
return new ExclusiveReentrantLock(owner, activeLocks);
78+
}
79+
80+
if (owner.compareAndSet(null, Thread.currentThread())) {
81+
activeLocks.incrementAndGet();
82+
return new ExclusiveReentrantLock(owner, activeLocks);
83+
}
84+
}
85+
86+
return null;
87+
88+
}
89+
90+
private Lock getExclusiveLockInner() throws InterruptedException {
91+
92+
synchronized (owner) {
93+
94+
if (lockMonitoring.requiresManualCleanup()) {
95+
do {
96+
if (Thread.interrupted()) {
97+
throw new InterruptedException();
98+
}
99+
Lock lock = tryExclusiveLockInner();
100+
if (lock != null) {
101+
return lock;
102+
} else {
103+
lockMonitoring.runCleanup();
104+
owner.wait(waitToCollect);
105+
}
106+
} while (true);
107+
} else {
108+
while (true) {
109+
if (Thread.interrupted()) {
110+
throw new InterruptedException();
111+
}
112+
Lock lock = tryExclusiveLockInner();
113+
if (lock != null) {
114+
return lock;
115+
} else {
116+
owner.wait(waitToCollect);
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
public Lock tryExclusiveLock() {
124+
return lockMonitoring.tryLock();
125+
}
126+
127+
public Lock getExclusiveLock() throws InterruptedException {
128+
return lockMonitoring.getLock();
129+
}
130+
131+
public boolean isActiveLock() {
132+
return owner.get() != null;
133+
}
134+
135+
static class ExclusiveReentrantLock implements Lock {
136+
137+
final AtomicLong activeLocks;
138+
final AtomicReference<Thread> owner;
139+
private boolean released = false;
140+
141+
public ExclusiveReentrantLock(AtomicReference<Thread> owner, AtomicLong activeLocks) {
142+
this.owner = owner;
143+
this.activeLocks = activeLocks;
144+
}
145+
146+
@Override
147+
public boolean isActive() {
148+
return !released;
149+
}
150+
151+
@Override
152+
public void release() {
153+
if (released) {
154+
throw new IllegalStateException("Lock already released");
155+
}
156+
157+
synchronized (owner) {
158+
if (owner.get() != Thread.currentThread()) {
159+
logger.warn("Releasing lock from different thread, owner: " + owner.get() + ", current: "
160+
+ Thread.currentThread());
161+
}
162+
163+
if (activeLocks.decrementAndGet() == 0) {
164+
owner.set(null);
165+
owner.notifyAll();
166+
}
167+
}
168+
169+
released = true;
170+
171+
}
172+
}
173+
174+
}

0 commit comments

Comments
 (0)