Skip to content

Agents & Idle Strategies

In 2006, Edward A. Lee wrote a tech report titled "The Problem with Threads". In the tech report, he had the following to say:

Threads, as a model of computation, are wildly nondeterministic, and the job of the programmer becomes one of pruning that nondeterminism. Although many research techniques improve the model by offering more effective pruning, I argue that this is approaching the problem backwards. Rather than pruning nondeterminism, we should build from essentially deterministic, composable components. Nondeterminism should be explicitly and judiciously introduced where needed, rather than removed where not needed.

Agrona Agents and Idle Strategies are one way to achieve what he proposes. Agrona Agents – when used alongside Aeron – allow for deterministic, resource managed threads to be built safely, and in a consistent manner that is easy for the developer to reason about.

Agents

Agrona Agents are containers for application logic that execute in a duty cycle, such as processing messages from an Aeron subscription. Agent duty cycle intervals and by extension their CPU consumption are controlled by idle strategies. An Agent can be scheduled on a dedicated thread, or it can be run as part of a composite group of agents on a single thread.

A typical duty cycle will poll the doWork function of an agent until it returns zero. Once the zero is returned, the idle strategy will be called.

Below is a sample agent, showing the duty cycle. The duty cycle will return an integer value in this case from the combined value from dutyCyclePart1() and and dutyCyclePart2(). With a typical idle strategy, if the combined value is greater than or larger than zero, it will be returned immediately to execute the next duty cycle. If the value returned is zero, then the idle strategy will execute the selected back off.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public final class Sample implements Agent
{
    ...
    public int doWork()
    {
        int workCount = 0;
        workCount += dutyCyclePart1();
        workCount += dutyCyclePart2();
        return workCount;
    }
    ...
}

Here's the key part of the Yielding Idle Strategy, showing the key logic where the workCount value is checked. If workCount is zero, the agent backs off.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public final class YieldingIdleStrategy implements IdleStrategy 
{
    ...
    public void idle(int workCount) {
        if (workCount <= 0) {
            Thread.yield();
        }
    }
    ...
}

These Agent and IdleStrategy then come together in the AgentRunner as follows:

public class AgentRunner implements Runnable, AutoCloseable
{
...
    public void run()
    {
    ...
        while (isRunning)
        {
            doDutyCycle(idleStrategy, agent);
        }
    ...
    }

    private void doDutyCycle(final IdleStrategy idleStrategy, final Agent agent)
    {
    ...
        final int workCount = agent.doWork();
        idleStrategy.idle(workCount);
    ...
    }
...
}

Idle Strategies

Agrona provides a range of idle strategies, however, it is easy to implement a custom one if needed.

Name Implementation Details
SleepingIdleStrategy Uses parkNanos to park the thread for the given time period
SleepingMillisIdleStrategy Uses thread.sleep to idle the thread for the given time period. Good for using when developing locally on a lower spec machine, or with a large number of processes.
YieldingIdleStrategy Uses thread.yield to yield control of the thread
BackoffIdleStrategy An aggressive strategy that backs off from spinning to yielding and then to parking for a configurable period of nanos. This is the default strategy for Aeron Cluster.
NoOpIdleStrategy The most aggressive idle strategy available. This never idles.
BusySpinIdleStrategy This will call java.lang.Thread.onSpinWait() if available on the running JVM i.e. the JVM is running Java 9+. This provides a weak hint to the CPU that the thread is in a tight loop but busy waiting for something, and the CPU may then assign additional resources to another thread without involving the OS scheduler.

If you want to implement a custom idle strategy, you will need to implement the IdleStrategy interface, which is:

1
2
3
4
5
public interface IdleStrategy {
    void idle(int count);
    void idle();
    void reset();
}

Scheduling Agents

To start the agent duty cycle, you will need to decide on how it should be scheduled.

  • You can have an agent run on a thread supplied by Agrona;
  • You can supply a thread factory to run your agent on;
  • You can construct a CompositeAgent out of a collection of agents that allows you to then schedule them as a single unit.

Warning

Not all idle strategies are thread safe. It is recommended to generally have a distinct idle strategy per agent that is scheduled.

Once you've decided how to execute the agent, you can start it using an Agent Runner. An example, with the idleStrategy set to one of the idle strategies above, and an agent. Error handler is typically a method accepting a string value and captures errors raised. Error Counter is used to count the errors raised by this agent. The usage of AgentRunner.startOnThread tells Agrona to schedule the agent on a new thread.

1
2
3
4
5
6
final AgentRunner runner = new AgentRunner
                        (idleStrategy,
                            errorHandler,
                            errorCounter,
                            agent);
AgentRunner.startOnThread(runner);

See also

Full IPC Example