Package org.curjent.agent

Agent public API.

See:
          Description

Interface Summary
AgentCall<V> Monitor and control an agent call.
AgentConfig Configure an agent.
AgentLoader Creates a generated class.
AgentMark Synthetic mark for tracing the progress of an agent's calls.
AgentStats Statistics for an agent.
AgentTasks Source for agent tasks.
CallSite Metadata and configuration for an agent's method.
CallStateListener<V> Validate, monitor and control call state transitions.
ExceptionHandler Handler for exceptions.
 

Class Summary
Agent Agent creation and configuration.
AgentResult<V> Holder for a future's value.
CustomLoader Custom class loader.
DelegatingLoader Delegates class creation and loading to an existing ClassLoader.
DynamicTasks Source for dynamically created tasks.
FixedTasks Source for a fixed number of tasks.
ReusableTask Source for a task that can be used concurrently by multiple threads.
SingletonTask Source for one task.
 

Enum Summary
CallCompletion Reason codes for why a call finished.
CallState Execution state of a call.
MarkerType Type of Marker.
 

Exception Summary
AgentException Generic agent exception and wrapper for undeclared checked exceptions.
CallSiteNotFoundException Lookup for an agent's call site failed.
CapacityExceededException Number of accepted and/or pending calls has exceeded an agent's configured capacity.
ConfigLockedException Agent's configuration is locked.
DeadlockException Two or more synchronous agent calls have deadlocked.
ExpiredException Call waited too long to begin executing.
 

Annotation Types Summary
Capacity Specifies the maximum number of messages an agent accepts for processing.
Expiration Agent call expirations.
Isolated Specifies that a call should execute by itself.
Leading Specifies that a call should finish executing before any subsequent calls start executing.
Marker Specifies that a call should not start executing until all prior calls have finished executing.
Reentrant Specifies that a synchronous call can be executed within the context of an existing agent call without blocking.
Synchronous Specifies that a call should execute synchronously.
 

Package org.curjent.agent Description

Agent public API. Curjent agents are a concurrency mechanism. Writing to standard Java interfaces, clients of an agent execute commands asynchronously in background threads. The designer of an agent provides one or more interfaces and a class that implements those interfaces. The curjent agent library provides the facilities for running and coordinating client calls concurrently in one or more background threads.

Examples

The quickest way to understand agents and get started is to review the examples on the curjent website.

The following demonstrates how to say "Hello World!" asynchronously using an agent:

    interface World {
        void hello();
    }
 
    class WorldTask {
        void hello() {
            System.out.println("Hello World!");
        }
    }
 
    class WorldTest {
        void run() {
            World world = Agent.newInstance(World.class, new WorldTask());
            world.hello();
        }
    }
 

Concepts

Because calls to agents can behave differently than standard Java calls (e.g., asynchronously), it is helpful to understand not only the public behavior of an agent, but also some of the internal mechanisms. It also helps to have a conceptual model of how agents work when designing and using agents. The following bullet points outline the key agent concepts, including the more salient details of the internal mechanisms. This model is consistent with the Active Object design pattern.

Usage

Specific usage patterns and features are described in the documentation for the agent's public interfaces and classes: Complimentary usage patterns to fork-join. Much research and effort has gone into fine-grained concurrency frameworks based on the fork-join model, such as OpenMP for C/C++ and Fortran. Doug Lea describes a fork-join framework for Java that was used as the basis for extending JSR 166 to include fork-join capabilities in Java 7. The curjent agent library is complementary with these efforts. The fork-join frameworks are tuned for CPU-intensive, fine-grained, divide-and-conquer algorithms. Examples given in Doug Lea's paper include quick sort, matrix multiplication, and the like. This library, on the other hand, enables use of the coarse-grained Active Object design pattern (refer to the Concepts section above for details). Users of an agent write to standard Java interfaces that asynchronously and transparently dispatch work in background threads. Suitable units of work include coarse-grained background operations, I/O-bound operations, and the like. Whereas fork-join encourages massive parallelism to solve a single algorithmic problem as quickly as possible, the curjent agent framework encourages the use of many independent and cooperative active objects that decouple execution dependencies with asynchronous calls while fully utilizing multiple processor cores.

Java-based alternative to concurrent programming languages. Another active area of work is concurrency-friendly programming languages. Erlang was developed in the 1980's for telecommunications and has recently gained popularity for general computing. Scala and Clojure are modern examples built atop the JVM. For Java programmers, the curjent library provides an agent framework for the Java programming language. It works with standard Java interfaces and classes.

Futures

Calls to agent methods can execute asynchronously or synchronously. The default behavior of a method with a void return type is asynchronous. The agent's designer can override this behavior by annotating the task's method with @Synchronous. The default behavior for methods with non-void return types is synchronous. The one exception is methods with Future return types.

A method with a Future return type is executed asynchronously. The caller can use the returned Future object to monitor the call, and to wait and get the eventual result (see Future). When the agent's task executes the call, it returns its result to the agent which the agent makes available to the caller via one of the Future object's get methods.

Of course, the result returned by the task can be of any type, such as an int, a List, or whatever. However, because the Java language does not have built-in support for future values, the task's class will not compile since the interface's method has a Future return type but the task's method has some other return type.

The task designer has two options. One is to not include the agent's interfaces in the task class's implements clause. The primary disadvantage of this option is that method signature problems are reported at runtime when the agent is first created instead of at compile time. The second option is to have the task return a Future wrapper for the actual result. The AgentResult class is provided for this purpose. When the task method's return type is Future, the agent returns the value obtained from the task result's get() method instead of the future object.

If the intent is to truly return a Future result, annotate the task's method with @Synchronous. The agent will return the task's result directly instead of calling and returning the value from its get() method.

An alternative to Future is AgentCall which extends Future with advanced functionality.

Exceptions

Exceptions thrown by an agent's task are handled differently depending on the context. Exceptions from synchronous calls are thrown to the caller just like exceptions from typical non-agent Java method calls. Task exceptions for agent methods returning a Future result are handled as documented for the get methods of the Future interface. Specifically, exceptions thrown by an agent's task are wrapped as the cause in an ExecutionException. More typically, though, calls to an agent are executed asynchronously. The agent cannot propagate task exceptions to the caller of asynchronous methods, so the agent instead calls its handler for unhandled exceptions (see AgentConfig.setUnhandledExceptionHandler(ExceptionHandler) for details).

A task's method is permitted to throw any type of exception. Of course, if the task's class includes the agent's interfaces in its implements clause, the task must follow the Java language rules in order to compile. But if the task does not include the interfaces in its implements clause, a task's method is free to declare any type of exception in its throws clause. This can simplify exception handling in some cases. One example is the common case where the task method is called asynchronously. Because the exception cannot propagate to the caller, an effective solution is to let the handler for unhandled exceptions deal with these exceptions generically for all of the task's methods, such as logging them. If an undeclared checked exception is thrown by a synchronous task method, the agent wraps the exception as the cause in an AgentException. For methods returning a Future, the Future object always wraps exceptions in an ExecutionException, as specified by the Future interface.

Listeners

Agent listeners validate, monitor and control calls. See CallStateListener and AgentCall. Clients can listen for all call state transitions or target more specific transitions. A listener can validate calls during the initial CallState.STARTING state, providing immediate feedback to clients, even for otherwise asynchronous calls. Listeners can also implement cross-cutting functionality, such as debug tracing. A listener can finish a call at any time and set a call's result value or exception.

Synchronization

In the nominal case thread synchronization is transparent to the designer of an agent, as well as to the users of the agent. A stateful task can be written without regard to synchronization. The agent dispatches one call at a time to the task and always from the task's thread. The agent ensures memory consistency of mutable parameters and results. The user's call to the agent and the agent's dispatch of that call to the task adhere to the happens-before semantics discussed in the description for java.util.concurrent.

Memory consistency holds for unsynchronized mutable values if users of the agent and the agent's task adhere to a transfer of ownership protocol. Following this protocol, only one thread can own a mutable object at a time, and only the owner can read and write mutable attributes of the object. For example, a caller can pass an unsynchronized ArrayList as a parameter to an agent. This transfers ownership of the list to the agent. When the agent dispatches the call to the task, the task can safely use and update the list. Furthermore, if the call is synchronous or returns a Future, the completion of the call transfers ownership back to the caller. The caller can then inspect the task's changes.

Deadlock

Curjent agents automatically detect deadlock. Only synchronous calls can deadlock. The default agent behavior is asynchronous, and asynchronous calls cannot deadlock. The agent queues the call and immediately returns control back to the caller. Calls explicitly annotated with Synchronous override this behavior. Calls with non-void and non-Future return types are also implicitly synchronous.

An example deadlock scenario is one agent making a synchronous call on a second agent, and the second agent making a synchronous call on the first. Both agents wait indefinitely for the other to finish.

The practicality of automatically detecting deadlock depends on the context. Java and most programming languages and operating systems do not, whereas relational databases typically do.

Agent usage and design patterns are ideally suited for automatic deadlock detection. No overhead is incurred for asynchronous calls. And even synchronous calls incur no overhead until the wait time exceeds a few seconds. After the minimum wait time, deadlock processing is performed only by the threads that are already waiting. This avoids overhead for the vast majority of agents and agent calls, and leverages multi-core systems for background detection of deadlocks.



Copyright 2009-2011 Tom Landon
Apache License 2.0