|
[aop]
Semantics for a Synchronized Block Join Point
[
jonas
]
As I mentioned in my previous blog entry, the synchronized
block is currently not a supported join point in AspectJ 5 (or in any other AOP framework):
Currently you can for example pick out a call to a method that is declared as being synchronized and you can pick out calls to Thread:: notify()/notifyAll()/wait(). Meaning that being able to pick out synchronized blocks are the only missing piece left in order to completely control thread management and locking in Java. The actual bytecode modifications needed to make this work would be fairly simple, but capturing the correct semantics in a good language design would probably be a lot trickier. Well, I'm not a language designer, but I think the problem is interesting so I will spend some time discussing it anyway. In bytecode, a synchronized block is represented as a MONITOR ENTRY and a MONITOR EXIT bytecode instruction pair (however these are not required to be paired). The first natural approach would to let these two bytecode instructions be join points and treat them similar to field access and modification (PUTFIELD and GETFIELD bytecode instructions), meaning simply pick out (and intercept) this single bytecode instruction. Just to clarify what I mean, here is an example of how the syntax for the above given semantics could look in the AspectJ pointcut expression language: lock(Type t) && withincode(* Foo.bar(..)) && args(t) unlock(Type t) && withincode(* Foo.bar(..)) && args(t) Let us take a look at a synchronized block and how it would be affected: synchronized(obj) { // body } In bytecode to this is equivalent to (pseudo code): MONITOR ENTRY // lock on obj // body MONITOR EXIT If we now add around advices to these join points then we could for example get (pseude code): try { // a call to the around advice, which calls the lock manager aroundAdvice1(obj) --> myLockManager.acquire(obj); // body } finally { aroundAdvice2(obj) --> myLockManager.release(obj); } This approach would give you the possibility to completely control how locking is done in Java (including the possibility of enhancing or completely screw up the Java Memory Model (JMM)). On the other hand it does not allow you to pick out the actual code block that is synchronized (is this something that we want?) . Therefore this approach is perhaps not intuitive, since in Java source code, what we see is not lock acquisition and release but a code block that is guaranteed to be synchronized. So now let's try to approach this problem from the perspective of source code. Since AspectJ (and AspectWerkz) already has limited support for the synchronized keyword by allowing calls to methods declared as being synchronized to be matched, let us take a look at the semantics for this join point. Here's a simple method that is defined as being synchronized: public synchronized void doStuff() { // body } What this actually means is: public void doStuff() { synchronized(this) { // body } } The options we have of picking up this synchronized code block (wrapped up in the doStuff() method) is by using a execution(synchronized void *.doStuff())or call(synchronized void *.doStuff())pointcut. Using the execution pointcut, the above method body would be transformed to (pseudo code): synchronized(this) { // the advice has option of invoking the original body myAroundAdvice(this) } I.e. only synchronized body is matched and intercepted. If we are using the call pointcut, the same code snippet would be transformed to (pseudo code): ... // the advice has the option of invoking the original synchronized block myAroundAdvice(this) ... I.e. the whole synchronized block, including the locking is matched (and intercepted). (This is conceptionally true, in regards to the locking, which is easy to see if you think of the doStuff() method as being inlined.)
I will not go into much detail about how these semantic differences should be expressed in the pointcut language here (this post is too long already). But for example, the last discussion would require the possibility of making a distinction between picking out a code block // pick out synchronized block including the locking block-inclusive(synchronized(Type t)) && withincode(* Foo.bar(..)) && args(t) // pick out only the synchronized block's body, not the locking block-exclusive(synchronized(Type t)) && withincode(* Foo.bar(..)) && args(t) (These "block" pointcut descriptors can of course be used in any kind of block, loops, conditional statements etc., if/whenever they are supported in AspectJ.) To sum up, the questions are: Do we want the power of intercepting the whole locking mechanism (but not the synchronized body), or is it better to follow the semantics we have for a synchronized method? Which approach addresses the use cases we want? Is the most intuitive? Is more orthogonal? Thoughts? Comments? Ideas? I think such a discussion must be driven by concrete uses cases. Here are some: * time to acquire the lock : need an advice just before the lock is acquired and one (possibly the same is there are sort of a proceed concept) just after it is acquired * time spend with the lock (ownership) : need an advice just before (or after) the lock is released * lock implementation : bypass the JMM lock and use something else - possibly with conditional logic such as " if (t is distributed) { getADistributedLock(); proceedWithoutJMMLock(); releaseDistributedLockFinally(); } else { proceedWithJMMLock(); (or possibly proceed() and let the user add is own enclosing JMM synchronized(t) {proceed();} } How each of those would be implemented by the proposed semantics ? --Alex, July 18, 2005 02:39 PM
It would be nice to be able to use annotation for variables and blocks inside of the method code. Unfortunately first case only supported on a source level and second case is not supported at all, which leave very little options to the runtime. Monitorenter/exit should work on to some extend and for more complicated cases one could use hack with try/finally block as I've siggested earlier. http://jroller.com/page/eu/20050608#expressing_pointcuts_within_method_code --eu, July 18, 2005 04:37 PM
Couple of nits: 1) Though the monitor enter/exit don't have to be paired in the bytecode you will find that JVMs will puke on them not being paired with the same method. Also, the last time I checked many JVMs will puke if they are interleaved, eg: monitor1.enter() Even though that construction would be very useful for navigating trees of information. 2) The bytecode for synchronized methods vs. a method enclosed enitrely within a synchronized block are actually different and will go through different code paths in the JVM. My suggestion is the you have 2 new pointcuts, monitorEnter and monitorExit that you can apply before/after/around advice to. Basically treat them as method calls whose target is the object that is being used as a monitor. --Sam Pullara, July 18, 2005 07:38 PM
> 2) The bytecode for synchronized methods vs. a method enclosed enitrely within a synchronized Right, this is the reason why call(synchronized *.*(..)) and execution(synchronized *.*(..)) are semantically different in regards to the locking (since the locking is not seen as part of the method body). But it is conceptually equal to locking on 'this' (and was stated there only to drive the discussion). > My suggestion is the you have 2 new pointcuts, monitorEnter and monitorExit that you can apply I wanted to stay neutral, at least in the beginning of this discussion, but this (the 1:st solution in the post) is actually the one I prefer (probably the only one that makes sense). By the way, it is the only solution that addresses all use cases that Alex defined in his comment. Thanks all for your comments. --Jonas, July 18, 2005 08:26 PM
By the way, it would be coll to add a support for named block is AspectJ compiler, so you would be able to access information aqbout method slicing in the runtime. The syntax could be like this: @SomeAnnotation(...) {...} @SomeAnnotation(...) synchronized(...) {...} for(...) @SomeAnnotation(...) {...} if() @SomeAnnotation(...) {...} try So, this can actually go into a custom attribute that would have start and end offset in the method bytecode (similar to try/catch) and then value of the annotation for this block. Perhaps such atribute can even make into Dolphin... --eu, July 19, 2005 07:21 PM
Today I was actually swearing about why the JSR-175 spec does not support annotated blocks...(only one out of a long list of shortcomings). It feels like such a natural thing and would open up many more possibities. I really like your proposal, but it should not end up in the AJ compiler but in the real spec. --Jonas, July 19, 2005 09:20 PM
Hmmm this is a nice proposal (which has been proposed many times before :-) ). Unfortunately you will run into problems with interleavind monitorenter/exit statements (see above), which *cannot* be disambiduated statically. So you *would* need runtime instrumentation for this! There is no general way around. W.r.t. the "block" pointcut, I think that a) it would be a huge overkill (why not extract a method if I want a semantic block?) and also some blocks are agaibn not statically defined by the bytecode, so for instance exception handlers, which have no defined end... --Eric Bodden, July 20, 2005 12:12 PM
Jonas, don't see why it can't be actually prototyped in AJ compiler... :-) Anyway, I've started a thread in Mustand discussion forum. Please leave a comment or two out there. http://forums.java.net/jive/thread.jspa?threadID=988&tstart=0 By the way, Eric, with this functionality on a Java code level you should not have issues with an interleaved blocks because compiler will generate a proper ranges. I disagree about the overkill. Because all annotated blocks are visible on a method level it is actually more lightweigh option then extracting methods. It would also allow to add e.g. onException adviced only for annotated exeption blocks, which is not that easy to do with currently available pointcut definitions (especially if you have two nested exception handles on the same exception type). --eu, July 20, 2005 04:52 PM
Hello, However, I think there might be a solution for a large number of cases. 2/ Finding the monitorenter instruction, and the block immediatly after (it should always be the case, since it's the beginning of a trap) 3/ There should be (I guess) a couple of blocks (one "normal" and one handler for the finally/monitorexit) that "post-dominate between them" the monitorenter block (i.e. it is compulsory to go through one of them before returning from the method). 4/ The subgraph between the monitorenter instruction, and the "post-dominating" monitorexits would then represent the synchonized block. (This would be a similar approach to the potential if-then-else join point I've suggested at the end of the paper on the join point for loops presented in FOAL 2005. I would not encourage an if-then-else join point, though.)
If you take this simple program: public void run() { Here is its jimple representation, based on the bytecode generated by Eclipse JDT (see Soot: )
r0 := @this: Test; label0: label1: label2: catch java.lang.Throwable from label0 to label1 with label1;
This would require further investigation, and perhaps a prototype implementation (in abc for example). --Bruno Harbulot, August 2, 2005 02:57 PM
Post a comment
|