grailsquartz-schedulergrails-4

Illegal attempt to associate a collection with two open sessions thrown in Quartz Job


I have a Grails 4.0.10 application which is a running registration application.

I am getting a Illegal attempt to associate a collection with two open sessions error in a Quartz Job in my application.

This is the method in RegistrationService called by PaymentController. It triggers BibAssigner Job at the end. The BibAssigner Job is responsible for assigning bibs to the registrations.

RegistrationService

@Transactional
void activateRegistrations(SaleInvoice invoice){
    Assert.notNull(invoice, 'SaleInvoice cannot be null when activating registrations.')
    List<Registration> registrations = this.findRegistrationsBySaleInvoice(invoice)
    
    
    def mp = [:]
    
    
    registrations.each{
        it.status = EntityStatus.ACTIVE



        if (it.race.capacity) {
            it.race.capacity?.allocated += 1
            it.race.capacity?.save()
        }


        if (it.compositeEvent.capacity) {
            it.compositeEvent.capacity?.allocated += 1
            it.compositeEvent.capacity?.save()
        }

        if (it.registrationEntry?.template?.capacity) {
            it.registrationEntry.template.capacity?.allocated += 1
            it.registrationEntry.template.capacity?.save()
        }


        it.acceptedRunnercardDisclaimer = it.waiver.runnercardDisclaimer.disclaimer.content
        it.acceptedEventDisclaimer = it.waiver.eventDisclaimer.disclaimer.content

        it.save()
        
        if(!mp[it.race.id]){
            
            mp[it.race.id] = [it]
            
        }
        else{
            
            mp[it.race.id].add(it)
            
        }
                    
        if(it.errors?.errorCount > 0){
            log.error "error when activating registrations " + it.errors
        }
        
    }
    

    if(invoice.compositeEvent.assignBibNumbers){
    
        BibsAssignerJob.triggerNow([regmap: mp])
    }

        
            
}

This is the Quartz Job for bib assignment.

class BibsAssignerJob {


    static concurrent = false




    def mailService
    def bibsService

    static triggers = {
//      simple repeatInterval: 5000l // execute job once in 5 seconds
    }



    //mp is map of race id -> [registration1, registration2]
    void assignBibNumbers(mp){



            Race.withTransaction {


                try {

                    def bibRangeEventBased = true



                    for (r in mp) {

                        def rac = Race.get(r.key)


                        if (rac.bibsRange) {

                            bibRangeEventBased = false
                            break

                        }

                    }


                    if (!bibRangeEventBased) {

                        //first assign bibs to races with range assignment

                        for (r in mp) {

                            def rac = Race.get(r.key)

                            if (rac.bibsRange) {

                                def regs = r.value


                                def bibs = bibsService.convertRangeStringToRangeList(rac.bibsRange)

                                //def lastBib = bibsService.getLastBibAssignmentInEvent(regs[0].compositeEvent)

                                bibs.removeAll(bibsService.getAllBibsAssigned(rac.compositeEvent))

                                for (int i = 0; i < regs.size(); i++) {

                                    if (i < bibs.size()) {

                                        if (regs[i].compositeEvent.assignBibNumbers) {




                                            def registration = grails.util.Holders.applicationContext.getBean('proxyHandler').unwrapIfProxy(regs[i])

                                            registration.raceParticipant.bibNumber = bibs[i]
                                            registration.markDirty('raceParticipant')
                                            registration.save(flush: true)


                                        }

                                    }

                                }

                            }


                        }


                    } else {


                        //assign bibs based on event range

                        def regs = []

                        for (r in mp) {
                            regs += r.value
                        }

                        def bibs = bibsService.convertRangeStringToRangeList(regs[0].compositeEvent.bibsRange)

                        //def lastBib = bibsService.getLastBibAssignmentInEvent(regs[0].compositeEvent)

                        bibs.removeAll(bibsService.getAllBibsAssigned(regs[0].compositeEvent))

                        for (int i = 0; i < regs.size(); i++) {

                            if (i < bibs.size()) {

                                if (regs[i].compositeEvent.assignBibNumbers) {



                                    def registration = grails.util.Holders.applicationContext.getBean('proxyHandler').unwrapIfProxy(regs[i])

                                    registration.raceParticipant.bibNumber = bibs[i]
                                    registration.markDirty('raceParticipant')
                                    registration.save(flush: true)


                                }


                            }

                        }

                    


                    }


                }
                catch (Exception e) {


                    def regs = mp.values().toList().flatten()

                    def ev = regs.first().compositeEvent.name + "(${regs.first().compositeEvent.id})"
                    def pars = ""

                    for(reg in regs){
                        pars += reg.participant.name
                        pars += ", "
                    }


                    println "************************************************************************************************************************************"
                    println "Bibs assignment failed for ${ev} and participants ${pars}. Exception thrown was ${e}"
                    println "************************************************************************************************************************************"

           



                }

            }




    }



    def execute(context) {
        // execute job

        Thread.sleep(20000)

        println "Running Bib Assigner"

        def mp = context.mergedJobDataMap.regmap


        if (mp.size() == 0){
            return
        }

        assignBibNumbers(mp)

       
    }
}

the error is caught by Exception block in BibAssigner Job

 catch (Exception e) {


                    def regs = mp.values().toList().flatten()

                    def ev = regs.first().compositeEvent.name + "(${regs.first().compositeEvent.id})"
                    def pars = ""

                    for(reg in regs){
                        pars += reg.participant.name
                        pars += ", "
                    }


                    println "************************************************************************************************************************************"
                    println "Bibs assignment failed for ${ev} and participants ${pars}. Exception thrown was ${e}"
                    println "************************************************************************************************************************************"

           



                }

The error is

org.springframework.orm.hibernate5.HibernateSystemException: Illegal attempt to associate a collection with two open sessions. Collection : [com.runnercard.registration.Registration.inventory#382465]; nested exception is org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : [com.runnercard.registration.Registration.inventory#382465]

I appreciate any insights from the Grails experts who have worked with Quartz Jobs on what could be the cause. My guess is it has something to do with concurrency issue. I have tried to recreate this issue but it seems to be intermittent issue. How can i solve this issue? I appreciate any help. Thanks!


Solution

  • You actually have two sessions here. You are sending objects associated with the session in the activateRegistrations method in the context for the trigger. In the BibsAssignerJob you get a new session created specifically for that execution.

    A common way to handle this is to first save the objects in the activateRegistrations method and only enclose a list of the objects id's within the context to BibsAssignerJob.triggerNow. In the BibsAssignerJob you'll then have to load those objects again within that session - Registration.get() - and you will be able to continue work with them as expected.