This happened in 2.1.214 and also 2.4.240, under some (not too high) concurrency, we often get this NPE:
org.jooq.exception.DataAccessException: Cannot get transactionIsolation
at org.jooq.impl.DefaultConnectionProvider.getTransactionIsolation(DefaultConnectionProvider.java:270)
at org.jooq.impl.DefaultTransactionProvider.isolation(DefaultTransactionProvider.java:156)
at org.jooq.impl.DefaultTransactionProvider.brace(DefaultTransactionProvider.java:286)
at org.jooq.impl.DefaultTransactionProvider.begin(DefaultTransactionProvider.java:203)
at org.jooq.impl.DefaultDSLContext.lambda$transactionResult0$3(DefaultDSLContext.java:559)
at org.jooq.impl.Tools$3$1.block(Tools.java:6543)
at java.base/java.util.concurrent.ForkJoinPool.unmanagedBlock(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.managedBlock(Unknown Source)
at org.jooq.impl.Tools$3.get(Tools.java:6540)
at org.jooq.impl.DefaultDSLContext.transactionResult0(DefaultDSLContext.java:613)
at org.jooq.impl.DefaultDSLContext.transactionResult(DefaultDSLContext.java:522)
...
Caused by: org.h2.jdbc.JdbcSQLNonTransientException: General error: "java.lang.NullPointerException: Cannot invoke ""org.h2.mvstore.tx.Transaction.getStatus()"" because ""this.transaction"" is null"; SQL statement:
SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID() [50000-240]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:566)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:212)
at org.h2.message.DbException.convert(DbException.java:407)
at org.h2.command.Command.executeQuery(Command.java:219)
at org.h2.server.TcpServerThread.process(TcpServerThread.java:360)
at org.h2.server.TcpServerThread.run(TcpServerThread.java:193)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NullPointerException: Cannot invoke "org.h2.mvstore.tx.Transaction.getStatus()" because "this.transaction" is null
at org.h2.engine.SessionLocal.hasPendingTransaction(SessionLocal.java:867)
at org.h2.table.InformationSchemaTable.sessions(InformationSchemaTable.java:2711)
at org.h2.table.InformationSchemaTable.sessions(InformationSchemaTable.java:2675)
at org.h2.table.InformationSchemaTable.generateRows(InformationSchemaTable.java:980)
at org.h2.index.MetaIndex.find(MetaIndex.java:53)
It happens since jooq always reads the transaction isolation on every request, leading to the query "SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS". There seems to be a race condition if there are concurrent writing requests while this meta table is built:
Building that meta-table makes H2 iterate over all live sessions and call SessionLocal.hasPendingTransaction() on each (a non-atomic transaction != null check followed by transaction.getStatus(). When another session commits (nulling its transaction) in between, the read races and H2 throws NullPointerException.
It can easily be reproduced by having one thread repeatedly accessing the transaction isolation level while another repeatedly updates some data.
See this unit test demonstrating the bug:
H2SessionsIsolationRaceTest.java
This happened in 2.1.214 and also 2.4.240, under some (not too high) concurrency, we often get this NPE:
It happens since jooq always reads the transaction isolation on every request, leading to the query "SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS". There seems to be a race condition if there are concurrent writing requests while this meta table is built:
Building that meta-table makes H2 iterate over all live sessions and call
SessionLocal.hasPendingTransaction()on each (a non-atomictransaction != nullcheck followed bytransaction.getStatus(). When another session commits (nulling itstransaction) in between, the read races and H2 throwsNullPointerException.It can easily be reproduced by having one thread repeatedly accessing the transaction isolation level while another repeatedly updates some data.
See this unit test demonstrating the bug:
H2SessionsIsolationRaceTest.java