Reputation: 4436
I'm having trouble with transactionality in Grails when using promises, I have the following services, which should be running inside the same transaction. Earlier in this transaction a Photo
was created. ClassifierService
calls FaceDataClassifierService
inside a promise, but the Photo
which exists in the Hibernate Session in the ClassifierService
does not exist in Hibernate inside the FaceDataClassifierService
.
import grails.async.Promise
import grails.gorm.transactions.Transactional
import static grails.async.Promises.task
import static grails.async.Promises.waitAll
@Transactional
class ClassifierService {
FaceDataClassifierService faceDataClassifierService
FaceDataService faceDataService
def classify(Photo photo) {
List promises = []
log.error("$photo exists: ${Photo.countById(photo.id) == 1}")
findAllByActive(true).each { Classifier classifier ->
promises << classify(photo, classifier)
}
List<Classification> classifications = waitAll(promises)?.flatten()
classifications.each { save(it) }
}
Promise classify(Photo photo, Classifier classifier) {
log.debug("Classifying photo $photo.id with Face Data classifier $classifier.name")
log.error("$photo exists in promise: ${Photo.countById(photo.id) == 1}")
FaceData faceData = faceDataService.findByPhoto(photo, true)
log.debug("Found face date ${faceData?.id} for ${faceData?.photo}")
return task { faceDataClassifierService.classify(photo) }
}
\\ Unnecessary code omitted
}
import grails.gorm.transactions.Transactional
@Transactional
class FaceDataClassifierService {
FaceDataService faceDataService
List<Classification> classify(Photo photo) {
log.error("$photo exists: ${Photo.countById(photo.id) == 1}")
\\ Unnecessary code omitted
FaceData faceData = faceDataService.findByPhoto(photo, true)
log.debug("Photo $photo.id has face data: ${faceData?.id}")
if (!faceData) return []
\\ Unnecessary code omitted
}
\\ Unnecessary code omitted
}
ClassifierService : Photo(1427248, ENROUTE) exists: true
ClassifierService : Classifying photo 1427248 with Face Data classifier Face Data
ClassifierService : Photo(1427248, ENROUTE) exists in promise: true
FaceDataService : Photo(1427248, ENROUTE) exists in findByPhoto: true
ClassifierService : Found face data 3406 for Photo(1427246, DISCARDED)
FaceDataClassifierService : Photo(1427248, ENROUTE) exists: false
FaceDataService : Photo(1427248, ENROUTE) exists in findByPhoto: false
FaceDataClassifierService : Photo 1427248 has face data: null
Moving the call to faceDataClassifierService
outside of a promise is our current workaround for the problem but limits our ability to benefit from asynchronous execution.
def classify(Photo photo) {
List promises = []
log.error("$photo exists: ${Photo.countById(photo.id) == 1}")
findAllByActive(true).each { Classifier classifier ->
photo.bytes = photoService.fetchBytes(photo)
promises << classify(photo, classifier)
}
List<Classification> classifications = faceDataClassifierService.classify(photo)
log.debug("Classified $photo with ${classifications.size()} face data classifications")
classifications += waitAll(promises)?.flatten()
log.debug("Classified $photo with ${classifications.size()} classifications")
classifications.each { save(it) }
}
Additionally, I was able to rewrite the faceDataClassifierService
so that it didn't do any DB lookups or saves, and that worked inside of a promise, but it created an ugly
tangling of concerns.
Upvotes: 2
Views: 22