Skip to content

Commit 09f8c31

Browse files
CalvinKirsliulijia
authored and
liulijia
committed
[feat](lock)add deadlock detection tool and monitored lock implementations apache#39015 (apache#39099)
This issue proposes the addition of new features to the project, including a deadlock detection tool and monitored lock implementations. These features will help in identifying and debugging potential deadlocks and monitoring lock usage. Features: A monitored version of Lock that tracks and logs lock acquisition and release times. Functionality: Overrides lock(), unlock(), tryLock(), and tryLock(long timeout, TimeUnit unit) methods. Logs information about lock acquisition time, release time, and any failure to acquire the lock within the specified timeout. ##### eg ```log 2024-08-07 12:02:59 [ Thread-2:2006 ] - [ WARN ] Thread ID: 12, Thread Name: Thread-2 - Lock held for 1912 ms, exceeding hold timeout of 1000 ms Thread stack trace: at java.lang.Thread.getStackTrace(Thread.java:1564) at org.example.lock.AbstractMonitoredLock.afterUnlock(AbstractMonitoredLock.java:49) at org.example.lock.MonitoredReentrantLock.unlock(MonitoredReentrantLock.java:32) at org.example.ExampleService.timeout(ExampleService.java:17) at org.example.Main.lambda$test2$1(Main.java:39) at java.lang.Thread.run(Thread.java:750) ``` Uses ScheduledExecutorService for periodic deadlock checks. Logs deadlock information including thread names, states, lock info, and stack traces. **ThreadMXBean accesses thread information in the local JVM, which is already in memory, so accessing it is less expensive than fetching data from external resources such as disk or network. Thread state cache: The JVM typically maintains a cache of thread states, reducing the need for real-time calculations or additional data processing.** ##### eg ```log Thread Name: Thread-0 Thread State: WAITING Lock Name: java.util.concurrent.locks.ReentrantLock$NonfairSync@1d653213 Lock Owner Name: Thread-1 Lock Owner Id: 12 Waited Time: -1 Blocked Time: -1 Lock Info: java.util.concurrent.locks.ReentrantLock$NonfairSync@1d653213 Blocked by: java.util.concurrent.locks.ReentrantLock$NonfairSync@1d653213 Stack Trace: at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285) at org.example.lock.MonitoredReentrantLock.lock(MonitoredReentrantLock.java:22) at org.example.Main.lambda$testDeadLock$3(Main.java:79) at org.example.Main$$Lambda$1/1221555852.run(Unknown Source) at java.lang.Thread.run(Thread.java:750) 2024-08-07 14:11:28 [ pool-1-thread-1:2001 ] - [ WARN ] Deadlocks detected: Thread Name: Thread-1 Thread State: WAITING Lock Name: java.util.concurrent.locks.ReentrantLock$NonfairSync@13a2dfcf Lock Owner Name: Thread-0 Lock Owner Id: 11 Waited Time: -1 Blocked Time: -1 Lock Info: java.util.concurrent.locks.ReentrantLock$NonfairSync@13a2dfcf Blocked by: java.util.concurrent.locks.ReentrantLock$NonfairSync@13a2dfcf Stack Trace: at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285) at org.example.lock.MonitoredReentrantLock.lock(MonitoredReentrantLock.java:22) at org.example.Main.lambda$testDeadLock$4(Main.java:93) at org.example.Main$$Lambda$2/1556956098.run(Unknown Source) at java.lang.Thread.run(Thread.java:750) ``` ``` @WarmUp(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) @threads(1) Benchmark Mode Cnt Score Error Units LockBenchmark.testMonitoredLock thrpt 2 15889.407 ops/ms LockBenchmark.testMonitoredLock:·gc.alloc.rate thrpt 2 678.061 MB/sec LockBenchmark.testMonitoredLock:·gc.alloc.rate.norm thrpt 2 56.000 B/op LockBenchmark.testMonitoredLock:·gc.churn.PS_Eden_Space thrpt 2 668.249 MB/sec LockBenchmark.testMonitoredLock:·gc.churn.PS_Eden_Space.norm thrpt 2 55.080 B/op LockBenchmark.testMonitoredLock:·gc.churn.PS_Survivor_Space thrpt 2 0.075 MB/sec LockBenchmark.testMonitoredLock:·gc.churn.PS_Survivor_Space.norm thrpt 2 0.006 B/op LockBenchmark.testMonitoredLock:·gc.count thrpt 2 20.000 counts LockBenchmark.testMonitoredLock:·gc.time thrpt 2 6.000 ms LockBenchmark.testNativeLock thrpt 2 103130.635 ops/ms LockBenchmark.testNativeLock:·gc.alloc.rate thrpt 2 ≈ 10⁻⁴ MB/sec LockBenchmark.testNativeLock:·gc.alloc.rate.norm thrpt 2 ≈ 10⁻⁶ B/op LockBenchmark.testNativeLock:·gc.count thrpt 2 ≈ 0 counts @WarmUp(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) @threads(100) Benchmark Mode Cnt Score Error Units LockBenchmark.testMonitoredLock thrpt 2 10994.606 ops/ms LockBenchmark.testMonitoredLock:·gc.alloc.rate thrpt 2 488.508 MB/sec LockBenchmark.testMonitoredLock:·gc.alloc.rate.norm thrpt 2 56.002 B/op LockBenchmark.testMonitoredLock:·gc.churn.PS_Eden_Space thrpt 2 481.390 MB/sec LockBenchmark.testMonitoredLock:·gc.churn.PS_Eden_Space.norm thrpt 2 55.163 B/op LockBenchmark.testMonitoredLock:·gc.churn.PS_Survivor_Space thrpt 2 0.020 MB/sec LockBenchmark.testMonitoredLock:·gc.churn.PS_Survivor_Space.norm thrpt 2 0.002 B/op LockBenchmark.testMonitoredLock:·gc.count thrpt 2 18.000 counts LockBenchmark.testMonitoredLock:·gc.time thrpt 2 9.000 ms LockBenchmark.testNativeLock thrpt 2 558652.036 ops/ms LockBenchmark.testNativeLock:·gc.alloc.rate thrpt 2 0.016 MB/sec LockBenchmark.testNativeLock:·gc.alloc.rate.norm thrpt 2 ≈ 10⁻⁴ B/op LockBenchmark.testNativeLock:·gc.count thrpt 2 ≈ 0 counts ``` Issue Number: close #xxx <!--Describe your changes.-->
1 parent 4818ba0 commit 09f8c31

22 files changed

+669
-112
lines changed

fe/fe-common/src/main/java/org/apache/doris/common/Config.java

+17
Original file line numberDiff line numberDiff line change
@@ -2453,4 +2453,21 @@ public class Config extends ConfigBase {
24532453

24542454
@ConfField(mutable = true)
24552455
public static boolean enable_cooldown_replica_affinity = true;
2456+
2457+
//==========================================================================
2458+
// end of cloud config
2459+
//==========================================================================
2460+
//==========================================================================
2461+
// start of lock config
2462+
@ConfField(description = {"是否开启死锁检测",
2463+
"Whether to enable deadlock detection"})
2464+
public static boolean enable_deadlock_detection = false;
2465+
2466+
@ConfField(description = {"死锁检测间隔时间,单位分钟",
2467+
"Deadlock detection interval time, unit minute"})
2468+
public static long deadlock_detection_interval_minute = 5;
2469+
2470+
@ConfField(mutable = true, description = {"表示最大锁持有时间,超过该时间会打印告警日志,单位秒",
2471+
"Maximum lock hold time; logs a warning if exceeded"})
2472+
public static long max_lock_hold_threshold_seconds = 10;
24562473
}

fe/fe-core/src/main/java/org/apache/doris/DorisFE.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.doris.common.ThreadPoolManager;
2727
import org.apache.doris.common.Version;
2828
import org.apache.doris.common.telemetry.Telemetry;
29+
import org.apache.doris.common.lock.DeadlockMonitor;
2930
import org.apache.doris.common.util.JdkUtils;
3031
import org.apache.doris.common.util.NetUtils;
3132
import org.apache.doris.httpv2.HttpServer;
@@ -59,6 +60,7 @@
5960
import java.nio.channels.FileLock;
6061
import java.nio.channels.OverlappingFileLockException;
6162
import java.nio.file.StandardOpenOption;
63+
import java.util.concurrent.TimeUnit;
6264

6365
public class DorisFE {
6466
private static final Logger LOG = LogManager.getLogger(DorisFE.class);
@@ -84,6 +86,13 @@ public static void main(String[] args) {
8486
start(DORIS_HOME_DIR, PID_DIR, args, options);
8587
}
8688

89+
private static void startMonitor() {
90+
if (Config.enable_deadlock_detection) {
91+
DeadlockMonitor deadlockMonitor = new DeadlockMonitor();
92+
deadlockMonitor.startMonitoring(Config.deadlock_detection_interval_minute, TimeUnit.MINUTES);
93+
}
94+
}
95+
8796
// entrance for doris frontend
8897
public static void start(String dorisHomeDir, String pidDir, String[] args, StartupOptions options) {
8998
if (System.getenv("DORIS_LOG_TO_STDERR") != null) {
@@ -202,7 +211,7 @@ public static void start(String dorisHomeDir, String pidDir, String[] args, Star
202211
}
203212

204213
ThreadPoolManager.registerAllThreadPoolMetric();
205-
214+
startMonitor();
206215
while (true) {
207216
Thread.sleep(2000);
208217
}

fe/fe-core/src/main/java/org/apache/doris/catalog/ColocateTableIndex.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.apache.doris.common.MetaNotFoundException;
2222
import org.apache.doris.common.io.Text;
2323
import org.apache.doris.common.io.Writable;
24+
import org.apache.doris.common.lock.MonitoredReentrantReadWriteLock;
2425
import org.apache.doris.persist.ColocatePersistInfo;
2526
import org.apache.doris.persist.gson.GsonPostProcessable;
2627
import org.apache.doris.persist.gson.GsonUtils;
@@ -50,7 +51,6 @@
5051
import java.util.Map;
5152
import java.util.Optional;
5253
import java.util.Set;
53-
import java.util.concurrent.locks.ReentrantReadWriteLock;
5454
import java.util.stream.Collectors;
5555

5656
/**
@@ -176,7 +176,7 @@ public static boolean isGlobalGroupName(String groupName) {
176176
// save some error msg of the group for show. no need to persist
177177
private Map<GroupId, String> group2ErrMsgs = Maps.newHashMap();
178178

179-
private transient ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
179+
private transient MonitoredReentrantReadWriteLock lock = new MonitoredReentrantReadWriteLock();
180180

181181
public ColocateTableIndex() {
182182

fe/fe-core/src/main/java/org/apache/doris/catalog/Database.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.doris.common.UserException;
2828
import org.apache.doris.common.io.Text;
2929
import org.apache.doris.common.io.Writable;
30+
import org.apache.doris.common.lock.MonitoredReentrantReadWriteLock;
3031
import org.apache.doris.common.util.DebugUtil;
3132
import org.apache.doris.common.util.PropertyAnalyzer;
3233
import org.apache.doris.common.util.QueryableReentrantReadWriteLock;
@@ -85,7 +86,8 @@ public class Database extends MetaObject implements Writable, DatabaseIf<Table>
8586
private volatile String fullQualifiedName;
8687
@SerializedName(value = "clusterName")
8788
private String clusterName;
88-
private QueryableReentrantReadWriteLock rwLock;
89+
90+
private MonitoredReentrantReadWriteLock rwLock;
8991

9092
// table family group map
9193
private Map<Long, Table> idToTable;
@@ -135,7 +137,11 @@ public Database(long id, String name) {
135137
if (this.fullQualifiedName == null) {
136138
this.fullQualifiedName = "";
137139
}
140+
<<<<<<< HEAD
138141
this.rwLock = new QueryableReentrantReadWriteLock(true);
142+
=======
143+
this.rwLock = new MonitoredReentrantReadWriteLock(true);
144+
>>>>>>> 30e2c3fb11 ([feat](lock)add deadlock detection tool and monitored lock implementations #39015 (#39099))
139145
this.idToTable = Maps.newConcurrentMap();
140146
this.nameToTable = Maps.newConcurrentMap();
141147
this.lowerCaseToTableName = Maps.newConcurrentMap();

fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
import org.apache.doris.common.UserException;
111111
import org.apache.doris.common.io.CountingDataOutputStream;
112112
import org.apache.doris.common.io.Text;
113+
import org.apache.doris.common.lock.MonitoredReentrantLock;
113114
import org.apache.doris.common.util.Daemon;
114115
import org.apache.doris.common.util.DynamicPartitionUtil;
115116
import org.apache.doris.common.util.HttpURLUtil;
@@ -118,7 +119,6 @@
118119
import org.apache.doris.common.util.NetUtils;
119120
import org.apache.doris.common.util.PrintableMap;
120121
import org.apache.doris.common.util.PropertyAnalyzer;
121-
import org.apache.doris.common.util.QueryableReentrantLock;
122122
import org.apache.doris.common.util.SmallFileMgr;
123123
import org.apache.doris.common.util.TimeUtils;
124124
import org.apache.doris.common.util.Util;
@@ -313,7 +313,7 @@ public class Env {
313313
// We use fair ReentrantLock to avoid starvation. Do not use this lock in critical code pass
314314
// because fair lock has poor performance.
315315
// Using QueryableReentrantLock to print owner thread in debug mode.
316-
private QueryableReentrantLock lock;
316+
private MonitoredReentrantLock lock;
317317

318318
private CatalogMgr catalogMgr;
319319
private GlobalFunctionMgr globalFunctionMgr;
@@ -586,7 +586,7 @@ private Env(boolean isCheckpointCatalog) {
586586
this.syncJobManager = new SyncJobManager();
587587
this.alter = new Alter();
588588
this.consistencyChecker = new ConsistencyChecker();
589-
this.lock = new QueryableReentrantLock(true);
589+
this.lock = new MonitoredReentrantLock(true);
590590
this.backupHandler = new BackupHandler(this);
591591
this.metaDir = Config.meta_dir;
592592
this.publishVersionDaemon = new PublishVersionDaemon();

fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import org.apache.doris.common.MetaNotFoundException;
2424
import org.apache.doris.common.io.Text;
2525
import org.apache.doris.common.io.Writable;
26-
import org.apache.doris.common.util.QueryableReentrantReadWriteLock;
26+
import org.apache.doris.common.lock.MonitoredReentrantReadWriteLock;
2727
import org.apache.doris.common.util.SqlUtils;
2828
import org.apache.doris.common.util.Util;
2929
import org.apache.doris.external.hudi.HudiTable;
@@ -72,7 +72,7 @@ public abstract class Table extends MetaObject implements Writable, TableIf {
7272
protected volatile String qualifiedDbName;
7373
protected TableType type;
7474
protected long createTime;
75-
protected QueryableReentrantReadWriteLock rwLock;
75+
protected MonitoredReentrantReadWriteLock rwLock;
7676

7777
/*
7878
* fullSchema and nameToColumn should contains all columns, both visible and shadow.
@@ -112,7 +112,7 @@ public Table(TableType type) {
112112
this.type = type;
113113
this.fullSchema = Lists.newArrayList();
114114
this.nameToColumn = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
115-
this.rwLock = new QueryableReentrantReadWriteLock(true);
115+
this.rwLock = new MonitoredReentrantReadWriteLock(true);
116116
}
117117

118118
public Table(long id, String tableName, TableType type, List<Column> fullSchema) {
@@ -132,7 +132,7 @@ public Table(long id, String tableName, TableType type, List<Column> fullSchema)
132132
// Only view in with-clause have null base
133133
Preconditions.checkArgument(type == TableType.VIEW, "Table has no columns");
134134
}
135-
this.rwLock = new QueryableReentrantReadWriteLock(true);
135+
this.rwLock = new MonitoredReentrantReadWriteLock(true);
136136
this.createTime = Instant.now().getEpochSecond();
137137
}
138138

fe/fe-core/src/main/java/org/apache/doris/catalog/Tablet.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.doris.common.Pair;
2727
import org.apache.doris.common.io.Text;
2828
import org.apache.doris.common.io.Writable;
29+
import org.apache.doris.common.lock.MonitoredReentrantReadWriteLock;
2930
import org.apache.doris.persist.gson.GsonUtils;
3031
import org.apache.doris.resource.Tag;
3132
import org.apache.doris.system.Backend;
@@ -50,7 +51,6 @@
5051
import java.util.List;
5152
import java.util.Map;
5253
import java.util.Set;
53-
import java.util.concurrent.locks.ReentrantReadWriteLock;
5454
import java.util.stream.Collectors;
5555
import java.util.stream.LongStream;
5656

@@ -97,7 +97,7 @@ public enum TabletStatus {
9797
private long cooldownReplicaId = -1;
9898
@SerializedName(value = "cooldownTerm")
9999
private long cooldownTerm = -1;
100-
private ReentrantReadWriteLock cooldownConfLock = new ReentrantReadWriteLock();
100+
private MonitoredReentrantReadWriteLock cooldownConfLock = new MonitoredReentrantReadWriteLock();
101101

102102
// last time that the tablet checker checks this tablet.
103103
// no need to persist

fe/fe-core/src/main/java/org/apache/doris/catalog/external/ExternalDatabase.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.doris.common.MetaNotFoundException;
2626
import org.apache.doris.common.io.Text;
2727
import org.apache.doris.common.io.Writable;
28+
import org.apache.doris.common.lock.MonitoredReentrantReadWriteLock;
2829
import org.apache.doris.common.util.Util;
2930
import org.apache.doris.datasource.CatalogIf;
3031
import org.apache.doris.datasource.ExternalCatalog;
@@ -50,7 +51,6 @@
5051
import java.util.Map;
5152
import java.util.Set;
5253
import java.util.concurrent.TimeUnit;
53-
import java.util.concurrent.locks.ReentrantReadWriteLock;
5454

5555
/**
5656
* Base class of external database.
@@ -61,7 +61,7 @@ public abstract class ExternalDatabase<T extends ExternalTable>
6161
implements DatabaseIf<T>, Writable, GsonPostProcessable {
6262
private static final Logger LOG = LogManager.getLogger(ExternalDatabase.class);
6363

64-
protected ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
64+
protected MonitoredReentrantReadWriteLock rwLock = new MonitoredReentrantReadWriteLock(true);
6565

6666
@SerializedName(value = "id")
6767
protected long id;
@@ -344,7 +344,7 @@ public void gsonPostProcess() throws IOException {
344344
for (T tbl : idToTbl.values()) {
345345
tableNameToId.put(tbl.getName(), tbl.getId());
346346
}
347-
rwLock = new ReentrantReadWriteLock(true);
347+
rwLock = new MonitoredReentrantReadWriteLock(true);
348348
}
349349

350350
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.doris.common.lock;
19+
20+
import org.apache.doris.common.Config;
21+
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
25+
/**
26+
* Abstract base class for a monitored lock that tracks lock acquisition,
27+
* release, and attempt times. It provides mechanisms for monitoring the
28+
* duration for which a lock is held and logging any instances where locks
29+
* are held longer than a specified timeout or fail to be acquired within
30+
* a specified timeout.
31+
*/
32+
public abstract class AbstractMonitoredLock {
33+
34+
private static final Logger LOG = LoggerFactory.getLogger(AbstractMonitoredLock.class);
35+
36+
// Thread-local variable to store the lock start time
37+
private final ThreadLocal<Long> lockStartTime = new ThreadLocal<>();
38+
39+
40+
/**
41+
* Method to be called after successfully acquiring the lock.
42+
* Sets the start time for the lock.
43+
*/
44+
protected void afterLock() {
45+
lockStartTime.set(System.nanoTime());
46+
}
47+
48+
/**
49+
* Method to be called after releasing the lock.
50+
* Calculates the lock hold time and logs a warning if it exceeds the hold timeout.
51+
*/
52+
protected void afterUnlock() {
53+
Long startTime = lockStartTime.get();
54+
if (startTime != null) {
55+
long lockHoldTimeNanos = System.nanoTime() - startTime;
56+
long lockHoldTimeMs = lockHoldTimeNanos >> 20;
57+
if (lockHoldTimeMs > Config.max_lock_hold_threshold_seconds * 1000) {
58+
Thread currentThread = Thread.currentThread();
59+
String stackTrace = getThreadStackTrace(currentThread.getStackTrace());
60+
LOG.warn("Thread ID: {}, Thread Name: {} - Lock held for {} ms, exceeding hold timeout of {} ms "
61+
+ "Thread stack trace:{}",
62+
currentThread.getId(), currentThread.getName(), lockHoldTimeMs, lockHoldTimeMs, stackTrace);
63+
}
64+
lockStartTime.remove();
65+
}
66+
}
67+
68+
/**
69+
* Method to be called after attempting to acquire the lock using tryLock.
70+
* Logs a warning if the lock was not acquired within a reasonable time.
71+
*
72+
* @param acquired Whether the lock was successfully acquired
73+
* @param startTime The start time of the lock attempt
74+
*/
75+
protected void afterTryLock(boolean acquired, long startTime) {
76+
if (acquired) {
77+
afterLock();
78+
return;
79+
}
80+
if (LOG.isDebugEnabled()) {
81+
long elapsedTime = (System.nanoTime() - startTime) >> 20;
82+
Thread currentThread = Thread.currentThread();
83+
String stackTrace = getThreadStackTrace(currentThread.getStackTrace());
84+
LOG.debug("Thread ID: {}, Thread Name: {} - Failed to acquire the lock within {} ms"
85+
+ "\nThread blocking info:\n{}",
86+
currentThread.getId(), currentThread.getName(), elapsedTime, stackTrace);
87+
}
88+
}
89+
90+
/**
91+
* Utility method to format the stack trace of a thread.
92+
*
93+
* @param stackTrace The stack trace elements of the thread
94+
* @return A formatted string of the stack trace
95+
*/
96+
private String getThreadStackTrace(StackTraceElement[] stackTrace) {
97+
StringBuilder sb = new StringBuilder();
98+
for (StackTraceElement element : stackTrace) {
99+
sb.append("\tat ").append(element).append("\n");
100+
}
101+
return sb.toString().replace("\n", "\\n");
102+
}
103+
}
104+
105+

0 commit comments

Comments
 (0)