Pages

Friday, March 4, 2016

Mel Basic

Welcome » NERWous C » Mel
  1. A Serial Program
  2. A Concurrent Program
  3. Mel Elements
  4. Mel Variables
  5. Mel Creation
  6. Mel Passing
  7. Mel Scope
  8. Mel Life
  9. Mel Queues
  10. Mel Operations
  11. Mel Properties


A Serial Program

Let's start with a Producer / Consumer example written serially in standard C language:
main () {
     int c;
     while (c = Produce())
           Consume(c);
}
The main task works serially. It calls Produce to get a value to c, then calls Consume to consume it. The cycle repeats until there is no more products (with Produce returning 0).


A Concurrent Program

Let's make the above program run in concurrency by using NERWous C. Its constructs are highlighted below:
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     while ( <?>store = Produce() );
}
void Consumer (<mel> int store) {
     while ( int c = <?>store )
         Consume(c);
}
The main task creates a mel variable, store, as the communication channel between the Producer and Consumer. Then it creates a task to run the Producer code via the pel construct <!>, and another task to run the Consumer code. The main task then ends itself.

The Producer task keeps producing until Produce returns 0. Every time it has a product, it will see if the mel store is available for it to deposit the product. By default, the mel store has one slot. If a slower Consumer task has not consumed the previous product in that slot, the Producer will wait at the mel store via the <?> wait operator. Eventually the previous product will be Consume'd, the mel store will be emptied, and the CHAOS runtime will allow the Producer to access the mel store. Once it has successfully deposited its new product into the mel store, the Producer resumes its Produce chore. When it cannot Produce any more, it will deposit a 0 value to the mel store to inform the Consumer task. The Producer task then breaks out of the while loop and ends itself.

The Consumer task runs in parallel with the Producer task. The two synchronize via the mel store. If store is empty (due to a slower Producer) or is locked by the Producer while it deposits a new product, the Consumer task will wait at the <?> wait operator. Once it can access the mel store and has detected that the mel store has a value, it will empty the mel store and moves the value to the local variable c. If c is not 0, it will be Consumed. Otherwise, the Consumer task breaks out of the while loop and ends itself.

When all the tasks in the program (main, Producer and Consumer) have ended, the program itself exits.


Mel Elements

A mel element (or just mel) is the "shared data". The NERWous C mel element is introduced by the mel construct <mel>. It is the NERWous C implementation of the Memory Element of the NERW Concurrency Model. The mel element resides somewhere on the model nerw network, hosted in a model computer element (cel). We can imagine the cel as maintaining a pool of shared data, and requesting tasks asking for a piece of this shared data.

A mel is created by default with a 1-slot buffer. It can also be created with additional buffer to optimize throughput. When a mel is requested for reading or writing, the request can be prioritized to minimize latency.

Any task that requests access to the mel must first establish a connection to that element and subsequently maintain a session with it. This session is represented by the mel variable. A NERWous C program has multiple tasks accessing the same shared mel element. Each of these tasks maintain their own resident mel variable. Let's get to know more about mel variables next.


Mel Variables

A mel element is requested via the mel creation statement:
<mel> int store;
Once a mel element has been created, it is represented by a mel variable. The mel variable by itself does not contain the actual shared data. The shared data remains at the remote mel element. What the mel variable contains is the information on how to access the mel element to get hold of the shared data. This information is maintained in the CORE properties within the mel variable.

The other part of the mel variable is the information about the specific request that the task makes against the mel element. The task provides the IN properties of the mel variable to make a request. The task gets the result and status data back via the OUT properties. The mel element also piggy-backs with the OUT data some useful configuration information for the task to cache in the SET properties.

Each task maintains its own resident version of the mel variable. If there are three tasks that access the same mel element such as in the Producer/Consumer example above (i.e., the main task, the Producer task, and the Consumer task), then there are three separate mel variables with the same name (i.e. store). Each mel variable resides in the context of its specific task. They have the same values for the CORE properties, usually the same values for the SET properties, and most likely different values for the IN and OUT properties due to their different interactions with the mel element.


Mel Creation

The main task creates the mel variable, store, via the NERWous C statement:
<mel> int store;
While the above statement looks like a compile-time declaration, it is actually a run-time operation that invokes the underlying CHAOS runtime to secure the resources necessary for accessing the remote shared data. A mel creation statement can contain attributes to let the programmer specify how and where the mel element will be created.

Since it is a run-time operation, the mel creation can fail. In this case, the mel variable <error> property can be used for defensive coding:
main () {
    <mel> int store;
    if ( store<error> ) {
        printf ("Creation of [store] failed on [%s]", store<why>);
        exit 411;
    }
    <!> Producer (store);
    <!> Consumer (store);
}
The reason for a mel creation failure can be found in the <why> property.

Another way to check for failure is to catch a mel operation exception:
main () {
  try {
    <mel> int store;
  }
  catch ( store<...> ) {
    printf ("Creation of [store] failed on [%s]", store<why>);
    exit 411;
  }
    <!> Producer (store);
    <!> Consumer (store);
}


Mel Passing

Like any C language variable, a mel variable can be passed to functions as arguments. One thing special about mel variables is that the way they are passed depends on the destination they are passed to. If they are passed to another task, they are passed by value. If they are passed to a function within the same task, they are passed by reference.

Unlike a normal C variable where passing by reference is an option requiring the use of a pointer, there is no such thing as a pointer to a mel variable. NERWous C does not allow a programmer to choose between passing a mel variable by value or by reference. It always enforces its passing rule: if a mel variable is passed to another task, it is always passed by value; and if passed within a task, it is always passed by reference.

This example shows the passing of mel variables as arguments:
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     if ( store<error> & NERW_ERROR_FAILED ) {
          printf ("Mel [store] invalid due to [%s]", store<why>);
          return;
     }
     while ( <?>store = Produce() );
}
void Consumer (<mel> int store) {
     if ( store<error> & NERW_ERROR_FAILED  ) {
          printf ("Mel [store] invalid due to [%s]", store<why>);
          return;
     }
     while ( 1 ) {
         DoConsume (store);
         printf ("Status in Consumer: %d", store<status>);
         if ( store<value> == 0 ) break;
     }
}
function DoConsume (<mel> int store) {
     Consume (<?>store);
     printf ("Status in DoConsume: %d", store<status>);
}
There are three different mel variables of the same name in the code above. The task main creates the first mel variable store to request the CHAOS runtime to set up the store mel element for sharing, and to maintain its own session with that shared data. It then passes that mel variable by value to the tasks Producer and Consumer. These tasks duplicate the property values of the parent mel variable into their own resident mel variables. As the processing goes on, some property values of the mel variable in Producer will diverge from those of the mel variable maintained by Consumer and the main task, while still sharing some common properties like the location of the mel element.

Within the Consumer task, the DoConsume serial function runs in the context of the task Consumer. It takes the mel variable store as argument by reference. Thus DoConsume is using the same mel variable as set up by Consumer, and whatever changes DoConsume makes on the mel variable store will be reflected back to the Consumer code. For example, DoConsume reads the value of the mel element and caches the <value> and >status< properties in the mel variable <store>. These properties are then used by the parent function Consumer appropriately.


Mel Scope

NERWous C mel variables follow the same scope rules of C variables. They are either global or local, as seen in the following example:
<mel> int GlobalStore;    /* global variable */
main () {
     <mel> int localStore;     /* local variable */
     <!> Producer (localStore);
     <!> Consumer ();
}
void Producer (<mel> int store) {
    <extern> GlobalStore;
    <?>GlobalStore = Produce();     /* OK - GlobalStore is global */
    <?>store = Produce();           /* OK - store is localStore passed by value */
    <?>localStore = Produce();      /* ERROR - localStore is not in scope */
}
void Consumer () {
    Consume (<?>GlobalStore);       /* ERROR - GlobalStore is not declared */
    Consume (<?>store);             /* ERRROR - store is not defined */
    Consume (<?>localStore);        /* ERROR - localStore is not in scope */
}
Any scope error in the above example is flagged during compile time. The NERWous C programmer has to fix all the scope related errors before the program can be compiled for execution.

The mel variable GlobalStore has a global scope. It is first created in the context of the main task. The Producer task re-creates it with the <extern> statement as its own resident mel variable. The NERWous C program now has two separate global mel variables, both pointing to the same GlobalStore mel element: one in the context of the main task and the other, in the context of the Producer task.

The Consumer task omits the <extern> operation before making use of the GlobalStore. This results in a compile-time error. We will learn more about the <extern> statement later: it is both a global scope facilitator during compile time and a global variable duplicator during run time.


Mel Life

The life of a mel element and the life of a mel variable are different. Let's consider the following example:
main () {
    <mel> int store;
    <!> Producer (store);
    <!> Consumer (store);
}
void Producer (<mel> int store) {
    while ( <?>store = Produce() );
    <close> store;
    printf ("Status of mel store [%d]\n", store<status>);  /* OK */
}
void Consumer (<mel> int store) {
  try {
    while ( 1 ) Consume(<?>store);
  } catch ( store<CLOSED> ) {
    store = null;
    printf ("Status of mel store [%d]\n", store<status>);  /* ERROR */
  }
}
The life of a mel element is from the time it is created until it is closed. In the above example, the mel element store comes into life at the mel creation statement as initiated by the main task
<mel> int store;
This statement requests the CHAOS runtime to allocate shared data for the int storage of the mel store, and to open the connections on the cel that hosts that mel element to all tasks that request access (main, Producer, Consumer).

When the mel element is closed, the CHAOS runtime recollects the allocated shared resource and closes the open connections. A mel element is closed implicitly when the NERWous C program exits, or explicitly when the <close> operation is invoked on the mel variable. Any task that has access to the mel element via its resident mel variable can invoke the <close> operation, like the Producer task above. Afterwards, any task that attempts to read or write to the closed mel element will encounter the CLOSED exception, as happens to the Consumer task above.

While there is only one remote mel element store, there are three mel variables store, resident in each of the task. Like all C variables, the life of the mel variables is dictated by their scope (local or global). For the mel variable store in the main task, it is between the mel creation statement and the end of the task. For the Producer task, its mel variable store is active from the task starts until the task ends. After Produce'ing a zero value, it writes that value to the remote mel element, gets off the while loop and closes the mel element. Even though CHAOS is removing the resources and accesses to the mel element store, the Producer still has full control of its resident mel variable. It can access the mel variable's properties at will from its own local context, such as the <status> property for the printf statement.

When the Consumer task detects that the remote mel store has been closed, it jumps into the exception handler, and explicitly deactivates its mel variable store by setting it to null. This frees any resource in the context of the Consumer task that has been allocated to the resident mel variable store. When Consumer tries to printf the property <status>, this property does not exist any more, and the Consumer task will end abnormally via a run-time error.
On a side note
Is the 0 value which is the last value Produce'd by Producer get Consume'd by Consumer? If Consumer gets hold to this value before the <close> statement is issued and processed, then yes, it does. If Consumer has been busy running the Consume on the previous-to-last value, the <close> statement from Producer has the chance to be fully processed by the CHAOS runtime. When Consumer goes back to the mel element store for the next value, it will be served by the <CLOSED> exception instead of getting the 0 value. If the logic of the program requires the 0 value to be Consume'd, then it should be the responsibility of the Consumer task to <close> the mel store.
In summary, the scope of a mel variable dictates its access during compile time, while the life of a mel variable dictates its access during run time.


Mel Queues

The three basic operations on a mel element is creation, reading and writing:
    <mel>int store;
    int c = <?>store;
    <?>store = c;
In the first statement above, the mel element represented by the mel variable store is created. As mentioned in the Mel Creation section, this declarative-looking statement is actually a run-time operation. The CHAOS runtime is instructed to collect all the necessary resources to allow tasks to access the shared data. The main resources are two queues: the reader's queue for read access, and the writer's queue for write access. NERWous C does not define where those queues are created, or even if these queues are physical. NERWous C programmers have to trust CHAOS to set up these shared queues properly, or to return a failure condition that allows them to check for creation errors.

In the second statement above, the task requests a READ access:
  1. The reader task comes to the mel element and stands in line at the default readers' queue.
  2. Whenever the task reaches the top of the default readers' queue, it waits for the queue's turn to read off the mel element. This is because there can be multiple queues with different reading priorities.
  3. The task then examines the mel element from the reading side. If the mel is buffered, reading is done on one side of the buffer and writing is done at the other side. By default, a mel element is unbuffered, or looking from a different angle, is buffered with one slot.
  4. If the mel element does not contain any value on the reading side of the mel buffer, it is said to be empty. The task keeps waiting at the top of the default readers' queue, until the mel element receives a value from a writer task.
  5. If the mel element contains a value on the reading side of the mel buffer, it is said to be filled. When this happens, the task removes the value from the mel element, and gets off the readers' queue with the retrieved value.
  6. The task then stores the retrieved value in the <value> property of the associated mel variable.
  7. The task resumes its program execution past the mel read statement.
In the third statement above, the calling task requests a WRITE access:
  1. The writer task comes to the mel element and stands in line at the default writers' queue.
  2. Whenever the task reaches the top of the default writers' queue, it waits for the queue's turn to write to the mel element. This is because there can be multiple queues with different writing priorities.
  3. The task then looks at the mel element from the writing side of the mel buffer. If the mel is not buffered, there is only one slot to write to.
  4. If there is no available slot in the mel element to write another value, the mel element is said to be full. The task keeps waiting at the top of the default writers' queue, until a reader task reads out the mel element, making a slot available for the writer task.
  5. If there is an available slot to write another value, the mel element is said to be vacant. The task deposits its value into the available slot, and gets off the writers' queue.
  6. The task resumes its program execution past the mel write statement.
A mel element has several distinct queues.

For reading:
Readers' QueuesSynopsis
Default queue Tasks with default priority wait in this queue for the mel element to be filled.
Priority queues Tasks with higher priorities wait in these queues instead of the default readers' queue.
Read-only queueTasks wait in this queue for read-only requests.

For writing:
Writers' QueuesSynopsis
Default queue Tasks with default priority wait in this queue for the mel element to be vacant.
Priority queues Tasks with higher priorities wait in these queues instead of the default writers' queue.
Write-Over queueTasks wait in this queue for writeoever requests.

The default readers' queue and writers' queue are created by default with the mel creation operation. The other queues are created on demand. All queue creations are done by the CHAOS runtime. Not all CHAOS implementations support all the queue creations; however all of them are required to support at least the creation of the default queues, either physically or virtually.


Mel Operations

A mel operation is a request that a task sends to the CHAOS runtime for some action on a mel element. In NERWous C, an operation is specified in lower case within the < > markers, and placed before a mel variable. An operation can contain attributes to customize the request. The attributes are specified between the < and > markers.

These are the mel operations and their attributes, using the mel variable store as a stand-in:
OperationAttributesSynopsis
<mel> int store Mel creation operation
buffer
=n
Create a buffered mel with n slots
at
=cel
Create the mel at the specific cel location
mode
=setting
Create the mel with this mode setting:
readwrite: this default mode allows the mel to be read and written to.
readonly: the mel access is always readonly.
<?>store Mel read operation
priority
=n
Access the mel for reading with priority n
timeout
=msec
Wait for the mel to be read for msec milliseconds before aborting
as
=var
Used to represent the mel variable selected in an exclusive reader OR zone
mode
=setting
Request read access with this mode setting:
reader: request a read access (default)
readonly: request read-only access
<?>store = Mel write operation
priority
=n
Access the mel for writing with priority n
timeout
=msec
Wait for the mel to be written for msec milliseconds before aborting
mode
=setting
Request write access with this mode setting:
writer: request write access (default)
writeover: request overwrite access
<close>storeMel closure request
<resume>Mel wait resumption
<quit>storeMel unsubscription request
<rebuffer>storeMel buffer resize request
<snapshot>storeMel properties refresh

Since a mel operation is a run time activity, it can fail. In NERWous C, such failure can be detected by the programmer checking the <error> property of the mel variable immediately after the mel operation, and investigating the <why> property for the long-text description. Another way is to request an exception to be raised on mel operation failures. The NERWous C language borrows the C++ try/catch construct to support exceptions. A mel exception name is specified within the < > markers, and placed after a mel variable. Unlike mel properties which are lower cased, mel exceptions are upper cased.


Mel Properties

A property of a mel variable is a local value that a task caches from its most recent interaction with a mel element. In NERWous C, a property is specified in lower case within the < > markers, and placed after a mel variable.

The value of a mel property is specific to a task. Two tasks that access the same mel element will have two different mel variables to represent their own access to the mel. These mel variables may have different values for the mel properties depending on the circumstance the tasks access the mel element. For example, the <value> property, which represents the value of a mel upon a successful mel read operation, will likely be different for each reader task since each task reads a different value (unless the producer task produces the same value over and over).

Accessing a mel variable by itself, such as c = <?>store or <?>sotre = c, is a mel operation, and thus requires the mel wait operator (<?>). On the other hand, a mel property is like any C language variable, it resides in the context of the task, and accessing one does not require mel waiting.

Let's take the mel variable store as the stand-in. These are its properties:

IN properties
are cached values of the attributes of the last mel operation that the task requests against the mel element:
PropertySynopsis
store<priority>Priority value used by the last mel operation
store<timeout>Timeout value used by the last mel operation
store<name>Scoped name of the mel variable set during creation.
store<agent>Entity that executes a requested mel operation

OUT properties
are the results received back from the last mel operation:
PropertySynopsis
store<value>Value returned by the mel read operation. This value can be a simple entity (like an int), an array or a structured entity
store<count>Number of items in an array value
store<sequence>Version identifier of the value returned from a mel read operation
store<status>Status of the mel element after a mel operation
store<condition>Status of the mel element before a mel operation
store<error>Error code of the last mel operation. A value of 0 means success and non-0 values mean failures.
store<why>Reason of a non-zero <error> value

SET properties
are cached values of the mel element's configuration information. Any change in the mel configuration will be propagated to a mel variable by piggy-backing to the last mel operation return:
PropertySynopsis
store<buffer>Number of buffer slots created for the mel element
store<location>Cel location of the mel element
store<readers>Number of reader tasks subscribing to the mel element
store<writers>Number of writer tasks subscribing the mel element

If the remote mel configuration information has been changed but the task has not made any mel operation request, the SET properties in its mel variable keep the old values and do not reflect the new changes. They are deemed stale.

CORE properties
are system values assigned by the CHAOS runtime to allow a task to reach out to a mel element. These values cannot be changed once a mel element has been created.
PropertySynopsis
store<id>Mel element numerical identification
store<url>Mel element string identifier

Unlike the other properties that may be different between mel variables, the CORE properties are the same for all mel variables that represent the session between the tasks and the mel element.

A mel variable is basically a collection of properties. The properties that are visible to a NERWous C programmer are listed above. A NERWous C implementation can have additional custom properties that are particular to that implementation and hidden from the application-level programming.


Previous Next Top

No comments:

Post a Comment