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!
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.