SGT Grumpy Pants
SGT Grumpy Pants

Reputation: 4436

GORM/Hibernate Managed Object doesn't "exist" inside Promise

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.

ClassifierService

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

}

FaceDataClassifierService

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

}

Output

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

--- Update ---

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.

ClassifierService (workaround)

    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

Answers (0)

Related Questions