Home > database >  Integration testing concurrent idempotency in Grails 4 and Spock failing
Integration testing concurrent idempotency in Grails 4 and Spock failing

Time:10-21

I am running into some trouble with transactions & hibernate sessions while testing concurrency in an integration test.

Essentially we have a process that updates a few different database entries including some accounting entries. This process should be idempotent for a given id, including when being called concurrently from different threads.

I am trying to update an old test that hasn't worked for a while to work in Grails 4.0.12. This test last worked in Grails 2.x.

The code below is generalized for simplicity.

void testName() {
    given:
    Thing thing = Thing.findAll(someId)[0]
    int attemptsPerThing = 5
    
    when:
    def pool = Executors.newFixedThreadPool(attemptsPerThing)
    List<Future> futureList = []
    
    for (int i = 0; i < attemptsPerThing; i  ) {
        Future future = pool.submit({
            try {
                serviceToBeTested.functionToBeTestedThatCommitsThingsToDB(thing)
            } catch (Exception _) {}
        })
        futureList.add(future)
    }
    
    for (Future future in futureList) {
        def futureResults = future.get()
        assert futureResults == null
    }
    
    pool.shutdown()
    assert pool.awaitTermination(60, TimeUnit.SECONDS)

    sessionFactory.currentSession.clear()
    
    then:
    // QUERY DATABASE AND ASSERT THAT THE CALL ONLY COMPLETED ONCE AND DATA IS AS EXPECTED
}

This test fails. Upon initial investigation it looks to be failing because nothing within the future closures gets committed in a way that is visible within the test. It looks as if the function was never called.

Upon interrogating the exception caught in that try block I can see that all the iterations are actually failing with an exception unrelated to the business logic. The exception is:

org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : [com.kiind.coreservices.KiindUser.paymentInfoByCurrency#70]

The above com.kiind.coreservices.KiindUser.paymentInfoByCurrency is a collection of domain objects that live in the database but are only read in this function.

So my question is, is there a best practice for testing concurrency in Grails 4 / Spock? Is it bad practice to test concurrency in an integration test?

Specifically, is there a way to attach the test's hibernate session to the threads so that they can all share it? Or get the threads to work with their own session, complete their transactions, and then update the session in the test with the current state of the database during the then?

Thanks for any assistance and let me know if additional information is required.

CodePudding user response:

You are loading an object in the main thread from an Hibernate Session:

Thing thing = Thing.findAll(someId)[0]

And then you are using that same object in another thread:

serviceToBeTested.functionToBeTestedThatCommitsThingsToDB(thing)

The Hibernate session and the objects associated to that session altogether are not thread safe, so you cannot do that. The safe way it to let each thread manage its own session and never pass objects around between threads.

CodePudding user response:

i believe the only way to see one thread's atomic transaction by another thread in the pool is to turn off transaction management. You can do that in the spock integration test, and you can certainly do that in the real service as well. But that is not recommended, and transactions are there to avoid this.

In other words, what @Guillaume said above.

  • Related