Monday, July 16, 2012

[O]Flux High-level Motivation

Tasty.


If you read the original Flux paper carefully, there are several things that the Flux tool provides:


A data-flow-like top-level language which describes
  1. How the C++ functions in the application should be composed to complete the task flow in the program.
  2. How to protect common resources these functions manipulate using atomicity constraints.
  3. The C++ types and arguments passed from one task to another (though the Flux compiler itself does not comprehend the types very deeply).
An event-based run-time on which to run the final program which prioritizes:
  1. Abstracting concurrency issues from the developer writing the lower level  C++ functions as much as possible.
  2. Trying not to block when doing synchronized work
  3. Completing work which it has already underway (so don't begin something new when there is a partially complete and runnable task available).
Other benefits of the Flux approach include

  1. High re-use of existing code (they cite the migration of an existing BitTorrrent client to Flux).
  2. Deadlock avoidance is part of the design
  3. Performance analysis and prediction
Although a seemingly cumbersome choice for a small domain-specific language, Java was used to implement the original Flux compiler.  One advantage of this choice is that the compiler is highly distributable as a JAR (build once, run everywhere -- right?).  The generated C code that the compiler produced from the Flux flow description implemented the flow with a giant switch statement.  The Flux language implements choice using a style that is similar to pattern matching in a functional language (so a single conditional successor is possible after a node event has run).

Webserver Example


This example demonstrated nicely how to use the basic parts of the Flux language and how to mentally visualize the program:

/* Your basic web server flow */

node Page (int socket) => ();

node ReadRequest (int socket) 
   => (int socket, bool close, const char* request);

node Reply (int socket, bool close, int length, 
      const char* content, const char* output) 
   => ();

node ReadWrite (int socket, bool close, const char* file)
   => (int socket, bool close, int length, 
    const char* content, const char* output);

node Listen () => (int socket);

node FourOhFour (int socket, bool close, const char* file) 
   => ();

node BadRequest (int socket) => ();

source Listen -> Page;

Page = ReadRequest -> ReadWrite -> Reply;

handle error ReadWrite -> FourOhF;

handle error ReadRequest -> BadRequest;


Visualized as a graph the source node Listen  listening to an accept socket passes the request socket to its successor node.  The Page node is just an abstract node or expression for what follows (ReadRequest in this case). ReadRequest reads the request from the socket and determines if there is any HTTP keep-alive in the request.



This flow is straight-through handling except that there are error handles along the way for mis-formed requests (BadRequest) and resources which the webserver does not have (FourOhFour named after the famed 404 HTTP return code).

In terms of concurrency the run-time will juggle the system calls that are used to read files, read requests from sockets and write responses on those sockets.  Without bothering the application programmer very much about it, the ability to have multiple threads reading different resources (possibly separate files that are not paged into virtual memory yet) at the same time is cool.  In this case we say that there are several threads running events for a node like ReadWrite.



No comments:

Post a Comment