spring-projects/spring-data-neo4j

Transaction timeout is not happening when setting the spring.transaction.default-timeout property.

Closed this issue · 5 comments

When I'm using declarative method level @transactional(timeout = 15), then transaction timeout is happening properly. But when I removed the timeout property from @transactional and put spring.transaction.default-timeout=15 this time timeout is not happening.

Hello @amit-kumaryadav I cannot reproduce this.

Here's a test showing all combinations I would say we support:

package org.neo4j.timeoutdemo;

import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.time.Duration;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.neo4j.core.Neo4jTemplate;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers(disabledWithoutDocker = true)
@Import(ServiceConnectionsConfig.class)
class TimeoutTests {

	static final String EXPECTED_MSG = "The transaction has been terminated. Retry your operation in a new transaction, and you should see a successful result. The transaction has not completed within the timeout specified at its start by the client. You may want to retry with a longer timeout.";

	@DynamicPropertySource
	static void setDefaultTime(DynamicPropertyRegistry registry) {
		System.out.println();
		registry.add("spring.transaction.default-timeout", () -> Duration.ofSeconds(3));
	}

	@Test
	void defaultTimeoutGetsApplied(@Autowired BookRepository bookRepository) {
		assertThatExceptionOfType(InvalidDataAccessResourceUsageException.class)
			.isThrownBy(() -> bookRepository.save(new Book("Test")))
			.withMessageContaining(EXPECTED_MSG);
	}

	@Test
	void annotatedMethodTimeoutGetsApplied(@Autowired BookRepository bookRepository) {
		assertThatExceptionOfType(InvalidDataAccessResourceUsageException.class)
			.isThrownBy(bookRepository::findAll)
			.withMessageContaining(EXPECTED_MSG);
	}

	@Test
	void usingTransactionTemplate(@Autowired BookRepository bookRepository, @Autowired Neo4jTransactionManager transactionManager) {

		var transactionTemplate = new TransactionTemplate(transactionManager);
		transactionTemplate.setTimeout(1); // Seconds, again
		assertThatExceptionOfType(InvalidDataAccessResourceUsageException.class)
			.isThrownBy(() -> transactionTemplate.execute(
				tx -> bookRepository.findByTitle("whatever"))
			)
			.withMessageContaining(EXPECTED_MSG);
	}

	@Test
	void usingTransactionTemplateWithNeo4jTemplate(@Autowired Neo4jTemplate neo4jTemplate, @Autowired Neo4jTransactionManager transactionManager) {

		var transactionTemplate = new TransactionTemplate(transactionManager);
		transactionTemplate.setTimeout(1);
		assertThatExceptionOfType(InvalidDataAccessResourceUsageException.class)
			.isThrownBy(() -> transactionTemplate.execute(
				tx -> neo4jTemplate.findAll("CALL apoc.util.sleep(2000) MATCH (n:Book) RETURN n", Book.class)))
			.withMessageContaining(EXPECTED_MSG);
	}
}

In general, the algorithm is such that if anything is specified on the method or on the transaction template with a value greater zero, that value applies. Otherwise, if the default value is greater zero, the default applies. If no value is specified, no time out is applied.
I can only guess that you might have a declaration laying around somewhere.

Here's the full test project attached:
timeoutdemo.zip

@michael-simons In in our project extensively we are using Neo4jClient instead or Repository, I tried with Neo4jClient and it's not working, I'm using spring boot 3.2.5 version.

@Transactional( value = "transactionManager", readOnly = true)
    public VehicleResponse getVehicleData(String id) {
        Optional<Map<String, Object>> queryResult =
                neo4jClient.getResultUsingNeo4jClient(QueryConstants.QUERY_TO_GET_VEHICLE_DATA, Map.of(Constants.ID, id));
        if (queryResult.isEmpty()) {
            return VehicleResponse.builder().build();
        }
        return commonUtils.convertValue(queryResult.get().get("result"),
                VehicleResponse.class);
    } 

Works for me, even with 3.2.5

See updated projects:

timeoutdemo_325.zip
timeoutdemo_331.zip

My suspicion is that for some reason you reference the wrong tx-manager. I know that this is necessary in 3.2.5 (and no longer in 3.3.x).

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.