Error on async job
Asked Answered
D

4

10

I'm trying to create an async task that will not block the request. The user make the request, the task will start, and the controller will render "Job is running...", this is for avoid the request being blocked waiting the task to complete. Once the task is finish, it will execute the onComplete and do something with the result of that task (for example call a service that will send a mail to an user)

| Error 2014-09-16 17:38:56,721 [Actor Thread 3] ERROR gpars.LoggingPoolFactory  - Async execution error: null

The code is the following:

package testasync

import static grails.async.Promises.*

class TestController {

  def index() {
    //Create the job
    def job1 = task {
        println 'Waiting 10 seconds'
        Thread.sleep(10000)
        return 'Im done'
    }
    //On error
    job1.onError { Throwable err ->
        println "An error occured ${err.message}"
    }
    //On success
    job1.onComplete { result ->
        println "Promise returned $result"
    }
    render 'Job is running...'
  }

Complete stacktrace:

| Error 2014-09-17 10:35:24,522 [Actor Thread 3] ERROR gpars.LoggingPoolFactory  -  Async execution error: null
Message: null
   Line | Method
 ->>   72 | doCall    in org.grails.async.factory.gpars.GparsPromise$_onError_closure2
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
  |     62 | run       in groovyx.gpars.dataflow.DataCallback$1
  |   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
  |    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
  ^    745 | run . . . in java.lang.Thread
Dameron answered 16/9, 2014 at 20:53 Comment(4)
is there a complete trace?Eutrophic
added the complete stacktraceDameron
I tried your code on grails 3.2.11 and it can run perfectly without error, except if you call your serviceMethod() and working with domain, you might need to Domain.withTransaction() in your service codeEquipment
def yourServiceMethod(){ YourDomain.withTransaction{ YourDomain.column = "new_value" YourDomain.save(flush:true) } }Equipment
D
3

I ended using the executor framework with the grails-executor plugin. I uploaded a very basic example here: https://github.com/agusl88/grails-async-job-queuqe

That code is using a "custom" version of the grails-executor plugin, i merged some PR's from the plugin repo and packaged as jar just for testing propuses. The repo of the plugin is this: https://github.com/basejump/grails-executor

Dameron answered 28/11, 2014 at 13:59 Comment(0)
B
1

I was able to get rid of this exception in a controller by removing the onComplete and onError calls. I guess the exception happens because the parent thread ended when you called render.

So your:

Promise p = task {
    complexAsyncMethodCall(); // (1) do stuff
}
.onComplete { result -> println result } // (2) on success
.onError { Throwable t -> System.err.println("Error: " + t) } // (3) on error

Becomes:

Promise p = task {
    try {
        def result = complexAsyncMethodCall(); // (1) do stuff
        println result // (2) on success
    } catch(Throwable t) {
        System.err.println("Error: " + t) // (3) on error
    }
}

This adds coupling between your work (1) and the result processing (2 and 3) but you could overcome this by writing your own Closure wrapper that takes extra Closures as arguments. Something like this:

// may not work! written off the top of my head
class ProcessableClosure<V> extends Closure<V> {
    Closure<V> work;
    Closure<?> onError;
    Closure<?> onComplete;

    @Override
    public V call(Object... args) {
        try {
            def result = work.call(args); // (1) do stuff
            onComplete.call(result); // (2) on complete
        } catch(Exception e) {
            onError.call(result); // (3) on error
        }
    }
}

That makes your code more readable:

Closure doWork = { complexAsyncMethodCall(); } // (1) do stuff
Closure printResult = { println it } // (2) on complete
Closure logError = { Throwable t -> log.error t } // (3) on error
Closure runEverythingNicely = new ProcessableClosure(work: doWork, onComplete: printResult, onError: logError)
Promise p = task { runEverythingNicely }
Blurb answered 27/8, 2015 at 23:23 Comment(0)
P
0

When creating a Promise async task inside a controller you actually have to return the response by calling the get() method on the task, or the onError and onComplete methods will never be called. Adding:

job1.get()

Before your call to render will resolve the issue.

Pecker answered 16/9, 2014 at 23:36 Comment(3)
But the get() blocks the current request, so the render is not executed until the async task is complete. I want to start the task, end the request (to avoid wait time in the user end), and when the task is over, pick the result with the onComplete.Dameron
@Dameron Have you got any solution to this? I'm facing the same problem with grails 2.4.4Naomanaomi
@ShashankAgrawal i added a response with the solution i found. Hope it helpsDameron
M
0

In my case, just returning a promise worked.

MyService.groovy

import static grails.async.Promises.*
def getAsync(){
    Promise p = task {
    //Long running task
            println 'John doe started digging a hole here.'
            Thread.sleep(2000)
            println 'John doe working......'
            return 'Kudos John Doe!'
        }
        p.onError { Throwable err ->
            println "Poor John"+err
        }
        p.onComplete { result ->
            println "Congrats." +result
        }

        println 'John Doe is doing something here.'
        return p
}
Marilyn answered 1/6, 2020 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.