Actors Guild

Saturday, January 24, 2009

AG 0.6's New Property-based Initialization

The most important change in the next Actors Guild version will be a complete rewrite of the initialization mechanism. So far initialization in AG imitated Java's constructors. There was a special message with an @Initializer annotation which was guaranteed to be always the first message to be executed in an actor. I never really liked this approach, but it was my first attempt.

My main grieve with the old @Initializer was that it did not work very well in a Dependency Injection environment. First of all, while AG 0.5 allowed writing properties (independent of the @Initializer), this would have been very error-prone. Typical Java property implementations, like the ones automatically generated by Eclipse, are not thread-safe. This is a problem, because values that you set during the initialization of an actor are not guaranteed to be visible in the threads that run the actor's messages. Secondly, it was not possible to set immutable final fields in the initializer, since it ran after the object's real constructor.

So for Actors Guild 0.6 I decided to take a completely different approach that's completely based on properties. One way to use it is to define standard Java properties in your actor class and then pass values for them to Agent.create(), which is AG's way of creating new actors. Let's say you have the following actor:
class Multiplicator {
private int m;

public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}

@Message
public AsyncResult multiply(int value) {
return result(getM() * value);
}
}

Then you can create a new instance with m=5 like this:
Agent agent = new DefaultAgent();
Mulitplicator mul = agent.create(Multiplicator.class, new Props("m", 5));

Even though the property accessors in Multiplicator are not thread-safe, it is guaranteed that any value set by Agent.create() is visible in the message implementations.

However, that would not be true if you had any other actors changing the m property by calling setM() from their threads. To be able to do this safely, the properties need to be synchronized. You could achieve this by adding a synchronized modifier to getM() and setM(). The easier way is to use AG's new property generation feature. Using the @Prop annotation you can let AG implement the accessors for you. Just implement abstract property accessors and annotate the getter with @Prop:
abstract class Multiplicator2 {
@Prop
public abstract int getM();
public abstract void setM(int m);

@Message
public AsyncResult multiply(int value) {
return result(getM() * value);
}
}

...
Mulitplicator2 mul = agent.create(Multiplicator2.class, new Props("m", 5));

@Prop generated methods are thread-safe by default. Thus you can also call them while the actor is already running (they will block while a message is being processed though).

To create a read-only property, just omit the setter. The generated code will create a final field to store the value. Read-only properties can only be set using Agent.create:
abstract class Multiplicator3 {
@Prop
public abstract int getM();

@Message
public AsyncResult multiply(int value) {
return result(getM() * value);
}
}

...
Mulitplicator3 mul = agent.create(Multiplicator3.class, new Props("m", 5));

So far it has been assumed that you always provide values for the properties in Agent.create(). In case you shouldn't, the property will be initialized to its default value (0 for numbers, false for boolean, null for references). If this is not a useful default value, you can declare a better one with a static final
field and the new @DefaultValue annotation. The following example defines a default value of 10 for the Multiplicator actor:
abstract class Multiplicator4 {
@DefaultValue("m") static final int DEFAULT_M = 10;

@Prop abstract int getM();

@Message
public AsyncResult multiply(int value) {
return result(getM() * value);
}
}

...
Mulitplicator4 mul = agent.create(Multiplicator4.class, new Props("m", 5));

Sometimes a default value is not enough, and you want to forbid certain values. Then you can use the @Initializer annotation to create an initializer method. Let's forbid negative values for m:
abstract class Multiplicator5 {
@DefaultValue("m") static final int DEFAULT_M = 10;

@Prop abstract int getM();

@Initializer
void init() {
if (m < 0)
throw new RuntimeException("Property m can not be negative!");
}

@Message
public AsyncResult multiply(int value) {
return result(getM() * value);
}
}

...
Mulitplicator5 mul = agent.create(Multiplicator5.class, new Props("m", 5));

The initializer can be used for other initialization work as well. Everything that is written in the initializer is always guaranteed to be visible in messages of the actor.


The @Prop, @DefaultValue and @Initializer annotations proved to be so useful that I also wanted to use them in regular beans. So I added a fourth annotation, @Bean, to declare a class as bean with auto-generated properties. @Bean has a mandatory parameter threadSafe which specifies whether the generated accessor methods for read-write properties will be synchronized or not (it does not have any effect on read-only properties which are always thread-safe). Here are two simple bean classes to represent points:
@Bean(threadSafe=true)
abstract class ImmutablePoint3D {
@Prop abstract double getX();
@Prop abstract double getY();
@Prop abstract double getZ();
}

@Bean(threadSafe=false)
abstract class MutablePoint3D {
@Prop abstract double getX();
abstract void setX(double x);

@Prop abstract double getY();
abstract void setY(double y);

@Prop abstract double getZ();
abstract void setZ(double z);
}

Like actors, @Bean annotated classes must be created using Agent.create:
DefaultAgent agent = new DefaultAgent();
ImmutablePoint3D p = agent.create(ImmutablePoint3D.class, new Props("x", 5.5).add("y", 10).add("z", -54.3));
MutablePoint3D p = agent.create(MutablePoint3D.class, new Props("x", 5.5).add("y", 10).add("z", -54.3));

Property-based initialization with @Prop, @DefaultValue, @Initializer and @Bean is already in SVN and will be the main feature of the Actors Guild Framework version 0.6. I hope to release it in the next week or two.