Bug 333237 - Enhancement: new special reference variable thisLazyJoinPoint
Summary: Enhancement: new special reference variable thisLazyJoinPoint
Status: NEW
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: unspecified   Edit
Hardware: All All
: P3 enhancement with 6 votes (vote)
Target Milestone: ---   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-12-27 06:32 EST by Marko Umek CLA
Modified: 2011-02-09 12:43 EST (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Marko Umek CLA 2010-12-27 06:32:09 EST
Hello,

there was a discussion about this here: http://aspectj.2085585.n4.nabble.com/Is-there-any-performance-issue-using-thisJoinPoint-td3160915.html

Ok. I'm currently writing an AspectJ based Logging Facade (ajlog: http://code.google.com/p/ajlog/ ). And therefore I need to access thisJoinPoint in some cases and sometimes not. Andy suggested to use if() dynamic point cut, but it would be quite complicated. It's neccessary to call proceed(), so using if() is not useful. Using before and after will force me to use 3 advices and a lot of thread local tricks. And I want to add performance monitoring, so a around advice would be the best.

So my usage would be like this:

// 
// An Usage example of ajlog
//
// The annotations are from ajlog.
//
@DebugOnEntry
@ErrorOnException("Oops an exception occured (${this}, ${1}, ${2})")
@WarningOnSlow("It's awful slow ${sw:duration} ms (${this}, ${1}, ${2})")
int anyMethod(int i1, String s) {
    // Do something and sometimes an exception will happen and this will be
    // logged
    // And if it's to slow also an log entry will be created with severity 
    // level Warning
}

// the advice: logEntryPc is execution point cut.
Object around() : logEntryPc() {
    Object result=null;
    Throwable exception=null;

    executeEntryLogEntries(thisStaticJoinPoint, thisLazyJoinPoint);
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    try {
       result=proceed();
       stopWatch.stop();
       executeReturningLogEntry(thisStaticJoinPoint, thisLazyJoinPoint, 
           result);
    }
    catch(Throwable t) {
       stopWatchStop();
       exception=t;
       executeExceptionLogEntry(thisStaticJoinPoint, thisLazyJoinPoint, 
             exception);
    }
    finally {
       executePerformanceLogEntry(thisStaticJoinPoint, thisLazyJoinPoint, 
                   stopWatch, 
                   result, 
                   exception);
    }
}

My proposal for thisLazyJoinPoint :

public interface LazyJoinPoint {
    JoinPoint bind();
}


I'll hope my example expresses my needs. If you need more information please don't hesitate to contact me. I'll proceed implementing ajlog with the around advice.

Thx Marko

P.S.: A little bit motivation ;-). The ajlog logging facade will finally solve "the logging paradox of AOP" and therefore quite useful for the spread of AspectJ. I call it a paradox, because Logging has been used as one of the primary motivation example for AOP, but there isn't any solution for Logging around. This enhancement will show the power of the AOP solution and also could prove that using AOP is not a performance killer. 

IMHO Logging is THE Killer Application for AOP/AspectJ.
Comment 1 Simone Gianni CLA 2011-01-10 12:54:43 EST
I'd be happy with a lazy way of creating the thisJoinPoint variable, if it is possible without creating an even bigger performance problem : defining a new class for each join point matched by a global tracing aspect can be far more expensive and slower than passing all arguments every time, since it's a simple Object[] array. Moreover, I don't know if this is true also at bytecode level, but arguments have to be final to be accessible from the LazyJoinPoint implementation.

Since the if() pointcut can access stuff from the context, given a static class (say, Logger) that knows if logging is enabled/disabled and at which level for each class, it's possible to write an if() pointcut that activates the around advice only when the matched join point is in a class that has debug logging activated.

I'll try to write it here, without proper testing, just as an example :

Object around(Object t) : execution(* *.*(...)) && this(t)  && if(Logger.getSettings(t).isDebugEnabled()) {
  // do debug logging, using thisJoinPoint etc...
}

I do understand that this means creating more advice, but consider that it's a good rule to have advice only forward actual "real" work to something else, so it's normal to have many "small" advice that calls more complicated methods somewhere else

Moreover, the proceed can be used inside a closure class (which would be a single class loaded), consider the following (again, "proof of concept", not tested verbatim) :

private interface ProceedClosure() {
  public Object doProceed();
}

private Object performLogging(ProceedClosure pc, JoinPoint jp, LogLevel level) {
  if (level.equals(Debug)) {
    // Do debug logging using jp to extract data
  } else if (........) ....
 
  Object ret = pc.doProceed();

  // do logging on exit, performance logging etc....

  return ret;
}


Object around(final Object t) : execution(* *.*(...)) && this(t)  && if(Logger.getSettings(t).isDebugEnabled()) {
  ProceedClosure pc = new ProceedClosure() {
    public Object doProceed() { 
      return proceed(t);
    }
  }
  return performLogging(pc, thisJoinPoint, Debug);
}

Object around(final Object t) : execution(* *.*(...)) && this(t)  && if(Logger.getSettings(t).isInfoEnabled()) {
  ProceedClosure pc = new ProceedClosure() {
    public Object doProceed() { 
      return proceed(t);
    }
  }
  return performLogging(pc, null, Info);
}


Again, I do understand that having a lazy way of populating the thisJoinPoint variable would simplify things a lot, while still keeping performances down, but I'm afraid that generating an internal class implementing LazyJoinPoint for each matched join point is a pain, while using the above method you can do it "the other way around", creating a limited number of classes.
Comment 2 Marko Umek CLA 2011-02-09 12:43:57 EST
Hello Simone,

> 
> 
> Object around(final Object t) : execution(* *.*(...)) && this(t)  &&
> if(Logger.getSettings(t).isDebugEnabled()) {
>   ProceedClosure pc = new ProceedClosure() {
>     public Object doProceed() { 
>       return proceed(t);
>     }
>   }
>   return performLogging(pc, thisJoinPoint, Debug);
> }
> 

The problem with your solutions, that in the moment the AspectJ-Weaver detects an potential(!) access to thisJoinPoint, the weaver does all the stuff which is necessary to create the thisJoinPoint instance, even if within the advice does not really need this information in some occasions. 

> 
> Again, I do understand that having a lazy way of populating the thisJoinPoint
> variable would simplify things a lot, while still keeping performances down,
> but I'm afraid that generating an internal class implementing LazyJoinPoint for
> each matched join point is a pain, while using the above method you can do it
> "the other way around", creating a limited number of classes.

In Groovy for each Closure literal a class will be created by the compiler. And I've never have had any problem with this or heard about it. And for JAVA 8 the concept of closures (Lambdas) will be part of the language specification, so I expect some clever solutions, to handle the potential problem with the increasing amount of implicit class definitions or the avoidance of them.

Regards Marko