Lessons from "The connection is closed"

Our DevOps team reported that a specific RESTful service had suddenly started throwing a large number of exceptions.

Upon checking the logs, it was mainly the below exception -

Caused by: java.sql.SQLRecoverableException: The connection is closed
    at oracle.ucp.jdbc.proxy.oracle.ConnectionProxy.pre(ConnectionProxy.java:237)
    at oracle.ucp.jdbc.proxy.oracle$1ucp$1jdbc$1proxy$1oracle$1ConnectionProxy$2oracle$1jdbc$1internal$1OracleConnection$$$Proxy.prepareStatement(Unknown Source)
    at oracle.jdbc.OracleConnectionWrapper.prepareStatement(OracleConnectionWrapper.java:161)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$1.doPrepare(StatementPreparerImpl.java:94)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:183)

The database connections were getting closed unexpectedly while the requests (events) were still being processed.

We were using Oracle Universal Connection Pool (UCP). The PoolDataSource in UCP has a property abandonedConnectionTimeout. This timeout determines how long a borrowed connection can remain unused before it is considered as abandoned and reclaimed by the connection pool.

In our case, while processing an event, the service was borrowing a connection from the pool, but it was remaining unused for longer than the abandonedConnectionTimeout. Hence the above exception.

This exposed another flaw in the service’s design - the service was holding database connections for too long, even when it was not performing any database operations. Ideally, connections should be used only during the database transactions and then promptly returned to the pool for reuse. This ensures efficient resource management.

Prototyping

To make debugging easier and try various cases, I built a small application that replicated the same issue of closed connections.

You can find the source code in a GitHub repository here.

The EmployeeController has an endpoint /employees/bulk that creates and saves two employees in the database. However, there’s a 60-second delay between saving each employee. This delay represents a long running process in our production application that does not perform any database transactions.

This delay of 60-seconds is greater than the abandoned-connection-timeout of 20 seconds configured inside application.yml and DataSourceConfig.java files.

The createEmployee method in the EmployeeService class is a straightforward implementation. It simply takes the received Employee object and saves it using EmployeeRepository.

To monitor database connections and transaction activities behind the scenes, logging is enabled for the org.springframework.orm.jpa.JpaTransactionManager class in application.yml.

When you send a POST request to the endpoint /employees/bulk, you see that the application throws below exception —

java.sql.SQLRecoverableException: The connection is closed
    at oracle.ucp.jdbc.proxy.oracle.ConnectionProxy.pre(ConnectionProxy.java:246) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.jdbc.proxy.oracle$1ucp$1jdbc$1proxy$1oracle$1ConnectionProxy$2oracle$1jdbc$1internal$1OracleConnection$$$Proxy.setAutoCommit(Unknown Source) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.begin(AbstractLogicalConnectionImplementor.java:72) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.begin(LogicalConnectionManagedImpl.java:295) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.begin(JdbcResourceLocalTransactionCoordinatorImpl.java:232) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
    at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:83) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:176) ~[spring-orm-6.2.3.jar:6.2.3]
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:420) ~[spring-orm-6.2.3.jar:6.2.3]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:532) ~[spring-tx-6.2.3.jar:6.2.3]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:405) ~[spring-tx-6.2.3.jar:6.2.3]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:639) ~[spring-tx-6.2.3.jar:6.2.3]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:374) ~[spring-tx-6.2.3.jar:6.2.3]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.3.jar:6.2.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.3.jar:6.2.3]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.3.jar:6.2.3]
    at com.ucp.example.service.EmployeeService$$SpringCGLIB$$0.createEmployee(<generated>) ~[main/:na]
    at com.ucp.example.controller.EmployeeController.createEmployees(EmployeeController.java:46) ~[main/:na]

Since the abandoned-connection-timeout property was set to 20 seconds, the database connection used by the EntityManager was closed after remaining unused for 20 seconds.

More importantly - and crucially - this reveals that the same EntityManager and the same database connection were used, even though the two database operations occurred in separate transactions.

In the log console, you will see the logs Found thread-bound EntityManager. This indicates that Spring creates EntityManager at the beginning of request and the application uses same EntityManager for saving both the employees.

c.u.e.controller.EmployeeController      : saving first employee ...
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(910391533<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.ucp.example.service.EmployeeService.createEmployee]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@3b531f40]
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(910391533<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(910391533<open>)]
o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
c.u.e.controller.EmployeeController      : waiting for 60 seconds ...
c.u.e.controller.EmployeeController      : saving second employee ...
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(910391533<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.ucp.example.service.EmployeeService.createEmployee]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

The database connection was held beyond the duration of the database operations, even when it was not actively in use.

It might happen that in a production system, for example, if abandoned-connection-timeout is set to 10 minutes, and the application performs a database operation followed by a 5 minutes of in-memory processing, the connection remains held for those 5 minutes. This is not an efficient use of database resources.

Spring’s Open Session in View (OSIV)

There are lot of articles on the internet that explains this in greater detail. This article talks the basics.

Spring's Open Session in View (OSIV) keeps a Hibernate Session (or JPA EntityManager) open for the entire request lifecycle, allowing lazy-loaded entities to be fetched even after the transaction has ended.

By default, OSIV is enabled. As we have observed, it can lead to inefficient database connection usage where connections are held without actually being used. This can lead to database connection starvation. To see this in action, simply run the application and send 5 POST requests to an endpoint. Given that max-pool-size is set to 4, the first 4 requests will successfully fetch connections from the pool. The 5th request, however, will be forced to wait for 10 seconds (as per connection-wait-duration). Once the timeout expires and the request is still waiting for connection, the application will throw the following exception:

oracle.ucp.UniversalConnectionPoolException: UCP-45064: All connections in the Universal Connection Pool are in use - [ 4, 4, 4, 0, 0, 0, 1, 0, 4, 4 ]
    at oracle.ucp.util.UCPErrorHandler.newUniversalConnectionPoolException(UCPErrorHandler.java:316) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.util.UCPErrorHandler.throwUniversalConnectionPoolException(UCPErrorHandler.java:62) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.util.UCPErrorHandler.throwUniversalConnectionPoolException(UCPErrorHandler.java:98) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.common.UniversalConnectionPoolImpl.borrowConnectionWithoutCountingRequests(UniversalConnectionPoolImpl.java:520) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.common.UniversalConnectionPoolImpl.borrowConnectionAndValidateHelper(UniversalConnectionPoolImpl.java:279) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.common.UniversalConnectionPoolImpl.borrowConnectionAndValidate(UniversalConnectionPoolImpl.java:191) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.common.UniversalConnectionPoolImpl.borrowConnection(UniversalConnectionPoolImpl.java:157) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]
    at oracle.ucp.jdbc.JDBCConnectionPool.borrowConnection(JDBCConnectionPool.java:207) ~[ucp-23.3.0.23.09.jar:23.3.0.23.09]

You can disable Open Session in View (OSIV) by setting:

spring:
  jpa:
    open-in-view: false

When OSIV is disabled, Spring no longer opens an EntityManager at the start of a request. Instead, a new EntityManager is created only when a transaction begins with @Transactional and is closed immediately once the transaction completes. This ensures more efficient database connection usage and prevents connections from being held unnecessarily.

With OSIV disabled, when you send a request to the endpoint, it will be processed successfully. Additionally, if you send 5 requests simultaneously, all 5 requests will be handled without issues.

Key takeaways

  • Connections should be borrowed only while performing database operations and then promptly returned to the pool for reuse. This ensures efficient resource management.

  • Executing database operations in separate transactions does not necessarily mean that different database connections will be used.

  • Consider optimizing database resource usage by disabling Spring OSIV, especially for applications that perform extensive processing outside transactional contexts.