2
votes

We had a production incident that resulted in a bunch of threads deadlocking and the server stopped working. To try and investigate, I tested some stuff with different spring transactional propagations, and if I'm not mistaken, the REQUIRES_NEW propagation will start two connections if there is no existing transaction at all. Is this correct?? I tried googling, but found no information about this.

I made a test. Here's a sample class:

package test;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TheService {

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void doSomething() {
        System.out.println("Here I am doing something.");
    }
}

Here is the unit test that I made:

package test;

import javax.annotation.Resource;

import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;

@ContextConfiguration(locations = {"classpath:test.xml"})
public class TheServiceTest extends AbstractTransactionalJUnit4SpringContextTests {

    @Resource
    private TheService theService;

    @Test
    public void test() {
        theService.doSomething();
    }

}

And last but not least, here is my test xml:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
    default-autowire="byName">

    <context:component-scan base-package="test" />

    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="operator.entityManagerFactory" />
    </bean>

    <bean id="operator.entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="operatorPersistenceUnit" />
        <property name="dataSource" ref="operator.dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="false" />
                <property name="generateDdl" value="true" />
                <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
            </bean>
        </property>
    </bean>

    <bean id="operator.dataSource" class="org.apache.commons.dbcp.BasicDataSource"  destroy-method="close">
        <property name="driverClassName" value="org.h2.Driver" />
        <property name="url" value="jdbc:h2:mem:operator" />
        <property name="username" value="sa" />
        <property name="password" value="" />
        <property name="maxActive" value="1" /> <!-- NOTE -->
    </bean>
</beans>

The reason why I want REQUIRES_NEW for a method is because it is vital to not get any dirty reads from it, and it can be executed from both within another transaction and outside.

If I keep the maxActive property at 1, this test will deadlock and never print anything. If I change it to 2 however, the test will go through.

The reason why this is a concern is that even if I set maxActive to a much higher value, with enough threads waiting to execute this method, they can all end up occupy one connection each and wait for a second one.

Have I done something wrong? Have I misunderstood anything?

I appreciate any help! Thanks!

1
No it doesn't open 2 connections. It does in your test because 1 is opened by your test (it is transactional) and a new one in your service because the REQUIRES_NEW.. - M. Deinum
Why is one opened by my test? Is there a way to avoid that? Also, sometimes to see if a certain method is in a transaction I add a new Exception().printStackTrace() and I look for the TransactionInterceptor. This, I cannot see when adding it in the test method. - user3306066
What do you think AbstractTransactionalJUnit4SpringContextTests does... that Transactional part of the classname is there for a reason. There are more ways to work with transactions. For tests that is done by the TransactionalTestExecutionListener which manually start/stops transactions. If you don't want your tests to be transactional don't extend AbstractTransactionalJUnit4SpringContextTests. - M. Deinum

1 Answers

2
votes

It has nothing to do with propagation=REQUIRES_NEW that will NOT open 2 connections by default. The issue is that you are extending AbstractTransactionalJUnit4SpringContextTests.

As your test case extends AbstractTransactionalJUnit4SpringContextTests which, as you can see, is @Transactional. This transaction, for tests, is managed by the TransactionalTestExecutionListener.

So what happens is when you start your test, before the execution of the test method a transaction is started by the test framework. Next you invoke your service which starts another transaction due to being annotated with @Transactional(propagation=REQUIRES_NEW).

The fix is quite easy don't extend AbstractTransactionalJUnit4SpringContextTests and just annotate your class with @RunWith(SpringRunner.class).

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"classpath:test.xml"})
public class TheServiceTest { ... }