Archive for December, 2007

Pattern: Shutdown Flag

Problem: An agent should consider a shutdown flag presence.

Solution: The solution is to use a modified Boolean Flag Pattern, where the flag specific operation is to break the loop for reading an n-tuple, i.e. the Agent Life Cycle pattern.

(life cycle, boolean flag and shutdown flag patterns)

loop do
  begin
    read or take n-tuple with timeout t1
  rescue Rinda::RequestExpiredError

    begin
      read the shutdown flag with timeout t2
      break # stop listening for the n-tuple
    rescue Rinda::RequestExpiredError
      # ignore a situation when there is no shutdown flag
    end

    next # repeat the loop

  end
  process n-tuple
end

Example:

(life cycle, boolean flag and shutdown flag patterns)

loop do
  begin
    t = ts.take [:plus, nil, nil], 10
  rescue Rinda::RequestExpiredError

    begin
      ts.read [:end], 1
      break # stop listening for the n-tuple
    rescue Rinda::RequestExpiredError
      # ignore a situation when there is no shutdown flag
    end

    next # repeat the loop

  end
  res = t[1] + t[2]
  ts.write [:result, res]
end

Pattern: Boolean Flag

Problem: An agent should consider a flag presence.

Solution: The solution is to set a timeout for a read or take operation, where the timeout value specifies how often a flag appearance should be checked, and if the flag is presented to perform the flag specific operation.

The Agent Life Cycle pattern is, of course, used too.

(life cycle and boolean flag patterns)

loop do
  begin
    read or take n-tuple with timeout t1
  rescue Rinda::RequestExpiredError

    begin
      read the flag n-tuple with timeout t2
      perform a flag specific operation
    rescue Rinda::RequestExpiredError
      perform an operation when the flag is not set
    end

    next # repeat the loop

  end
  process n-tuple
end

Pattern: Agent Life Cycle

Problem: An agent should be able to check the blackboard for an n-tuple and to process the obtained n-tuple. (It is a trivial pattern.)

Solution: The solution is to perform operation read or take in a loop.

loop do
  read or take n-tuple

  process n-tuple
end

Example:

loop do
  t = ts.take [:plus, nil, nil]

  res = t[1] + t[2]
  ts.write [:result, res]
end

Comments:
The method process should execute agent’s duty :), there is only questionable, if it should:

  • start a new thread, to process the loaded n-tuple, and immediately listen for a next n-tuple
  • or it should process the loaded n-tuple, and when the processing is finished, to start listening for a next n-tuple.

I would prefer the second way - it is more agent-like, it is not mixing threads and agents concepts (including managing agents vs managing threads) - if you would like to process more n-tuples in parallel, simply start another agent instance.

Access Blackborad With Timeout

Operations that access a blackboard to retrieve information, i.e. operations read and take, have a second parameter that defines how long should this operation wait until there will be a matching n-tuple or, simply, timeout. A value of the parameter specifies number of second.

Example:

  • To read a tuple and if there is no such tuple, to wait 4 seconds for it:
    ts.read [nil, nil], 4
    
  • Or to take a tuple and if there is no such tuple, to wait 1 minute for it:
    ts.take [nil, nil], 60
    

There are two special values that you may use as a timeout value:

  • 0 (zero) - an operation would not wait at all. It is useful for checking, if a n-tuple is stored in the blackboard or not.
  • nil - an operation would wait endlessly. Of course, there is not difference if you would call an operation without a specified timeout value or with nil.
    Actually, to be more precise, an operation would wait 231 - 1 seconds, that is approximately 30 years. ;)

But what would happen, if we specify a timeout value and an n-tuple would not appear on the blackboard in the specified time(out)?
Rinda throws the Rinda::RequestExpiredError exception.

The handling exceptions in Ruby is very comfortable (esp. with the command retry). An example follows, where a tuple is taken with a timeout 5 seconds. This allows to inform an user about the state, that the application is still waiting for a message:

puts "Waiting for a message."
begin
  t = ts.take [:message, nil], 5
rescue Rinda::RequestExpiredError
  puts “Still waiting for a message.”
  retry
end

The timeout functionality allows to:

  • create a responsiveness application - to inform other components or users about its state (e.g. waiting for a message - processing a message).
  • end an application normally - to break waiting for a n-tuple every so often and check if the application should end.
  • check for different n-tuples - it is possible to wait in deferent intervals for different n-tuples. Of course, a better solution is to create for each n-tuple an own thread that performs a blackboard access operation for this particular n-tuple.
  • create an application that does not access a blackboard all the time (e.g. only every hour for 1 minute).