In recent days, a forkjoin thread has been reported to the database from the Druid connection:
2020-07-15 11:46:01.228 INFO [ForkJoinPool-1-worker-15]... Caused by: java.lang.NullPointerException:null
at com.alibaba.druid.pool.DruidPooledConnection.transactionRecord(DruidPooledConnection.java:720)
at com.alibaba.druid.pool.DruidPooledStatement.transactionRecord(DruidPooledStatement.java:284)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:491)
Copy the code
Check out the source code:
// DruidPooledConnection.java:720
710 protected void transactionRecord(String sql) throws SQLException {
720 if (transactionInfo == null&& (! conn.getAutoCommit())) {721 DruidAbstractDataSource dataSource = holder.getDataSource();
722 dataSource.incrementStartTransactionCount();
723 transactionInfo = new TransactionInfo(dataSource.createTransactionId());
724}...Copy the code
That is, conn is null, and conn is an instance of java.sql.Connection, a few lines back
// DruidPooledPreparedStatement.java:491
486 public int getMaxFieldSize(a) throws SQLException {
487 checkOpen();
488
489 try {
490 return stmt.getMaxFieldSize();
491 } catch (Throwable t) {
492 throw checkException(t);
493 }
494}...Copy the code
Open ==false; open ==false
// DruidPooledPreparedStatement.java:491
protected void checkOpen(a) throws SQLException {
if (closed) {
Throwable disableError = null;
if (this.conn ! =null) {
disableError = this.conn.getDisableError();
}
if(disableError ! =null) {
throw new SQLException("statement is closed", disableError);
} else {
throw new SQLException("statement is closed"); }}}Copy the code
Conn ==null throws an exception. Could conn be concurrent and its state be changed by another thread?
Then looking up the log, the database connection is brokered by the distributed transaction framework:
2020-07-15 11:46:01.215 DEBUG [ForkJoinPool-1-worker-15] com.codingapi.txlcn.tc.aspect.weave.DTXResourceWeaver - proxy a sql connection: com.codingapi.txlcn.tc.core.transaction.lcn.resource.LcnConnectionProxy@65907ef8.
Copy the code
If there is a concurrency problem, is the connection being used by multiple threads, so then look for the keyword “65907EF8”,
2020-07-15 11:46:00.476 DEBUG [http-nio-20790-exec-39] com.codingapi.txlcn.tc.aspect.weave.DTXResourceWeaver - proxy a sql connection: com.codingapi.txlcn.tc.core.transaction.lcn.resource.LcnConnectionProxy@65907ef8.
......
2020-07-15 11:46:01.227 WARN [http-nio-20790-exec-39] com.codingapi.txlcn.tc.core.transaction.lcn.resource.LcnConnectionProxy - transaction type[lcn] proxy connection:com.codingapi.txlcn.tc.core.transaction.lcn.resource.LcnConnectionProxy@65907ef8 closed.
Copy the code
By comparing the time, it can be found that the HTTP-NiO-20790-exec-39 thread, which processes the application request, opens the proxy connection for distributed transactions, and closes the connection after processing. In between, forkJoinPool-1-worker-15 connections are proided. Finally, after the HTTP connection is closed, the ForkJoin thread throws an exception. Why, then, does the HTTP thread share a database connection?
An exception occurred with the forkjoin thread:
// Execute the check concurrentlyforkJoinConfig.getForkJoinPool().submit(() -> { list.parallelStream().forEach(checker -> { checker.check(param, result); }); }).join();Copy the code
GetForkJoinPool () takes the forkJoin thread pool that we configured for the list’s foreach operations.
View tX-LCN source code, part of the database agent:
// com.codingapi.txlcn.tc.aspect.weave.DTXResourceWeaver
public Object getConnection(ConnectionCallback connectionCallback) throws Throwable {
DTXLocalContext dtxLocalContext = DTXLocalContext.cur();
if (Objects.nonNull(dtxLocalContext) && dtxLocalContext.isProxy()) {
String transactionType = dtxLocalContext.getTransactionType();
TransactionResourceProxy resourceProxy = txLcnBeanHelper.loadTransactionResourceProxy(transactionType);
Connection connection = resourceProxy.proxyConnection(connectionCallback);
log.debug("proxy a sql connection: {}.", connection);
return connection;
}
return connectionCallback.call();
}
Copy the code
Presumably to get the transaction context and thus the proxy object for the database connection.
Forkjoin thread, log type “proxy a SQL Connection”, indicating dtxLocalContext! =null, what is DTXLocalContext?
// com.codingapi.txlcn.tc.core.DTXLocalContext
/** * gets the current thread variable. This method is not recommended, as it generates a NullPointerException * *@returnThe current thread variable */
public static DTXLocalContext cur(a) {
return currentLocal.get();
}
///
private final static ThreadLocal<DTXLocalContext> currentLocal = new InheritableThreadLocal<>();
Copy the code
So the problem should be currentLocal, which is an instance of InheritableThreadLocal.
Get (ThreadLocal) allows you to retrieve objects stored by the current thread.
InheritableThreadLocal works similarly, except that when a child thread is initialized, it copies the parent thread’s map data.
// java.lang.Thread#init
if(inheritThreadLocals && parent.inheritableThreadLocals ! =null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
Copy the code
InheritableThreadLocals is what we call the thread of the map, it is a ThreadLocal ThreadLocalMap instance.
In our example, the forkJoin thread is created by copying the data of the parent thread with a DTXLocalContext object, indicating that the forkJoinPool-1-worker-15 thread was created by http-NiO-20790-execut-39. That means the same forkJoin thread pool was used for HTTP thread execution (verified by code search).
conclusion
In order to track the concurrency in request Q1, we store the distributed transaction context, including the database connection, in the InheritableThreadLocal object, so that child thread A can obtain the distributed transaction context and obtain the same database connection later.
However, if subthread A is in the thread pool, and another request Q2 uses the thread pool and happens to be partitioned to thread A, then the same database connection is obtained. However, this database connection is controlled by request Q1, and Q2 is at risk of closing the database connection at any time.
Conversely, if a modification is made to request Q2, the accuracy of request Q1 may also be affected, i.e. the database connection is compromised for Q1.
How do distributed transaction contexts and thread pools coexist?Copy the code
Druid: 1.1.16tx-lcn: 6.0.20.RELEASE druid: 1.1.16tx-lcn: 6.0.20.RELEASECopy the code
Blog address: Wonderwater.github. IO /