Wednesday, July 11, 2012

OFlux

Tasty.



Many years ago, I was looking for a way to help write C++ applications quickly using re-usable components with an event-based metaphor. The problems we were dealing with felt like describing an assembly line which turned requests into responses.  Each stage of the assembly line could be described by pretty basic functions which mutate state in some way.  Some casting around for solutions to this problem led me to Emery Berger et al's Flux project which had a USENIX 2006 paper published describing it.  I really liked how this model abstracted away the writing of thread code.  I think we take this for granted now with event-based programming paradigms now, but it looked like a promising line of attack for us.

For several reasons, we decided to rewrite the Flux tool and add features to it that our application developers needed.  First of all the compiler was redone from scratch using OCaml instead of Java, and then the run-time was redone using C++ which loaded flow information via XML.  These choices made the code a bit more flexible, and -- to be fair -- moving away from expedient graduate student project code had to happen for production use.

Account Cache Example


To consider a small example (below) of an integer indexed account cache (containing C++ Account objects), consider the following.

 exclusive account_lock (int account) => Account *;
 node Start () => (int account);
 node Finish (int account, guard account_lock(account)) => ();
 source Start -> Finish;


Using exclusive guard account_lock as a resource the flow consisting of C++ functions Start() and Finish() operates as follows:


  • Start() generates integers refering  to the keys of the account_lock which are accessed by the Finish() function.
  • Start() may be doing I/O (reading a socket or a file) to obtain account integer keys
  • multiple Finish() events can run at the same time on different accounts (the semantics of the exclusive key word)
  • The account_lock guard resource is accessed for the duration of the Finish() function invocation and de-referenced with the account integer key.

Here is the shell C++ code for Finish which does the initial object population from the heap when the guard de-references to a NULL:
int Finish(const Finish_in * fi
         , Finish_out *
         , Finish_atoms * fa)
{
 if(fa->account_lock() == NULL) {
  fa->account_lock() = new Account(fi->account);
 }
 ...
 return 0;
}

This example demonstrates how the different sub-tasks of producing and consuming work in the program can be decoupled to increase concurrency in a beneficial way.  Often, for the purpose of testing/demonstrating, it is sufficient to write an example source node (which generate events from nothing like Start() does) via a call to sleep() and some formula for where the input comes from.  Like the Flux run-time the issuing of a blocking system call like sleep() causes the run-time to consult its queue of run-able events to see if another thread can either finish (preferred) or initiate an event execution.  This is detected via interposition of the system calls so that the application developer is unaware that it is happening.

New Repo


The OFlux repo is now publicly available on Github and released under AGPLv3.  Please have a look at the src/examples sub-directory for more examples (documentation is coming), if you are interested.

No comments:

Post a Comment