Token Ring Election Benchmark

Workers forward tokens around a ring to measure message-passing performance. This historical benchmark demonstrates agent creation, message routing and a Java module shared by multiple agents. Despite its retained title, the code measures token passing and does not elect a leader.

Run the example

Use an ASTRA 2.6.1 project from the Quick Start. Save Worker.astra and Launcher.astra in src/main/astra/benchmarks/tokenring, and Timer.java in src/main/java/benchmarks/tokenring.

The original workload uses 500 workers and 500,000 forwarding steps per token. For an initial experiment, change WT to 5 and N to 100, then enter 1 token. The code is retained from the original guide and has not been validated against ASTRA 2.3.0.

Worker.astra

package benchmarks.tokenring;

agent Worker {
    module Timer timer;

    plan @message(inform, string sender, token(int I, 0)) {
        timer.shutdown();
    }

    plan @message(inform, string sender, token(int I, int N)) : next(int X) {
        send(inform, ""+X, token(I, N-1));
    }

    plan @message(inform, "main", init(int X)) {
          +next(X);
    }
}

Launcher.astra

package benchmarks.tokenring;

agent Launcher {
    module Timer timer;
    module Console console;
    module System system;

    plan +!main(list args) {
        int WT = 500;
        int N = 500000;
        console.println("enter number of tokens: ");
        console.readInt(int T);
        console.println("enter scheduler pool size: ");
        console.readInt(int size);

        // Create the token ring
        int i = 1;
        while (i <= WT) {
            system.createAgent(""+i, "benchmarks.tokenring.Worker");
            send(inform, ""+i, init((i%WT)+1));
            i = i + 1;
        }

        // Start the timer (configuring it for T tokens)
        timer.startup(T);

        // Send out the initial tokens
        i = 1;
        while (i <= T) {
            send(inform, "" + (i * (WT/T)), token(i, N+1));
            i = i + 1;
        }
    }
}

Timer.java

package benchmarks.tokenring;

import astra.core.Module;

public class Timer extends Module {
    private static long startTime;
    private static int T = 0;

    @ACTION
    public boolean startup(int t) {
        startTime = System.currentTimeMillis();
        T = t;
        return true;
    }

    @ACTION
    public boolean shutdown() {
        if (--T == 0) {
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("Duration: " + duration);
            System.exit(0);
        }
        return true;
    }
}

From the project folder, run:

mvn -Dastra.main=benchmarks.tokenring.Launcher

Enter the token count and the requested scheduler pool size when prompted. The current launcher reads the pool size but does not apply it. Use a positive token count no greater than WT that divides WT exactly; the initial destination calculation is unsuitable for other values.

Expected output

The launcher prompts for inputs, then the timer is intended to print a duration in milliseconds and stop the process:

[main] enter number of tokens:
1
[main] enter scheduler pool size:
1
Duration: <elapsed milliseconds>

The duration depends on the machine and workload. This is an illustrative transcript, not a measured result. The shared timer counter also needs synchronization before timings from concurrent token completions can be trusted.

How it works

The launcher names workers 1 through WT and tells each worker its successor, with the last pointing back to 1. It starts a shared timer and sends each initial token to a calculated position in the ring.

A worker receiving a token with a nonzero counter forwards it to its successor with the counter reduced by one. The zero-counter plan ends that token’s journey and calls Timer.shutdown(); its placement before the general forwarding plan is significant.

The timer’s static fields share the start time and outstanding-token count across module instances. When the count reaches zero, it prints the elapsed time and exits. The current unsynchronized decrement can lose updates when tokens finish concurrently.

Try a change

Start with one token and a small ring, then increase the forwarding count. Add a diagnostic message for each token’s completion. Before comparing multiple-token timings, address the timer-counter and launch-configuration issues recorded in BUGS-TO-FIX.md in the guide repository.

See Creating Multiple Agents, Agent Communication and Creating your own Actions.

References

[1] Cardoso, R. C., Zatelli, M.R., Hubner, J.F., Bordini, R. H., “Towards Benchmarking Actor- and Agent-Based Programming Languages”, Proceedings of the 2013 workshop on Programming based on actors, agents, and decentralized control, pp 115-126, ISBN: 978-1-4503-2602-5, 2013