Simple Example - Calculator Agent

by ondrej on May 20th, 2007

Now, we have enough knowledge to create a simple example: a calculator.

The example shows how to:

  • Create an agent.
  • Locate the blackboard.
  • Write a tuple (a calculator operation).
  • Take a tuple (a calculator operation result).
  • Differentiate between different operations (plus and minus).
  • Handle a simple ID.

The calculator agent will be able to handle two kinds of operations: plus and minus. It will receive an operation request tuple, where one item will be an operation identifier (to handle two operations) and two items with numerical values (to transmit values).
The agent will produce a result tuple, where one item will be a numerical value (to transmit back a result value).
To track a result, each operation request tuple will have an ID, that will be inserted back into a result tuple.

To sum up, there will be two tuple types:

  • operation request [:calculator, id, operation, a, b], e.g.
    [:calculator, id, :plus, 1, 2] or [:calculator, id, :minus, 6, 2].

  • result [:result, id, value], e.g.
    [:result, id, 4].

To run this example, we will need three files: rinda.rb to start the environment, calculator.rb the calculator agent and client.rb to send requests and display results.

The calculator agent source code, calculator.rb:

require 'rinda/ring'
require 'rinda/tuplespace'

# A simple calculator example.
class SimpleCalculator

  def plus(a, b)
    a + b

  def minus(a, b)
    a - b

  def main
    ts = Rinda::RingFinger.primary

    loop do
      t = ts.take [:calculator, nil, nil, nil, nil] # take an operation request

      # assign values
      id = t[1]
      operation = t[2]
      arg1 = t[3]
      arg2 = t[4]

      puts "Calculator recived operation ##{id} #{operation} with arguments #{arg1} and #{arg2}"

      res = operation == 'plus' ? plus(arg1, arg2) : minus(arg1, arg2) # perform the received operation
      #sleep 1 # to pretend that an operation takes longer

      ts.write [:result, id, res] # send a result back

And the client source code, client.rb:

require 'rinda/ring'


ts = Rinda::RingFinger.primary

t1 = #the start time

# sending operation requests

id = 1 # the ID
for i in (1..5) # plus operations
  puts "Sending operation ##{id} plus with arguments #{i} and 1"
  ts.write [:calculator, id, :plus, i, 1]
  id += 1 # an unique ID value
for i in (5..9) # minus operations
  puts "Sending operation ##{id} minus with arguments #{i} and 1"
  ts.write [:calculator, id, :minus, i, 1]
  id += 1 # an unique ID value

puts "Waiting for results..."

# receiving results

i = 1
loop do
  t = ts.take [:result, nil, nil], 2
  puts "Result ##{t[1]} = #{t[2]}"
  i += 1
  break if i > 10 # 10 request were sent

t2 = # the end time
puts "The operation took #{t2 - t1} seconds."

See a screencast Simple calculator agent screencast [.ogg]

To start the example, firstly start the environment ruby rinda.rb and then start the calculator agent ruby calculator.rb. Each execution of the client, ruby client.rb, will produce 10 operation requests that will be processed by the agent. The agent will produce 10 results, that will be read by the client.

There is a lot of log messages, so you can easily see when an agent instance received a request, when it send a result and when a client send a request and received a result. With the #id part of a log message you can track particular communication parts.

Execution time should be very short, approx. 0.06 seconds.

Let’s imagine that an operation processing would take longer, e.g. 1 second. Uncomment the 32nd line in the agent source code calculator.rb and restart all parts of the example (rinda.rb and calculator.rb).
Execute the client and the execution time will be, as expected, 10.06 seconds.

And now, to show power of a parallel processing in a multi-agent system, start another calculator agent…

The logs of the started calculator agent instances show that the processing load was nicely shared between the agents.

The result? A half of time (of course), approx. 5.04 seconds.

I like it :)

Files to download:

  1. Nice and concise demonstration. Works out of the box. Great playground for beginner’s experiments with rinda.Thx

  2. david permalink

    hey guys! nice project!

  3. Tilo permalink

    the problem with the Blackboard approach is that it is a communication bottleneck and a single point of failure in what is supposedly a parallel approach — if the communication with the blackboard breaks or it gets too much traffic, everything breaks!

  4. hi Tilo,
    sure, you are right.
    however, this is a usual problem - that same could be said about databases, mq servers, etc.

    you may find additional functionality for widely used solutions that is minimizing the risks that you mentioned (e.g. a replication of databases instances).

    but probably you will not find anything for the ruby blackboard rinda to make it fail-over and/or scalable - but it is not because the blackboard is a bad concept or idea, it is about missing additional tools, that would help you to start more instances, that would replicate the actual state over the started instances, would be able to detect a failed instance, propagate that event and handle it somehow…

    there are several tuple space implementations, some of them even commercial, that provide some features in this area, e.g. gigaspaces, but it is for java and .net.

    rinda, a ruby blackboard implementation, is ok. with general monitoring tools, like monit, you will get a nicely working solution. but if you need more, probably you should check another implementations.


Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS