asynchro

This is a set of extensions for event-driven, asynchronous Ruby applications and improved support for testing within the EventMachine engine.

The two primary components are:

  • A simple tracker that can be used to organize multiple independent blocks and ensure that they are all completed before moving on.

  • A simple state machine defining tool that can be used to map out the steps required to complete an operation. This is especially useful if the process will vary depending on certain conditions.

Installation

Using the gem installation tool is the easiest way to get started:

gem install asynchro

If you’re using it within a bundler managed project, add to your Gemfile:

gem 'asynchro'

Using Asynchro::State

To define a state mapping, include the Asynchro::Extensions methods and then use the ‘async_state` method:

include Asynchro::Extensions
ran = [ ]

async_state do |state|
  state.start do
    ran << :start
    state.state1!
  end

  state.state1 do
    ran << :state1
    state.state2!
  end

  state.state2 do
    ran << :state2
    state.state3!
  end

  state.state3 do
    ran << :state3
    state.finish!
  end

  state.finish do
    ran << :finish
  end
end

At the end of this, the ‘ran` array will contain a list of all the states which have executed, which in this example will be all of them in order.

The two pre-defined states are ‘start` and `finish`, where the default behavior is to simply finish when started.

To declare a state, simply name it and supply a block to execute when in that particular state. Generally the ‘start` state is defined first in order to provide an entry point. The `finish` state is optional.

To transition to another state from within a state, call the name of the state with the exclamation at the end, so for ‘finish` then `finish!` would be called. Entering a state that has not been previously defined will result in a warning being sent to STDERR for diagnostic purposes.

If this warning is undesirable, declare the state with an empty block.

Be careful to avoid entering states for which there is no exit condition or the execution will never successfully complete.

Using Asynchro::Tracker

The tracker component is used to process multiple blocks independently, yet confirm that they have all completed before moving on. This control structure helps to avoid duplicating logic in callback methods.

A simple example is:

require 'rubygems'
gem 'asynchro'
require 'asynchro'

include Asynchro::Extensions

def example_async_call
  yield
end

success = false

async_tracker do |tracker|
  tracker.perform do |done|
    example_async_call do
      done.call
    end
  end

  tracker.perform(4) do |done|
    example_async_call do
      done.call
    end
  end

  tracker.finish do
    success = true
  end
end

The main ‘async_tracker` call will appear to block until all of the actions defined by `perform` are completed. More specifically, the `done` trigger must be called in order to carry forward. Generally this trigger is passed through to the final block that must be executed before the operation is completed. In this example the trigger is scoped such that the inner block has access to it.

The ‘perform` method is used to declare something that must be performed, and the `finish` method will be executed once all of the declared blocks have executed their callback.

The ‘perform` method takes a numerical argument that indicates the number of times the callback must be called in order to be completed. The default is 1. Negative or zero values will result in undefined behavior.

It is possible to declare ‘perform` blocks at any time prior to the completion of the last block. This enables additional processing to be performed only if certain requirements are met, or for actions to be chained together as required.

Just as multiple ‘perform` blocks can be declared, multiple `finish` blocks can be supplied. These will execute in the order they are defined, once, upon completion of all the prerequisite blocks.

As there is no timeout, the tracker will wait for an indefinite period of time if something precludes one or more of the callbacks from being executed. This tracker is only suitable for asynchronous code that is designed such that it will always trigger a callback of some sort within an acceptable period of time.

Other Notes

Additional demonstrations of these are included in the test/ directory.

Copyright © 2011-2012 Scott Tadman, The Working Group Inc. See LICENSE.txt for further details.