« December 2004 | Main | March 2005 »
January 23, 2005
Making concurrency a little bit easier
It's been hard to miss the discussion on concurrency and concurrent programming provoked
by Herb Sutter's excellent article, "The Free Lunch is Over: A
Fundamental Turn Towards Concurrency in Software". I've spent long enough
building distributed and concurrent systems (about a decade of industry experience) to
know that concurrency is hard and that I still don't know enough about it. Doug Lea's
contribution of the java.util.concurrent
library in Java 5 is a huge step
forward for Java programmers. With my AspectJ hat on though, I can't help thinking that
there's still more we could do...
Here's the fundamental observation: at one level, concurrency is about getting multiple objects to coordinate together at distinct points in the runtime execution of a program. There are a variety of multi-object protocols for the differing concurrency patterns. Furthermore, it's generally unlikely that this coordination is the primary concern of the modules in which it happens. So we have a multi-object protocol, somewhat distinct from the modules it is entangled in, and which relies on coordinating on events in the runtime execution of a program. A language that lets you encapsulate multi-object protocols, separate them from other modules, and that provides a first-class representation of events in the runtime execution of a program (we'll call them join points), sounds an excellent basis for writting concurrent applications. Even better if that language were to support pointcuts, giving the programmer the expressive power of abstraction and composition in selecting join points. Enter AspectJ :). Conceptually at least, it's a great fit to the problem. Of course, this is not a novel thought, way, way back on of the early predecessors to AspectJ was a language called COOL designed to assist in the development of concurrent object-oriented programs. What is new though, is the unique combination of factors that make the timing right :- the growing acceptance of aspect-oriented programming and of AspectJ, the increasing need for concurrency, the availability of Doug Lea's libraries in the Java 5 platform. The planets, as they say, seem to be aligning.
Now, I don't believe you can use an aspect-oriented programming language and just make
all the hard problems simply go away. I do believe though that a library of aspects for
concurrency, built on top of the java.util.concurrent
libraries, could take
us the next few steps and encapsulate more of the common usage patterns. This would
hopefully save programmer mistakes and "make the code look more like the design".
Anything that makes a concurrent design more readily apparent has got to be good.
I managed to snatch half an hour this afternoon to play with this idea just a little bit. Here's a simple program we can use as the basis for some concurrency experiments:
public class SpeedMeUp { private int[] myInts = new int[100]; public static void main(String[] args) { SpeedMeUp please = new SpeedMeUp(); please.doVoidLoop(); please.doIntLoop(); please.printSum(); } private void doVoidLoop() { for(int i = 0; i < 100; i++) { goFaster(); } System.out.println(); } private void doIntLoop() { for(int i = 0; i < 100; i++) { myInts[i] = goEvenFaster(i); } System.out.println(); } private void printSum() { int sum = 0; for (int i : myInts) sum += i; System.out.println("Sum = " + sum); } private void goFaster() { try { Thread.sleep(500); } catch (InterruptedException intEx) {} System.out.print("."); } private int goEvenFaster(int i) { try { Thread.sleep(500); } catch (InterruptedException intEx) {} System.out.print("."); return i; } } aspect Timing { pointcut timedEvent() : execution(* do*Loop*(..)); Object around() : timedEvent() { long start = System.currentTimeMillis(); Object ret = proceed(); long duration = System.currentTimeMillis() - start; System.out.println(thisJoinPoint + " took " + duration + " ms."); return ret; } }
If I compile and run this program as is, I see the output:
.................................................................................................... execution(void SpeedMeUp.doVoidLoop()) took 50072 ms. .................................................................................................... execution(void SpeedMeUp.doIntLoop()) took 50072 ms. Sum = 4950
Let's start with the easy loop to parallelize: doVoidLoop
. We can speed that up no end with a single
annotation:
@ParallelExecution private void goFaster() { try { Thread.sleep(500); } catch (InterruptedException intEx) {} System.out.print("."); }
With this annotation in place (and the Concurrency aspect we'll see next), the first loop now completes in 40ms instead of 5000. This support is very like the @Oneway tag supported by JBoss amongst others, but I choose to call it ParallelExecution because of my emphasis on concurrency here and because I want to extend the support to work with Futures shortly.
Here is the Concurrency aspect needed to support this basic tag (and yes, everything in this post is source compiled and working as-described with the latest AspectJ 5 development builds).
public aspect Concurrency { private ExecutorService executor = Executors.newCachedThreadPool(); public void shutdown() { executor.shutdown(); } pointcut voidParallelExecutionJoinPoint() : execution(@ParallelExecution void *.*(..)); pointcut futureParallelExecutionJoinPoint() : execution(@ParallelExecution Future *.*(..)); pointcut unsupportedParallelExecutionJoinPoint() : execution(@ParallelExecution * *.*(..)) && !voidParallelExecutionJoinPoint() && !futureParallelExecutionJoinPoint(); declare warning : unsupportedParallelExecutionJoinPoint() : "Methods scheduled for parallel execution must return either " + "void or a Future."; void around() : voidParallelExecutionJoinPoint() { executor.execute(new Runnable() { public void run() { proceed(); } }); return; } Future around() : futureParallelExecutionJoinPoint() { Future result = proceed(); if (result instanceof Runnable) { executor.execute((Runnable)result); } return result; } }
To get my application to terminate correctly, I added one more little aspect into the mix:
aspect ControlledShutdown { after() returning : execution(static void main(String[])) { Concurrency.aspectOf().shutdown(); } }
There are other more elegant techniques for this, but I'm only hacking to get a quick prototype up and running....
So that's the easy one dealt with, concurrent execution of a method with a void return type. Note that even in this case though, you can't just blindly use the annotation and hope things work out. If the method has side effects that need to complete before subsequent work happens in the thread you're going to need some redesign. I'm interested in making it easier to focus on the important issues in concurrency, and hiding some of the noise - not waving a magic wand.
Wouldn't it be great if you could do the same trick with the second loop, and just add the annotation to the
goEvenFaster
method too. If we try this:
@ParallelExecution private int goEvenFaster(int i) { try { Thread.sleep(500); } catch (InterruptedException intEx) {} System.out.print("."); return i; }
Then the AspectJ compiler will produce the following warning:
SpeedMeUp.java:59 [warning] Methods scheduled for parallel execution must return either void or a Future. @ParallelExecution private int goEvenFaster(int i) { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method-execution(int SpeedMeUp.goEvenFaster(int)) see also: Concurrency.aj:24::0 1 warning
Don't you just love design-level assertions :) (Take a look at the declare warning statement in the Concurrency aspect if you can't figure out why this message is coming out.
If a method returns a value, and we need it, we can't just kick of an
asynchronous piece of work and never see it again, we need some way to get the return value. Java 5 supports this with
the notion of a Future
. I won't go into how they work here, suffice to say that a Future
is like
a promise that although you don't have the answer right now, you'll be able to get it when you need it.
What's the minimum we have to do to our program to get parallel execution of the second loop then? The warning message
(and the source of the Concurrency aspect) should give you a clue - we need to change the goEvenFaster
method to return a Future
. The implications of this switch on the client are significant enough that I can't
immediately see how a simple aspect could hide them, and it's not clear that it should even if it could.
Here's a second version of the goEvenFaster
method that returns a future:
@ParallelExecution private Future<Integer> goEvenFaster2(final int i) { return new FutureTask<Integer>( new Callable<Integer>() { public Integer call() { return goEvenFaster(i); } } ); }
We need to update the loop method too to work with this. Here we see what I mean about not being able to hide this concurrency from the client. Compare the first and second versions of the doIntLoop method:
private void doIntLoop() { for(int i = 0; i < 100; i++) { myInts[i] = goEvenFaster(i); } System.out.println(); } private void doIntLoop2() { List<Future<Integer>> myFutures = new ArrayList<Future<Integer>>(); for(int i = 0; i < 100; i++) { myFutures.add(goEvenFaster2(i)); } for(int i = 0; i < 100; i++) { Future<Integer> future = myFutures.get(i); try { myInts[i] = future.get(); } catch (InterruptedException intEx) { System.err.println("It's rude to interrupt..."); } catch (ExecutionException exeEx) { System.err.println(exeEx.getCause()); } } }
The control structure has had to change significantly since we can't store the return values directly in the myInts array as we go along (this would entail waiting for each call to complete, and we don't want to do that. Instead there are two passes: the first kicks off all of the computations, and the second fills in all of the return values. (And yes, there are other designs you could use here, like passing the goEvenFaster2 method an index in the myInts array to fill-in once its computed the value - I'm just doing a bit of quick hacking here....).
Take a look at the Concurrency aspect to see how it schedules the Future-returning computations for execution using around advice. This version computes the sum in 530ms rather than 5000ms. I'm sure we could go further. Working with a Future inside the client is quite cumbersome for example. I'm pretty sure I'd be adding:
declare soft: InterruptedException : call(* Future.get()); declare soft: ExecutionException : call(* Future.get());
to my aspect, coupled with some afterThrowing advice to handle the exception cases after not too long. That would at least reduce some of the noise in doIntLoop2, getting it back to:
private void doIntLoop2() { List<Future<Integer>> myFutures = new ArrayList<Future<Integer>>(); for(int i = 0; i < 100; i++) { myFutures.add(goEvenFaster2(i)); } for(int i = 0; i < 100; i++) { Future<Integer> future = myFutures.get(i); myInts[i] = future.get(); } }
As you can see, I'm only just scratching the surface of the kinds of things that might be possible. A really good aspect library for concurrency would make a great project for someone to work on. We'll ship it with AspectJ as soon as its ready :)
Posted by adrian at 10:21 PM [permalink] | Comments (5)
January 21, 2005
Even more AspectJ goodies...
It's been a good week for AspectJ. We posted the first developer build to include full Java 5 compilation support, and finally managed to announce that the AspectWerkz team would be joining with us to work on AspectJ 5 together. I also got some more good news in the post yesterday and on the AspectJ mailing list today.
Copies of Russ Miles' new book, the AspectJ Cookbook, and Ivar Jacobsen and Pan-Wei Ng's book, Aspect-Oriented Software Development with Use Cases both arrived at my door yesterday. Obviously I haven't had time to read them both yet, but I like what I see on a quick flick-through.
Starting with the cookbook, it looks to be very complementary to the other AspectJ books out there (including our own). You won't find an in-depth overview of the language features, instead you get a bunch of really practical tips and ideas you can use straight-away. A quick peek at the contents of chapter 3 should give you an idea of the difference:
- 3.1 Deploying a Command-line AspectJ Application
- 3.2 Deploying an AspectJ Application as a Fully Contained Executable JAR File
- 3.3 Deploying a Java Servlet that uses AspectJ
- 3.4 Deploying a JSP that uses AspectJ
- 3.5 Deploying an Axis Web Service that uses AspectJ
You get the idea. The book also includes AspectJ implementations of 22 design patterns. Can't wait to get stuck in!
The use cases book looks great because it's the first time anyone has tackled in book form the question of how aspect-oriented ideas can fit in with design methodologies. The code examples in the book all use AspectJ, but the ideas apply to implementation in any aspect-oriented language. For those of you who've not seen any of Ivar's papers on the topic, the basic idea is that a use case is a single conceptual entity in the design, and yet the typical implementation of a use case ends up scattered across many parts of a system. Step forward aspects to enable modular use-case implementation. The book uses a single real-world example of a hotel management information system to demonstrate how all the ideas can be used in practice. I expect this book, and others like it, to spearhead the move of AOP into the areas of AOD and ultimately AOA. it's a great contribution to the field.
If two new books isn't enough for you, Gerard Davison has also announced the release of a new version of the AspectJ plugin for JDeveloper. There's a great streaming demo on the site that shows the tools in action - check it out! Gerard says future versions will focus on the integration of AspectJ 5 - cool :)
There's just a fantastic community surrounding AspectJ and AOP in general at the moment. A huge thank-you to everyone involved, collectively we can really make a difference...
Posted by adrian at 06:42 PM [permalink] | Comments (0)
The war against blog spam
Like many others running blogs on movabletype I've been hit by a huge increase in spam commenters recently. I had to switch on comment moderating a long time ago to stop unsavoury comments appearing on the site. Now all comments are moderated but I've been getting hundreds of spam comments to moderate (on-line poker seems especially popular for some reason - could be worse I guess). The result is that i'm sure I've deleted a few genuine comments in amongst all the noise - if that's happened to you please accept my apologies.
Today I've taken a few more steps in the war against spam and upgraded my MovableType installation, and installed the nofollow and MTBlacklist plugins. If only it was as simple as deploying an aspect into the system:
aspect SpamRemoval { after(Comment c) returning : spamCommentArrival(c) { c.delete(); } }
Maybe one day I'll be able to script this easily....
Posted by adrian at 06:31 PM [permalink] | Comments (3)
January 19, 2005
AspectJ launches campaign for world peace ;)
I hope by now you've seen the news that AspectJ and AspectWerkz are joining forces to collaborate on the development of Aspectj 5. In addition to the details on the AspectJ website, there's an interview at TheServerSide by Dion Almaer with quotes from Gregor Kiczales, myself, Alexandre Vasseur and Jonas Boner.
One of my favourite things that comes out in reading the posts on that TSS thread, is just how unusual it is for open source projects to decide to work together (as opposed to project splits and wars). As some posters have suggested, perhaps this could be the start of a new open source peace movement ;). What are your favourite suggestions for OSS mergers??
BTW, if you're looking for some of the meat behind the announcement, check out the updated AspectJ 5 Developer's Notebook. I've uploaded two new chapters covering the load-time weaving enhancements and the annotation-based development style.
Posted by adrian at 06:30 PM [permalink] | Comments (0)
January 17, 2005
AspectJ 5 now supports full source compilation of Java 5 programs
At last, all changes checked in and a healthy build report.
The latest AspectJ 5 development build (available from the download page at http://www.eclipse.org/aspectj now supports full source compilation of Java 5 programs. I've included below the source code for a simple aspect that exercises many of the new Java 5 features. If you cut-and-paste this into a file "SimpleAspect.aj" and then compile it with the latest AspectJ build:
ajc -1.5 SimpleAspect.aj
You'll see the output:
execution(void C.whatCsDoBest()) has an annotation. Varargs match: Autoboxing match Varargs match: java.lang.Object@1fee6fc java.lang.Object@1eed786 Covariant match on execution(C whoAmI()) Covariant match on execution(C whoAmI()) Covariant match on execution(D whoAmI())
(you need to look at the source of the program to make sense of this). Have fun reading through the sample and seeing how many Java 5 features you can spot!
Oh, and for those of you who were wondering about AJDT, Andy and Matt just sent me this screenshot. It shows AspectJ 5 integrated in Eclipse 3.1 M4 with full Java 5 support, eager parsing, and early error indications all working happily.
Here's the source for SimpleAspect.aj:
import java.util.List; @MyAnnotation public aspect SimpleAspect { int x; static List<String> myStringList; public static void main(String[] args) { for(String s:args) { System.out.println(s); myStringList.add(s); } C c = new C(); c.whatCsDoBest(); c.somethingElse(); c.myInt(5); c.somethingElse(new Object(), new Object()); c.whoAmI(); D d = new D(); d.whoAmI(); } pointcut annotatedExecution() : execution(@MyAnnotation * *(..)); before() : annotatedExecution() { System.out.println(thisJoinPoint + " has an annotation."); } before() : call(* myInt(..)) && args(int) { System.out.println("Autoboxing match"); } before(Object[] objects) : call(* somethingElse(Object...)) && args(objects) { System.out.print("Varargs match: "); for (Object o:objects) { System.out.print(o + " "); } System.out.println(); } before() : execution(C whoAmI()) { System.out.println("Covariant match on execution(C whoAmI())"); } before() : execution(D whoAmI()) { System.out.println("Covariant match on execution(D whoAmI())"); } } class C { @MyAnnotation public void whatCsDoBest() { } public void somethingElse(Object... objects) { } public C whoAmI() { return this; } Progress getAspectJ5Progress() { return Progress.EXCELLENT; } public int myInt(Integer i) { return i; } } class D extends C { public D whoAmI() { return this; } } @interface MyAnnotation {} enum Progress { OK, GOOD, VERYGOOD, EXCELLENT }
Posted by adrian at 06:57 PM [permalink] | Comments (0)
January 16, 2005
1427/1427
The glorious green bar.
A great day for AspectJ.
(see yesterday's entry if you have no idea what I'm on about).
Posted by adrian at 05:12 PM [permalink] | Comments (2)
January 15, 2005
Progress on AspectJ 5
I realised I haven't said anything about the progress we're making on AspectJ 5, and more specifically on the integration of the full Java 5 compiler from Eclipse that became available late last year.
I started work on the compiler integration on Thursday 6th January. It's been interesting to explore the Java compiler and see all the changes that have gone in to support the new Java 5 features. I explained the basic process we go through to integrate a new release of the Eclipse compiler in a previous blog entry. This time of course things are a little more challenging as there are lots of new features to get to grips with.
Some of you will understand how happy I was when I finally got the parser generator to report:
This grammar is LALR(1).
Things have been going well. On my laptop I have a version of AspectJ that can happily compile any Java 5 program. You can also use Java 5 features in aspects (parameterised types, annotations, enums, new for loops etc..). The hardest part of course is the interaction of AspectJ features with Java 5 features - for example, many of the new features in Java 5 change the resolution process for determining which method is the target of a call. These changes interact with the changes we make to support privileged aspects and inter-type declarations too and some rework has been needed.
Right now, I have 1421 out of 1427 tests passing. Once the last 6 bugs are nailed I'll do a big commit and then there'll be a developer build available for those who want to take the new features out for a spin. That build will represent a very significant milestone on the road to the AspectJ 5 release.
Posted by adrian at 04:26 PM [permalink] | Comments (0)
Little languages
In my last post I wrote that I had been dipping into the book "Structure and Interpretation of Computer Programs", and I observed that I'd built far more abstractions and interfaces than I had descriptive languages (described on the back cover as the three major techniques for controlling the complexity of large software systems).
Probably because this put back into my mind the possibility of writing a little language to help with a task, I found the need to create one a couple of weeks ago. The problem was the test cases for a feature I added to AspectJ in the run-up to Christmas. The feature implements pointcut rewriting for maximum efficiency of matching. As a simple example, if you write a pointcut
pointcut foo() : execution(* Foo+.*(..)) && within(org.xyz.*);
Then AspectJ's weaver (in the AspectJ 5 codebase) will rewrite it to:
pointcut foo() : within(org.xyz.*) && execution(* Foo+.*(..));
It does this because matching of within pcds is faster than matching of execution pcds, so by testing the within first, we can fail fast if it returns false. The rewriting rules are quite interesting in themselves and maybe I'll blog about them one day too. For now, lets press on.
Obviously, I wrote a whole bunch of test cases that made sure that rewritten pointcuts had the correct form. The problem is that I was lazy in writing those orignal tests, and used Pointcut.toString() as the basis for pointcut comparisons in some of the test cases. This works well when the rewriting gives a defined order for the rewritten expression (as in the example above), but turns out not to work so reliably when the rewriting comes across two terms with equal precedence. For example, given a pointcut "this(Foo) && this(Goo) && this(Boo)" the three terms can legitimately come in any order and the rewriting would be considered correct.
On my laptop, and on my linux box, and on Andy's laptop, by a quirk of implementation, the rewriting algorithm always returns the pointcut expression rewritten in the same order and I could get away with a toString comparison. On the build machine (typical), once in a while, the order would come out different and a test would fail. After a few random failed builds we'd had enough of this and I set about to fix it.
The brute force way to rewrite those tests would be to start walking the Pointcut AST produced by the rewriting, checking at each node for the elements we expect, and being tolerant of ordering where it didn't matter. There are quite a lot of tests, and this approach would have involved writing a lot of tedious code. So what I did instead is to invent a little language for specifying what the rewritten pointcut should be. To make life easy it uses RPN (reverse polish notation) for specifying pointcut expressions. The test cases now just call assertEquals(Pointcut pointcut, String spec) in place of the previous toString comparison. For the first example I gave above, the specification would be:
and within(org.xyz.*) execution(* Foo+.*(..))
The little language supports one extra keyword, "anyorder". The spec for the "this" example could be written:
or anyorder this(Foo) or anyorder this(Boo) this(Goo)
To parse the little language and verify a pointcut is just two small java methods. To make it extra easy to tokenize, I use % as the token delimiter since spaces can legitimately appear in pcds. So the actual test spec for the above example would be:
or%anyorder%this(Foo)%or%anyorder%this(Boo)%this(Goo)
It doesn't have to be pretty... it's just a tool to get a job done - and a very effective one at that. It saved me a lot of time, code, and effort.
Posted by adrian at 12:38 PM [permalink] | Comments (0)