Sunday, September 11, 2011

Asynchronous Method calls with Groovy: @Async AST

At work, I needed to create a very simple background job, without any concern about what I could get back, because mostly all the hard work was just batch processing and persistence, and all exceptions or roll-back concerns were already taking care of.

At the beginning I used a very simple way to call my background job, using: Executors.newSingleThreadExecutor()

 void myBackgroundJob() {
    Executors.newSingleThreadExecutor().submit(new Runnable() {               
                        public void run() {
                        //My Background Job
                    }
                });
  }

And it worked great, just what I needed.

Using Groovy facilitate even more the way to create a new Background job, as simple as:

def myBackgroundJob() {
    Thread.start {
        //My Background Job
    }
}

Then, after this simple way to send something into the background, I decided to create a new AST in groovy, that remove the need to remember or copy and paste the same logic.

I created two annotations that help to identify the class and the methods that are going to be put into a new Thread.

One for the Class:

package async

import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.*
import xml.ToXmlTransformation


@Retention (RetentionPolicy.SOURCE)
@Target ([ElementType.TYPE])
@GroovyASTTransformationClass (["async.AsyncTransformation"])
public @interface Asynchronous { }

And the other for the Method:


package async

import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.*
import async.AsyncTransformation

@Retention (RetentionPolicy.SOURCE)
@Target ([ElementType.METHOD])
@GroovyASTTransformationClass (["async.AsyncTransformation"])
public @interface Async { }


then the Asynchronous Transformation, using the AstBuilder().buildFromString(). Here I combined a GroovyInterceptable to connect the method being call with the AST transformation to wrapped it with the Thread logic.




package async

import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.transform.*
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.builder.AstBuilder

import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.ConstantExpression

import org.codehaus.groovy.ast.stmt.BlockStatement

import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.expr.ArgumentListExpression

@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class AsyncTransformation implements ASTTransformation{

    void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
        if (!astNodes ) return
        if (!astNodes[0] || !astNodes[1]) return
        if (!(astNodes[0] instanceof AnnotationNode)) return
        if (astNodes[0].classNode?.name != Asynchronous.class.name) return

        def methods = makeMethods(astNodes[1])
        if(methods){
            astNodes[1]?.interfaces = [  ClassHelper.make(GroovyInterceptable, false), ] as ClassNode []
            astNodes[1]?.addMethod(methods?.find { it.name == 'invokeMethod' })
        }
    }

    def makeMethods(ClassNode source){
         def methods = source.methods
         def annotatedMethods = methods.findAll {  it?.annotations?.findAll { it?.classNode?.name == Async.class.name } }

         if(annotatedMethods){
             def expression = annotatedMethods.collect { "name == \"${it.name}\"" }.join(" || ")
             def ast = new AstBuilder().buildFromString(CompilePhase.INSTRUCTION_SELECTION, false, """
                package ${source.packageName}

                class ${source.nameWithoutPackage} implements GroovyInterceptable {

                    def invokeMethod(String name, Object args){

                        if(${expression}){
                            Thread.start{
                                def calledMethod = ${source.nameWithoutPackage}.metaClass.getMetaMethod(name, args)
                                calledMethod?.invoke(this, args)
                            }
                        }else{
                           def calledMethod = ${source.nameWithoutPackage}.metaClass.getMetaMethod(name, args)?.invoke(this,args)
                        }
                    }

                }
             """)

             ast[1].methods
         }
    }

}



The example:

package async

@Asynchronous
class Sample{

    String name
    String phone

    @Async
    def expensiveMethod(){
        println "[${Thread.currentThread()}] Started expensiveMethod"
        sleep 15000
        println "[${Thread.currentThread()}] Finished expensiveMethod..."
    }

    @Async
    def otherMethod(){
        println "[${Thread.currentThread()}] Started otherMethod"
        sleep 5000
        println "[${Thread.currentThread()}] Finished otherMethod"
    }
}

println "[${Thread.currentThread()}] Start"
def sample = new Sample(name:"AST Sample",phone:"1800-GROOVY")
sample.expensiveMethod()
sample.otherMethod()
println "[${Thread.currentThread()}] Finished"


Final Notes:

As you can see on the example  I need to have the Asynchronous annotation on the class still. It could be better without it and just annotate the methods, something like the Groovy's SynchronizedASTTransformation. If you have any idea to complement this small example, please clone the source code [here], and let me know what you think.

I could used the @javax.ejb.Asynchronous or the Spring's @org.springframework.scheduling.annotation.Async, but I only needed a very simple solution without any other configuration or library inclusion.


The remain logic here could be play more with multi threading and expect some results like: java.util.concurrent.Future and its java.util.concurrent.Future.get() method or maybe integrated with another frameworks like Spring.

Source Code: [here]

5 comments:

  1. Good stuff. There's no need for two annotations though - you can specify the target as being class or method:

    @Target([ElementType.METHOD, ElementType.TYPE])

    ReplyDelete
  2. Those secretive phone calls may be completely innocent - or they could mean your partner is cheating. It could all be a series of coincidences, but it's sure looking suspicious. You innocently walk into the room while he's on his cell phone, and suddenly he startles and hangs up, or he begins mumbling so words so low you can't understand before snapping the phone closed. Either that or she adopts an overly-casual nonchalance and actually raises her voice as if she's wrapping up a free business calls and hangs up, again, as soon as she can get that phone closed.

    ReplyDelete
  3. The number of golf GPS devices and the number of apps for smartphones is growing quickly. It is very difficult to keep track as developers try to carve out their niche in the market. As a golfer who is looking for one of these devices, you can be overwhelmed by the number of choices available to you. Here are some of the pros and cons of using a smartphone app. Source

    ReplyDelete
  4. Today world is driven by technology, and retail industry is catching up with all the new technologies to keep up their sales and customer satisfaction. There is no stopping now. Sky is the limit. mobile tracker free

    ReplyDelete
  5. Mobile application development, though an emerging market has witnessed major changes in the past year. The year 2012 has been a watershed year for the mobile market in multiple ways with the number of smart phone shipments increasing at a very rapid pace during the year. Following are some of the leading mobile application development trends of 2012. singapore best mobile app services

    ReplyDelete