Skip to content

NullPointerException: Cannot invoke "org.h2.mvstore.tx.Transaction.getStatus()" because "this.transaction" is null #4352

@mkeller-ergon

Description

@mkeller-ergon

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions