Skip to content

Commit

Permalink
feat(core): Support beginTransaction with TransactionConfig duration (#…
Browse files Browse the repository at this point in the history
…620)

* Configure timeout before beginning neo4j transactions

* Create a new neo4j session if transaction propagation requires a new one

* Add integration tests
  • Loading branch information
guillermocalvo authored Mar 2, 2024
1 parent d63b4ea commit 3c69480
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package example

import grails.gorm.transactions.Transactional
import org.springframework.transaction.annotation.Propagation

@Transactional
class LibraryService {
Expand All @@ -14,6 +15,16 @@ class LibraryService {
bookService.get(id)
}

@Transactional(timeout = 60, propagation = Propagation.REQUIRES_NEW)
Book getBookWithLongTimeout(Serializable id) {
Book.executeQuery('CALL grails.sleep(4000) MATCH (n:Book) WHERE ( ID(n)=$1 ) RETURN n as data', [id])
}

@Transactional(timeout = 1, propagation = Propagation.REQUIRES_NEW)
Book getBookWithShortTimeout(Serializable id) {
Book.executeQuery('CALL grails.sleep(4000) MATCH (n:Book) WHERE ( ID(n)=$1 ) RETURN n as data', [id])
}

Person addMember(String firstName, String lastName) {
assert personService != null
personService.save(firstName, lastName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ class TestService {
libraryService.bookExists(id)
}

Book testDataServiceWithLongTimeout(Serializable id) {
libraryService.getBookWithLongTimeout(id)
}

Book testDataServiceWithShortTimeout(Serializable id) {
libraryService.getBookWithShortTimeout(id)
}

Person save(String firstName, String lastName) {
libraryService.addMember(firstName, lastName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package example

import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
import org.neo4j.driver.exceptions.ClientException
import org.springframework.beans.factory.annotation.Autowired
import spock.lang.Specification

Expand All @@ -23,6 +24,22 @@ class TestServiceSpec extends Specification {
noExceptionThrown()
}

void "test transaction with long timeout"() {
when:
testService.testDataServiceWithLongTimeout()

then:
noExceptionThrown()
}

void "test transaction with short timeout"() {
when:
testService.testDataServiceWithShortTimeout()

then:
thrown(ClientException)
}

void "test autowire by type"() {

expect:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ protected void doBegin(Object o, TransactionDefinition definition) throws Transa
try {
session = (Neo4jSession) txObject.getSessionHolder().getSession();

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
session = (Neo4jSession) getDatastore().connect();
txObject.setSession(session);
}

if (definition.isReadOnly()) {
// Just set to NEVER in case of a new Session for this transaction.
session.setFlushMode(FlushModeType.COMMIT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ import groovy.util.logging.Slf4j
import org.neo4j.driver.AccessMode
import org.neo4j.driver.Driver
import org.neo4j.driver.Session
import org.neo4j.driver.TransactionConfig
import org.grails.datastore.mapping.transactions.Transaction
import org.neo4j.driver.SessionConfig
import org.springframework.transaction.TransactionDefinition
import org.springframework.transaction.support.DefaultTransactionDefinition

import java.time.Duration

/**
* Represents a Neo4j transaction
*
Expand All @@ -48,7 +51,11 @@ class Neo4jTransaction implements Transaction<org.neo4j.driver.Transaction>, Clo

log.debug("TX START: Neo4J beginTx()")
this.boltSession = boltDriver.session(SessionConfig.builder().withDefaultAccessMode(transactionDefinition.readOnly ? AccessMode.READ : AccessMode.WRITE).build())
transaction = boltSession.beginTransaction()
final TransactionConfig.Builder config = TransactionConfig.builder()
if (transactionDefinition.timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
config.withTimeout(Duration.ofSeconds(transactionDefinition.timeout))
}
transaction = boltSession.beginTransaction(config.build())
this.transactionDefinition = transactionDefinition
this.sessionCreated = sessionCreated
}
Expand Down Expand Up @@ -98,6 +105,10 @@ class Neo4jTransaction implements Transaction<org.neo4j.driver.Transaction>, Clo
}

void setTimeout(int timeout) {
throw new UnsupportedOperationException()
log.debug("TX TIMEOUT: Neo4j tx.setTimeout({})", timeout);
// Neo4j tx config is immutable
if (timeout != transactionDefinition.timeout) {
log.warn("Transaction timeout for '{}' was already configured to {} seconds", transactionDefinition.name, transactionDefinition.timeout)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public static ServerControls start(String host, int port, File dataLocation, Map
}

return serverBuilder
.withProcedure(GrailsProcedures.class)
.newServer();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.grails.datastore.gorm.neo4j.util;

import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

/**
* Grails procedures for Neo4j embedded server.
*/
public class GrailsProcedures {

/**
* Procedure for Neo4j that sleeps for the specified number of milliseconds.
* @param millis the length of time to sleep in milliseconds
*/
@Procedure("grails.sleep")
@Description("Sleep for the specified number of milliseconds.")
public void sleep(@Name(value = "millis", defaultValue = "1000") Long millis) {
try {
Thread.sleep(millis);
} catch (Exception e) {
e.printStackTrace();
}
}
}

0 comments on commit 3c69480

Please sign in to comment.