Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Hibernate in background threads in grails

I am trying to create a specific type of background processing setup in a grails application.

  • A fixed size thread pool exists only for the duration of the batch of jobs
  • A single session is maintained by each thread
  • Each job runs in a separate transaction

I am trying to start the job as follows:

int poolSize = 10
ThreadFactory factory = new MyThreadFactory (Executors.defaultThreadFactory())
ExecutorService pool = Executors.newFixedThreadPool (poolSize, factory)

(1..100).each { i ->
  pool.submit {
    try {
      MyDomainClass.withTransaction {
        doSomeWork(i)
      }
    } catch (Exception e) {
      log.error "error in job ${i}", e
    }
  }
}

MyThreadFactory creates threads which have a hibernate session attached for the duration of the thread.

class MyThreadFactory implements ThreadFactory {

  ThreadFactory delegate
  PersistenceContextInterceptor persistenceInterceptor

  MyThreadFactory (ThreadFactory delegate) {
    this.delegate = delegate
    ApplicationContext applicationContext = ApplicationHolder.getApplication().getMainContext()
    persistenceInterceptor = applicationContext.getBean("persistenceInterceptor");
  }

  Thread newThread (Runnable work) {
    return delegate.newThread {
      persistenceInterceptor.init()
      try {
        work.run()
      } finally {
        persistenceInterceptor.flush()
        persistenceInterceptor.destroy()
      }
    }
  }
}

It seems to work, however I will get the following error the first time I run the batch job. (Subsequent jobs run without incident)

groovy.lang.MissingMethodException: No signature of method: static MyDomainClass.save() is applicable for argument types: (java.util.LinkedHashMap) values: [[flush:false]]
Possible solutions: save(), save(java.util.Map), save(java.lang.Boolean), wait(), any(), wait(long)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at ...

I have tried replacing the persitanceInterceptor with MyDomainClass.withNewSession {}, with no effect.

It appears as though GORM methods are not being injected into my domain classes.

Can anyone see what I'm doing wrong, and why running the batch job again allows it to succeed?

@fixitagain For completeness the work takes this form:

 doSomeWork = { id ->
    MyDomainClass a = MyDomainClass.findById (id)
    a.value = lotsOfWork()
    a.save()
 }

I believe the missing save is a red herring, as I tried wrapping the operating in a transaction, and then get an error saying 'DomainClass.withTransaction(Closure)' is not defined.

It looks like there might be a race condition where the first job fails to run, but all following jobs run successfully after (something?) has finished starting up.

like image 651
Akusete Avatar asked Nov 14 '22 18:11

Akusete


1 Answers

Instead of trying to create your own thread, it might be advisable to use the executor plugin for Grails. It injects the necessary hibernate session to the threads you create, also it is configurable with respect to the executor it uses, number of threads etc. I use it in production with quartz jobs and other scenarios and it works just fine.

Grails Executor Plugin If you have reservations in using it, you can take a look at its code before you write your own threading strategy.

like image 196
Abe Avatar answered Jan 28 '23 06:01

Abe