[AOP]
Synchronized block join points (v2)
[ avasseur ] 13:30, Monday, 10 October 2005
Back in July Jonas suggested some semantics for a synchronized block join point. The discussion was interesting but I am wondering if we actually came up with the right question.

What if we simply think in terms of "is this type listening to locking events ?" instead of trying to come up with a pointcut expression that defines the semantics of a synchronized block - as done in Jonas discusion?

Lets draft some code:
Consider the program:

class Bar {
   synchronized void foo() {
      ..
    }
 
   static synchronized void statfoo() {
      ..
    }
 
   void stuff() {
       ..
       synchronized(bar) {
          ..
        }
    }
}
So the deal is to listen to lock events - mainly:
  • waiting the lock
  • just acquired the lock
  • just released the lock (or perhaps about to release it - but this does not matter much for this discussion)

I argue that instead of defining semantics, the issue should be narrowed to a type pattern ie something as simple as Bar (or more fancy variations based on type hierarchy or annotation - you bet it).

Given that we can define this interface:

interface Lockable {
   void waiting();
   void acquired();
   void released();
}

As a user you can then implement this interface on your own types, use some sort of AOP or proxy to have it introduced where you want, etc.

So what should happen to make that work?

First based on the type pattern, we simply introduce Lockable to Bar. This can be done manually, by the mean of AOP if f.e. I was writing @Distributed class Bar { .. } and so on - depends on the use case.

Second the interface must then be implemented by Bar. The user can provide one if implemented manually, or the system (AOP f.e.) can provide an empty one ie something like (depends on the AOP system but you get the idea)

@Introduce("Bar")
class LockableNOOP implements Lockable {
   void waiting() {}
   void acquired() {}
   void released() {}
}

Last we need to transform the program so that the synchronized block or the Bar' synchronized method are triggering event to that. This is not that hard since we just need to look at the type hierarchy for non static synchronized method and given that an instance synchronized method can be rewritten (by the system) as a synchronized(this) block we end up to something like that:
Replace all synchronized blocks like that:
Given

synchronized(stuff) {
   ..
}
to
if (stuff instanceof Lockable) {
   Lockable lockable = (Lockable)stuff;
   lockable.waiting();
   synchronized(stuff) {
      lockable.acquired();
      ..// unchanged body
    }
   lockable.released();
} else {
   synchronized(stuff) {
      ..// unchanged body
    }
}
Fairly easy.

So far, nothing will happen, as we have a LockableNOOP implementation, unless the user (you) explicitly provided some implementation.

Last part: configure the system so that it advises Lockable' methods, so that you can add your behavior there (f.e. distribute lock, or trace them for some application performance system). There, usual AOP stuff can be used.
You can now access the enclosing static join point information if you need - which gives you if you really need it, the rich semantics Jonas was looking for: call(Lockable.waiting()) && withincode(....) && target(t) && cflow(...) ..... // t is the locked object

There are two interesting things to solve now:

  • What if I want to kick out the synchronized() block ?(f.e. for distributed lock). I think a small variation of Lockable can solve that - f.e. boolean shouldUseJavaLocks(), and a tiny change for the transformed program.
  • What about the static synchronized statfoo() {..} in Bar? For that one, the transformation would have to be a bit more compex so that we f.e. lock on an introduced static field. That said, the Lockable interface is not handy anymore. Any suggestion for that one? Should we define three methods like static void lockable_waiting() that ones would implement or introduce to adress this use case?

On a more high level perspective, this send us back to a recurrent set of problems in the AOP / bytecode transformation space:

  • to achieve API transparency, we have a very intrusive transformation engine (though the one described here can be quite fast)
  • by changing the bytecode we break the visibility another system may require to work properly (as f.e. we add if blocks, change synchronized method into synchronized blocks etc).

There is thus a set of open questions:

  • So should that belongs to the JVM directly, and in which form? (as f.e. JVMTI already provides monitor entry / exit events, that should be fairly easy).
  • Should those 3 Lockable' methods (or 6 if we include the static versions) belong to java.lang.Object direclty?
  • Would an hybrid system that changes the synchronize blocks using bytecode transformation but then relies on JVM level support for AOP such as we do in JRockit be enough to listen for lock events?
  • Is the cost of the introduced instanceof acceptable?

Happy thinking!

Comments

I'll try to provide inputs from the point of view of a user who wants to use this feature.
I'm interested in using synchronized join points for Application Performance Management (APM). In this context, I need a very simple way to do the following:
(1) Find out how much time is spent waiting for getting locks in synchronized methods/blocks of code
(2) Find out how much time is spent executing the synchronized block after getting the lock - [long time in synchronized blocks could be an indication of bad design]

So, the join points are (a) just before getting the lock (i.e. entering the monitor) (b) just after getting the lock and (c) just after releasing the lock (i.e. exiting the monitor)

I'd like to have a pointcut language that can express this in a simple way. And I need to be able to use them with existing pointcut expression such as cflows (and other such things) so that I can define pointcuts for synchronized blocks only for specific executions of code. (e.g., I'd like to know how much time was spent waiting for monitors within the Hibernate library).

What happens at these pointcuts (calculating time) can be expressed as simple advices.

For implementation choices, while it would be nice to leverage the existing features of the JVM such as JVMTI monitor entry/exit/wait events, whats the performance overhead of it? For e.g., I should be able to subscribe only to very specific monitor events (such as say those occurring from within certain methods) and not at all monitor events. If I subscribe to all monitor events in JVMTI and filter out the unnecessary events in my event handler, will be there a lot of overhead? If so, will a byte code instrumentation approach be faster for such use cases? Also, we need ways to do this for pre 1.5 versions of the JVM where JVMTI is not available (JVMPI had similar features, but it was never known to be fast).

To answer some other specific questions, for the use case of APM for enterprise apps, use of instanceof is not a big cost. While its better to have as little change to the original program structure as possible, I can live with some of the implementation details mentioned here such as using introductions, transforming code with if conditions etc.

Hope that helps!

--Srinivas Narayanan, October 11, 2005 05:47 PM

Are fighting yesterday's battles? Sure, synchronized join points are interesting, but with the advent of java.util.concurrent, I sincerely hope that in 2 years serious multi-threaded Java program will NOT be using synchronized. And as I understand it, java.util.concurrent methods are natural to capture with existing join points.

I'd be interested in opinions on that...

--Ron Bodkin, October 26, 2005 08:24 AM
Post a comment









Remember personal info?