New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GORM throws NHibernateExpection: No Session Found... when in GParsPool closure. #10654

Closed
jgossett opened this Issue May 31, 2017 · 2 comments

Comments

Projects
None yet
2 participants
@jgossett

jgossett commented May 31, 2017

I am trying to be a good developer and backfill some integration test. I found out GORM throws a NHibernateException when in GParsPool.withPool closure. I assume this is related to GPars thread pool and GORM needs a session for each thread.

I however do not know how to resolve this problem.

Note, we need GParsPool. It increased our performance of a job from 60 minutes to 5 minutes.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

Use the following code. The second unit test throws the exception.

`
@Integration
@Rollback
class HelloControllerIntegrationSpecSpec extends Specification {

def "should create 2 people using each method"() {
given:
def firstNames = ['Josh', 'Dave']
def count = 0

when:

firstNames.each { firstName ->
    Person person = new Person(
            firstName: firstName,
            lastName: 'Gossett'
    )
    person.save()

    count++
}

then:
count == 2

}

def "should create 2 people using eachParallel method"() {
given:
def firstNames = ['Josh', 'Dave']
def count = 0
when:
GParsPool.withPool(4, {
firstNames.eachParallel { firstName ->
Person person = new Person(
firstName: firstName,
lastName: 'Gossett'
)
person.save()

        count++
    }
})

then:
count == 2

}
`

Stacktrace

`
Grails application running at http://localhost:57338/hellowworld in environment: test

org.hibernate.HibernateException: org.hibernate.HibernateException: No Session found for current thread

at jsr166y.ForkJoinTask.getThrowableException(ForkJoinTask.java:504)
at jsr166y.ForkJoinTask.reportResult(ForkJoinTask.java:567)
at jsr166y.ForkJoinTask.join(ForkJoinTask.java:611)
at jsr166y.ForkJoinPool.invoke(ForkJoinPool.java:1492)
at groovyx.gpars.extra166y.ParallelArrayWithMapping.apply(ParallelArrayWithMapping.java:48)
at groovyx.gpars.extra166y.ParallelArray.apply(ParallelArray.java:329)
at groovyx.gpars.pa.GParsPoolUtilHelper.eachParallelPA(GParsPoolUtilHelper.groovy:192)
at groovyx.gpars.GParsPoolUtil.eachParallel(GParsPoolUtil.java:358)
at helloworld.HelloControllerIntegrationSpecSpec._tt___spock_feature_0_1_closure4(HelloControllerIntegrationSpecSpec.groovy:40)
at groovyx.gpars.GParsPool.withExistingPool_closure1(GParsPool.groovy:174)
at groovyx.gpars.GParsPool.withExistingPool_closure1(GParsPool.groovy)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:408)
at groovyx.gpars.GParsPool.withExistingPool(GParsPool.groovy:173)
at groovyx.gpars.GParsPool.withPool(GParsPool.groovy:145)
at groovyx.gpars.GParsPool.withPool(GParsPool.groovy:121)
at helloworld.HelloControllerIntegrationSpecSpec.$tt__$spock_feature_0_1(HelloControllerIntegrationSpecSpec.groovy:39)
at helloworld.HelloControllerIntegrationSpecSpec.should create 2 people using each parallel_closure2(HelloControllerIntegrationSpecSpec.groovy)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at grails.transaction.GrailsTransactionTemplate$1.doInTransaction(GrailsTransactionTemplate.groovy:70)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at grails.transaction.GrailsTransactionTemplate.executeAndRollback(GrailsTransactionTemplate.groovy:67)
at helloworld.HelloControllerIntegrationSpecSpec.should create 2 people using each parallel(HelloControllerIntegrationSpecSpec.groovy)

Caused by: org.hibernate.HibernateException: No Session found for current thread
at org.grails.orm.hibernate.GrailsSessionContext.currentSession(GrailsSessionContext.java:116)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:688)
at org.grails.orm.hibernate.cfg.GrailsHibernateUtil.setObjectToReadWrite(GrailsHibernateUtil.java:325)
at org.grails.orm.hibernate.HibernateGormInstanceApi.setObjectToReadWrite(HibernateGormInstanceApi.groovy:133)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:136)
at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:116)
at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:98)
at helloworld.HelloControllerIntegrationSpecSpec._tt___spock_feature_0_1_closure4$_closure5(HelloControllerIntegrationSpecSpec.groovy:45)
at groovyx.gpars.pa.GParsPoolUtilHelper.eachParallelPA_closure8(GParsPoolUtilHelper.groovy:192)
at groovy.lang.Closure.call(Closure.java:414)
at groovyx.gpars.extra166y.AbstractParallelAnyArray$OUPap.leafApply(AbstractParallelAnyArray.java:640)
at groovyx.gpars.extra166y.PAS$FJOApply.atLeaf(PAS.java:147)
at groovyx.gpars.extra166y.PAS$FJBase.internalCompute(PAS.java:118)
at groovyx.gpars.extra166y.PAS$FJBase.compute(PAS.java:106)
at jsr166y.RecursiveAction.exec(RecursiveAction.java:148)
at jsr166y.ForkJoinTask.doExec(ForkJoinTask.java:305)
at jsr166y.ForkJoinWorkerThread.execTask(ForkJoinWorkerThread.java:575)
at jsr166y.ForkJoinPool.scan(ForkJoinPool.java:755)
at jsr166y.ForkJoinPool.work(ForkJoinPool.java:617)
at jsr166y.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:369)
`

Expected Behaviour

GORM work with GPars.

Actual Behaviour

An exception is thrown.

Environment Information

  • Operating System: MacOS
  • Grails Version: 3.2.4
  • JDK Version: 1.7.X

@sdelamo sdelamo self-assigned this Jun 6, 2017

@sdelamo

This comment has been minimized.

Show comment
Hide comment
@sdelamo

sdelamo Jun 6, 2017

Contributor

You could call Person.withTransaction inside the eachParallel closure.

  def "should create 2 people using eachParallel method"() {
        given:
        def firstNames = ['Josh', 'Dave']

        when:
        GParsPool.withPool(4, {
            firstNames.eachParallel { firstName ->
                Person.withTransaction {
                    Person person = new Person(
                            firstName: firstName,
                            lastName: 'Gossett'
                    )
                    person.save()
                }
            }
        })

        then:
        Person.count() == old(Person.count()) + firstNames.size()
    }
Contributor

sdelamo commented Jun 6, 2017

You could call Person.withTransaction inside the eachParallel closure.

  def "should create 2 people using eachParallel method"() {
        given:
        def firstNames = ['Josh', 'Dave']

        when:
        GParsPool.withPool(4, {
            firstNames.eachParallel { firstName ->
                Person.withTransaction {
                    Person person = new Person(
                            firstName: firstName,
                            lastName: 'Gossett'
                    )
                    person.save()
                }
            }
        })

        then:
        Person.count() == old(Person.count()) + firstNames.size()
    }

@sdelamo sdelamo closed this Jun 6, 2017

@jgossett

This comment has been minimized.

Show comment
Hide comment
@jgossett

jgossett Jul 3, 2017

sdelamo,

Your response was helpful. Thank you.

I tried to write an integration test and I discovered another problem. When I do create another session or transaction it seems like the it uses another database. I was expecting it to use the same database. Have you see this before.

Below is my code.

package bookstore


import grails.test.mixin.integration.Integration
import grails.transaction.*
import groovyx.gpars.GParsPool
import spock.lang.*

@Integration
@Rollback
class BookSpec extends Specification {

    def "should find book on another thread."() {
        given:
        Book bookCreated = new Book(
                Title: "Truly Madly Guilty"
        ).save(flush: true)

        List<Long> bookIdList = [bookCreated.id]

        Book bookFoundOnThread
        Book bookFoundOnNotThread

        when:
        // not on thread
        bookFoundOnNotThread = Book.get(bookIdList[0])

        // on thread.
        GParsPool.withPool {
            bookIdList.eachParallel { Long bookId ->
                Book.withNewSession {
                    bookFoundOnThread = Book.get(bookId)
                }
            }
        }

        then:
        bookFoundOnNotThread
        bookFoundOnThread
    }
}

jgossett commented Jul 3, 2017

sdelamo,

Your response was helpful. Thank you.

I tried to write an integration test and I discovered another problem. When I do create another session or transaction it seems like the it uses another database. I was expecting it to use the same database. Have you see this before.

Below is my code.

package bookstore


import grails.test.mixin.integration.Integration
import grails.transaction.*
import groovyx.gpars.GParsPool
import spock.lang.*

@Integration
@Rollback
class BookSpec extends Specification {

    def "should find book on another thread."() {
        given:
        Book bookCreated = new Book(
                Title: "Truly Madly Guilty"
        ).save(flush: true)

        List<Long> bookIdList = [bookCreated.id]

        Book bookFoundOnThread
        Book bookFoundOnNotThread

        when:
        // not on thread
        bookFoundOnNotThread = Book.get(bookIdList[0])

        // on thread.
        GParsPool.withPool {
            bookIdList.eachParallel { Long bookId ->
                Book.withNewSession {
                    bookFoundOnThread = Book.get(bookId)
                }
            }
        }

        then:
        bookFoundOnNotThread
        bookFoundOnThread
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment