event.rb
Path: event.rb
Modified: Sun Nov 16 10:49:35 EST 2003
WorkerThread EventDispatcher EventProcessor EventHandler NullEventHandler ChainEventHandler PrintEventHandler EventFramework EventGenerator Event EventFramework TopLevel

Event Framework Documentation

This module provides a simple event framework for the generation, dispatching, and processing of events in an asynchronous manner. Events are generated and dispatched to an event queue explicitly or implicitly via event generators. Processing occurs asynchronously and does not interfere with the dispatching of events.

The framework is suited for applications that generate events which should be processed asynchronously. For example, included in the distribution are two sample applications:

  1. A minimal cron implementation that generates events as jobs should be executed. When events are processed, they simply execute the job on the underlying OS.
  2. An implemention of the ‘tail -f’ command. As new lines are appended to a file, events are generated which output the lines to the standard output.

These are just some of the many potential uses of this framework. As you’ll discover, the framework is easy to use and can be applied to a variety of applications. This document also contains numerous examples that can be modified for your own use. Lets move on to a more technical discussion about the framework.

Overview of the Framework

The framework is composed of several classes in the EventFramework module. Although these classes may be used directly, a facade (EventFramework::EventFramework) has been supplied to facilitate use of the framework. The facade has been provided to insulate general use of the framework. However, to effectively use the framework, it is still important to understand how the framework is composed. The diagram below illustrates how each of the classes are related:

                                              |
  Generator1 <----+                           |
                   \ gets             puts    |    gets
  Generator2 <----+------- Dispatcher ----> Queue <---- Processor
                   /                          |
  Generator3 <----+                           |
                                    Thread 1  |  Thread 2

The following is a brief overview of the significant classes in the framework:

EventFramework::Event
The Event class represents a single event and includes the following information: the event creation time, the originator of the event, a piece of arbitrary data associated with the event, as well as an event handler that is used to process the event.
EventFramework::EventHandler
The EventHandler specifies how an event is to be processed. Each event contains a reference to its handler which may be shared among events. The handler is invoked when the event processor is processing events. This module includes two simple event handlers (print and null) as well as a handler that is capable of chaining other handlers together (chain).
EventFramework::EventGenerator
The EventGenerator is the source of events in the framework. This class responds to a predefined interface which must be implemented by users when creating their own event generators. The event generator is polled by the event dispatcher at regular intervals.
EventFramework::EventDispatcher
The EventDispatcher polls each of the event generators at regular intervals (by default 1sec) for new events. These events are then placed on an event queue which is serviced by the event processor. Events can also be explicitly dispatched using the dispatcher. The event dispatcher runs in a separate thread so that event processing does not adversely affect the dispatching of events.
EventFramework::EventProcessor
The EventProcessor waits for the arrival of events on the event queue. As events are dequeued, they are processed using the event handler associated with the event. The event processor runs in a separate thread so that event dispatching does not adversely affect the processing of events.
EventFramework::EventFramework
The EventFrameworkFacade provides an easy to use facade to the framework. Most users will never need to use anything other than this interface. Sample usage of the API in the following section.

Using the Framework

In this section, we will discuss the various ways to utilize this framework using a small handful of examples for illustration. First, lets discuss the code that you’ll actually have to write in order to leverage this framework. In all but the simplest cases, you’ll need to write yourself one or more event handlers that will be used to process events in the system. Handlers can do anything you want. For example, I’ve personally written handlers for the following purposes:

The last two handlers are included in the framework. We‘ll use the print event handler in the first example so we don’t have to concern ourselves with writing our own. In a later example, we’ll chain two handlers together and write our own custom event handler.

The next decision that must be made is how you want to dispatch events into the framework. There are two options for event dispatching:

Explicit
EventFramework#dispatch_event can be called each time an event is to be dispatched for processing. When you use this method, it is your responsibilty to dispatch each event as they are generated by your application.
Implicit
By creating your own EventGenerator and registering it with the framework, events will be dispatched for processing at regular intervals by the EventDispatcher when it polls the registered generators for new events.

You can use either method or a combination of both depending on your requirements. The latter is the more hands-off approach as you do not have to concern yourself with the dispatching of events. It also requires that you provide your own event generator. The former is more direct, but you must manually dispatch events yourself. Examples of both are provided in the following sections, as well as examples of custom event handlers and chaining of handlers together.

Example of Explicit Dispatching

In this section, a trivial example will be used to illustrate the proper use of the framework when explicity dispatching events. This example will generate three events, dispatch those events, and then terminate after the events have been processed. As each event is processed, a message will be displayed to standard output because each event will specify the use of the PrintEventHandler (which is included in the framework). A custom event handler example will be presented in a subsequent section. The purpose of this example it to emphasize the dispatching of events.

With that said, lets create an array of the three events to be dispatched into the framework:

  require 'event'

  handler = EventFramework::PrintEventHandler.new
  events = []
  0.upto 3 do
    events << EventFramework::Event.new(self, 'arbitrary data', handler)
  end

As you can see, each event is created by passing a reference to self (the originator), some arbitrary data (in this case a string), followed by our event handler reference. It should be noted that we pass the same event handler to each event. This is more efficient than creating a new event handler object for each generated event (and required if you want to keep state between events).

Now that we have created the three events, we must manually dispatch them into the framework. This is easily accomplished by using the facade that is included with the framework:

  facade = EventFramework::EventFramework.new
  facade.start

  for event in events
    facade.dispatch_event event
  end
  facade.stop

Lets slowly step through this code. First, we instantiate an instance of the facade. Next, we start the framework which will trigger the following chain of events:

  1. An EventProcessor thread is started which is waiting to process events as they are dispatched to the event queue.
  2. An EventDispatcher thread is started which is used to poll any generators, which are not used in this example. See the next section for an example of using event generators.

After startup, control is immediately returned to the caller. This enables us to manually dispatch our events. As each event is dispatched, it is placed on an event queue, and control is again returned to the caller immediately. At some point, the event will be processed by the event processor (running in a separate thread) which consumes events on the queue. Finally, the framework is stopped in a clean manner. It is important to note that +facade.stop+ will not return until all events on the queue have been processed.

Example of Implicit Dispatching

In this section, another trivial example will be used to illustrate the proper use of the framework when implicitly dispatching events using an event generator. This example will generate a single event each time our generator is polled. As each event is processed, a message will be displayed to standard output because each event will specify the use of the PrintEventHandler (which is included in the framework). A custom event handler example will be presented in a subsequent section. The purpose of this example it to emphasize the dispatching of events.

First, lets build our simple event generator. There are two ways to create an event generator:

  1. Subclass EventGenerator and override get_events. This method must return an array of events (or an empty array if no events have been generated). The benefit of this approach is that you can create a re-usable class that can have additional methods and/or a constructor associated with it. The following is one implementation:
      require 'event'
    
      class TrivialEventGenerator < EventFramework::EventGenerator
        def initialize
          @handler = EventFramework::PrintEventHandler.new
        end
        def get_events
          [ EventFramework::Event.new(self, 'arbitrary data', @handler) ]
        end
      end
    
  2. Pass a block to the constructor of EventGenerator. This is useful when you are creating a one-time event generator that does not need additional methods and/or a constructor. It’s also convenient when you want access to the current context. For example:
      require 'event'
    
      handler = EventFramework::PrintEventHandler.new
      trivial = EventFramework::EventGenerator.new do
        [ EventFramework::Event.new(self, 'arbitrary data', handler) ]
      end
    

As you can see, both of our implementations always return an array containing a single event (which is quite trivial but serves the purpose of this example). The event is created by passing a reference to self (the originator), some arbitrary data (in this case a string), followed by our event handler object which is instantiated once in the constructor. Again, it should be noted that we pass the same event handler to each event. This is more efficient than creating a new event handler object for each generated event (and required if you want to keep state between events).

With our event generator complete, we are now ready to utilize the facade interface provided with the framework:

  generator = TrivialEventGenerator.new

  facade = EventFramework::EventFramework.new
  facade.add_event_generator generator
  facade.start
  begin
    while true do sleep 10000 end
  rescue Interrupt
    facade.stop
  end

First, we instantiate an instance of our event generator, followed by an instance of the facade. Next, we add or register our event generator to the facade. At this point, additional generators could be added if desired. Once all generators have been added, the facade must be started. Upon startup, the following chain of events will occur:

  1. An EventProcessor thread is started which is waiting to process events as they are dispatched to the event queue.
  2. An EventDispatcher thread is started. The EventDispatcher subsequently starts all of the registered event generators and polls them at regular intervals placing any new events on the event queue.

After startup, control is immediately returned to the caller so we must be careful to not terminate prematurely otherwise we will not have a chance to see the framework in action. Thus, we sleep for a very long time and loop forever. This prevents the main program from terminating. Furthermore, the loop is wrapped within a begin/rescue block so that keyboard interrupts (Ctl-C) can be caught enabling us to cleanly shutdown the event framework. Again, it should be noted that all events are processed before +facade.stop+ returns control to the caller.

Example of a Custom Event Handler

In this example, we’ll create our own custom event handler that will keep state in between invocations. We‘ll assume in this example that the "arbitrary data" that included in each event is a number that is to be summed as each event is processed. Furthermore, the handler will print a message with the current total to the console. There are two approaches we can take when building a custom event handler.

  1. Subclass EventHandler and override handle_event. The benefit of this approach is that you can create a re-usable class that can have additional methods and/or a constructor associated with it. For example:
      class SimpleEventHandler < EventFramework::EventHandler
        def initialize
          @sum = 0
        end
        def handle_event event
          puts "sum = #{@sum += event.data}"
          true
        end
      end
      handler = SimpleEventHandler.new
    
  2. Pass a block to the constructor of EventHandler. This is useful when you are creating a one-time event handler that does not need additional methods and/or a constructor. It’s also convenient when you want access to the current context. For example:
      sum = 0
      handler = EventFramework::EventHandler.new do |event|
        puts "sum = #{sum += event.data}"
        true
      end
    

In both cases, we now have a custom event handler which can be used in the framework. Readers wondering why handle_event returns true are asked to defer this question until the next section where chaining event handlers are discussed. To complete our example, lets explicitly (for simplicity) dispatch some events to test our custom event handler:

  require 'event'

  sum = 0
  handler = EventFramework::EventHandler.new do |event|
    puts "sum = #{sum += event.data}"
    true
  end
  facade = EventFramework::EventFramework.new
  facade.start
  facade.dispatch_event EventFramework::Event.new(self, 2, handler)
  facade.dispatch_event EventFramework::Event.new(self, 1, handler)
  facade.dispatch_event EventFramework::Event.new(self, 3, handler)
  facade.stop

When you run this example using either of the two versions of our custom event handler, you should see the following on your screen:

  sum = 2
  sum = 3
  sum = 6

Example of Chaining Event Handlers

There are times when you may want more than one event handler to process an event. However, the framework enforces (via the Event constructor) that only one handler be associated with each event. This is where the ChainEventHandler comes into play. In this final example, we will discuss how to chain event handlers together using this technique.

First, We‘ll create a simple chain consisting of our custom event handler from the previous section, as well as a PrintEventHandler:

  chain = EventFramework::ChainEventHandler.new
  chain.add_event_handler SimpleEventHandler.new
  chain.add_event_handler EventFramework::PrintEventHandler.new

Alternatively, you could specify the handlers as arguments to the ChainEventHandler constructor. With our chain, we can now pass it as the single event handler when constructing an event:

  require 'event'

  class SimpleEventHandler < EventFramework::EventHandler
    def initialize
      @sum = 0
    end
    def handle_event event
      puts "sum = #{@sum += event.data}"
      true
    end
  end
  handler = SimpleEventHandler.new
  chain = EventFramework::ChainEventHandler.new
  chain.add_event_handler SimpleEventHandler.new
  chain.add_event_handler EventFramework::PrintEventHandler.new

  facade = EventFramework::EventFramework.new
  facade.start
  facade.dispatch_event EventFramework::Event.new(self, 2, chain)
  facade.dispatch_event EventFramework::Event.new(self, 1, chain)
  facade.dispatch_event EventFramework::Event.new(self, 3, chain)
  facade.stop

The above code should output the following to your console:

  sum = 2
  [Mon Oct 20 16:10:32 EDT 2003 -> main] 2
  sum = 3
  [Mon Oct 20 16:10:32 EDT 2003 -> main] 1
  sum = 6
  [Mon Oct 20 16:10:32 EDT 2003 -> main] 3

So how does the ChainEventHandler work? When the chain is invoked, it invokes each of the embedded event handlers until one of them returns a false value at which point the processing of the chain stops. Thus, event handlers that wish to behave appropriately in a chain must return true if subsequent handlers are to process the event. It is for this reason that we return true in our SimpleEventHandler. If we had returned false instead, the PrintEventHandler would never get invoked and the output would be as follows:

  sum = 2
  sum = 3
  sum = 6

Why might you want to prevent subsequent event handlers from running in a chain? Perhaps you want to do some form of rate-limiting. For example, do not print the event unless it has occurred at least three times:

  require 'event'

  class CountEventHandler < EventFramework::EventHandler
    def initialize threshold
      @threshold = threshold
      @count = 0
    end
    def handle_event event
      @count += 1
      @count % @threshold == 0
    end
  end
  chain = EventFramework::ChainEventHandler.new
  chain.add_event_handler CountEventHandler.new(3)
  chain.add_event_handler EventFramework::PrintEventHandler.new

  facade = EventFramework::EventFramework.new
  facade.start
  facade.dispatch_event EventFramework::Event.new(self, 1, chain)
  facade.dispatch_event EventFramework::Event.new(self, 2, chain)
  facade.dispatch_event EventFramework::Event.new(self, 3, chain)
  facade.stop

The above will result in the following output:

  [Mon Oct 20 16:52:11 EDT 2003 -> main] 3

Although the first two events were dispatched, the CountEventHandler returned false thus terminating the handler chain. As a result, only the third event is actually printed to the console.

Conclusion

The event framework is simple and easy to use. With a few custom event handlers and event generators, you can have a fully autonomous system producing and consuming events asynchronously. Have fun!

Required files
thread   
Classes and Modules
Module EventFramework
  ::Class EventFramework::ChainEventHandler
  ::Class EventFramework::Event
  ::Class EventFramework::EventDispatcher
  ::Class EventFramework::EventFramework
  ::Class EventFramework::EventGenerator
  ::Class EventFramework::EventHandler
  ::Class EventFramework::EventProcessor
  ::Class EventFramework::NullEventHandler
  ::Class EventFramework::PrintEventHandler
  ::Class EventFramework::WorkerThread