(Quick Reference)

5.4 Active Objects - Reference Documentation

Authors: The Whole GPars Gang

Version: 1.0.0-rc1

5.4 Active Objects

Active objects provide an OO facade on top of actors, allowing you to avoid dealing directly with the actor machinery, having to match messages, wait for results and send replies.

Actors with a friendly facade

import groovyx.gpars.activeobject.ActiveObject
import groovyx.gpars.activeobject.ActiveMethod

@ActiveObject class Decryptor { @ActiveMethod def decrypt(String encryptedText) { return encryptedText.reverse() }

@ActiveMethod def decrypt(Integer encryptedNumber) { return -1*encryptedNumber + 142 } }

final Decryptor decryptor = new Decryptor() def part1 = decryptor.decrypt(' noitcA ni yvoorG') def part2 = decryptor.decrypt(140) def part3 = decryptor.decrypt('noitide dn')

print part1.get() print part2.get() println part3.get()

You mark active objects with the @ActiveObject annotation. This will ensure a hidden actor instance is created for each instance of your class. Now you can mark methods with the @ActiveMethod annotation indicating that you want the method to be invoked asynchronously by the target object's internal actor. An optional boolean blocking parameter to the @ActiveMethod annotation specifies, whether the caller should block until a result is available or whether instead the caller should only receive a promise for a future result in a form of a DataflowVariable and so the caller is not blocked waiting.

By default, all active methods are set to be non-blocking . However, methods, which declare their return type explicitly, must be configured as blocking, otherwise the compiler will report an error. Only def , void and DataflowVariable are allowed return types for non-blocking methods.

Under the covers, GPars will translate your method call to a message being sent to the internal actor . The actor will eventually handle that message by invoking the desired method on behalf of the caller and once finished a reply will be sent back to the caller. Non-blocking methods return promises for results, aka DataflowVariables .

But blocking means we're not really asynchronous, are we?

Indeed, if you mark your active methods as blocking , the caller will be blocked waiting for the result, just like when doing normal plain method invocation. All we've achieved is being thread-safe inside the Active object from concurrent access. Something the synchronized keyword could give you as well. So it is the non-blocking methods that should drive your decision towards using active objects. Blocking methods will then provide the usual synchronous semantics yet give the consistency guarantees across concurrent method invocations. The blocking methods are then still very useful when used in combination with non-blocking ones.

import groovyx.gpars.activeobject.ActiveMethod
import groovyx.gpars.activeobject.ActiveObject
import groovyx.gpars.dataflow.DataflowVariable

@ActiveObject class Decryptor { @ActiveMethod(blocking=true) String decrypt(String encryptedText) { encryptedText.reverse() }

@ActiveMethod(blocking=true) Integer decrypt(Integer encryptedNumber) { -1*encryptedNumber + 142 } }

final Decryptor decryptor = new Decryptor() print decryptor.decrypt(' noitcA ni yvoorG') print decryptor.decrypt(140) println decryptor.decrypt('noitide dn')

Non-blocking semantics

Now calling the non-blocking active method will return as soon as the actor has been sent a message. The caller is now allowed to do whatever he likes, while the actor is taking care of the calculation. The state of the calculation can be polled using the bound property on the promise. Calling the get() method on the returned promise will block the caller until a value is available. The call to get() will eventually return a value or throw an exception, depending on the outcome of the actual calculation.

The get() method has also a variant with a timeout parameter, if you want to avoid the risk of waiting indefinitely.

Annotation rules

There are a few rules to follow when annotating your objects:

  1. The ActiveMethod annotations are only accepted in classes annotated as ActiveObject
  2. Only instance (non-static) methods can be annotated as ActiveMethod
  3. You can override active methods with non-active ones and vice versa
  4. Subclasses of active objects can declare additional active methods, provided they are themselves annotated as ActiveObject
  5. Combining concurrent use of active and non-active methods may result in race conditions. Ideally design your active objects as completely encapsulated classes with all non-private methods marked as active

Inheritance

The @ActiveObject annotation can appear on any class in an inheritance hierarchy. The actor field will only be created in top-most annotated class in the hierarchy, the subclasses will reuse the field.

import groovyx.gpars.activeobject.ActiveObject
import groovyx.gpars.activeobject.ActiveMethod
import groovyx.gpars.dataflow.DataflowVariable

@ActiveObject class A { @ActiveMethod def fooA(value) { … } }

class B extends A { }

@ActiveObject class C extends B { @ActiveMethod def fooC(value1, value2) { … } }

In our example the actor field will be generated into class A . Class C has to be annotated with @ActiveObject since it holds the @ActiveMethod annotation on method fooC() , while class B does not need the annotation, since none of its methods is active.

Groups

Just like actors can be grouped around thread pools, active objects can be configured to use threads from particular parallel groups.

@ActiveObject("group1")
class MyActiveObject {
    …
}

The value parameter to the @ActiveObject annotation specifies a name of parallel group to bind the internal actor to. Only threads from the specified group will be used to run internal actors of instances of the class. The groups, however, need to be created and registered prior to creation of any of the active object instances belonging to that group. If not specified explicitly, an active object will use the default actor group - Actors.defaultActorPGroup .

final DefaultPGroup group = new DefaultPGroup(10)
ActiveObjectRegistry.instance.register("group1", group)

Alternative names for the internal actor

You will probably only rarely run into name collisions with the default name for the active object's internal actor field. May you need to change the default name internalActiveObjectActor , use the actorName parameter to the @ActiveObject annotation.

@ActiveObject(actorName = "alternativeActorName")
class MyActiveObject {
    …
}

Alternative names for internal actors as well as their desired groups cannot be overriden in subclasses. Make sure you only specify these values in the top-most active objects in your inheritance hierarchy. Obviously, the top most active object is still allowed to subclass other classes, just none of the predecessors must be an active object.