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 /