Pages

Sunday, December 23, 2018

Mel Global Variables

Welcome » NERWous C » Mel
  1. Global Variable Scope
  2. Global Variable Usage
    1. Semantic Sharing
    2. Code Sharing
    3. Name Discovery
    4. No Global Variables
  3. Global Variable Creation
    1. Multiple Global Variables
    2. No Exception Handling
    3. No Extern Statement
  4. Global Variable Extern Statement
    1. Extern Statement Signature
    2. Extern Statement Translated
    3. Extern Statement Chain
    4. Extern Statement Short Passing
    5. Extern Statement Multiple Values
    6. Extern Statement Macro
  5. Global Variable Closure


Global Variable Scope

In the Mel Basic chapter, we have touched on the topic of mel scope. Let's revisit the example used:
<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 local */
    <?>localStore = Produce();    /* ERROR - localStore is not in scope */
}
void Consumer () {
    Consume (<?>GlobalStore);     /* ERROR - GlobalStore is not in scope */
    Consume (<?>store);           /* ERROR - store is not defined */
    Consume (<?>localStore);      /* ERROR - localStore is not in scope */
}
Mel variables, like GlobalStore and localStore above, appear in NERWous C programs based on their scope rules: either local or global. The enforcement of the scope rules is during compile time. The programmer has to resolve the ERRORs flagged in the above program before it can be compiled for execution.

We have seen local mel variables in use extensively in the previous chapter on Mel Operations. In this chapter, we focus on mel variables as global variables.

Note that the remote mel elements are always "global", in the sense they can be accessed at run-time anywhere in a NERWous C program. The scope rules apply only at compile time and only to the mel variables, and not the mel elements. When mel variables have local scope, they appear only inside a function or block of code. When they have global scope, they can appear anywhere in the program, but only with the help of the <extern> statement. Incorrect appearances will trigger errors during compilation.

The CORE properties of a mel variable allow a task to reach out to a mel element. For local mel variables, the CORE properties are passed by value from the parent task to the children tasks via function arguments. For a global mel variable, the CORE properties can also be passed from the parent task to the children tasks, but only if the parent task knows about this particular global mel variable. If it does not, the children tasks have to query the CHAOS runtime for the CORE properties to initialize their own global mel variables to reach out to the mel elements. In NERWous C, there is a single mel element that resides somewhere on the NERW network, but there is no single global mel variable. Each task maintains its own global mel variable in its own individual context.


Global Variable Usage

We can see the following uses for global mel variables:
  1. Semantic Sharing
  2. Code Sharing
  3. Name Discovery
Semantic Sharing

In the example above, although both the GlobalStore and localStore mel elements are accessible by any task, their meanings are different. The GlobalStore mel element is meant to be a shared resource that is common to all tasks. On the other hand, the localStore mel element is controlled regionally by the main task. Only tasks that main wants them to work on localStore, will know about localStore that main explicitly passes as argument.

Code Sharing

Let's write a new Producer/Consumer example using only the global mel variable GlobalStore:
<mel> int GlobalStore;    /* global variable */
main () {
    <!> Producer ();
    <!> Consumer ();
    printReport ();
}
void Producer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore = Produce() );
    printReport ();
}
void Consumer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore )
        Consume ( GlobalStore<value> );
    printReport ();
}
void printReport () {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        GlobalStore<status>, GlobalStore<condition>);
}
The function printReport is a serial function. It is shared by all three tasks in the program (main, Producer and Consumer) to print out the status and condition of the global mel variable GlobalStore.

When invoked from the main task, printReport makes use of the global mel variable GlobalStore that is resident in the main context. This global mel variable is different from the global mel variables GlobalStore that are used by printReport invoked from the Producer and Consumer tasks. As said previously, each task maintains its own global mel variables in its own context. By using mel variables with the global scope, they do not need to explicitly pass them around to subsequent shared functions, giving the code a "cleaner" look.

Name Discovery

All mel variables, declared with either global or local scope, are reachable by any task in the NERWous C program, but only if the tasks know about them. For local mel variables, this knowledge comes from their explicit passing from parent tasks to children tasks as functional arguments. The global mel variables are also passed in a similar way, but implicitly via the <extern> statement. However, global mel variables have a feature that local mel variables does not, which is name discovery.

The CHAOS runtime environment keeps track of mel elements with global scope: their names and their contact information (such as the CORE properties). This allows tasks that do not get the mel element information passed to them from the parent task to ask CHAOS for the contact information so they can reach out to the mel element by themselves. This is done via the on attribute of the <snapshot> operation. We will see this use in the Extern Statement Translated section.

No Global Variables

Some programmers do not like the global-variable "cleaner" look. As a matter of style, they prefer not to deal with global variables at all, be it regular C variables or NERWous C mel variables. The above Producer/Consumer example can be rewritten by replacing global mel variables with local mel variables:
main () {
    <mel> int localStore;    /* local variable */
    <!> Producer (localStore);
    <!> Consumer (localStore);
    printReport (localStore);
}
void Producer (<mel> int store) {
    while ( <?>store = Produce() );
    printReport (store);
}
void Consumer (<mel> int store) {
    while ( <?>store )
        Consume ( store<value> );
    printReport (store);
}
void printReport (<mel> int store) {
    printf ("[%s] status [%d], condition [%d]\n",
        store<name>, store<status>, store<condition>);
}
Compared with the earlier global-variable version, the local-variable version looks a little more verbose with all the local mel variables being passed around. Interestingly, the local-variable version can be used to explain in detail the behavior of a global mel variable from creation to closure. As a matter of fact, a NERWous C compilation suite may support global mel variables by translating them into local mel variables that are passed around tasks behind the scene.


Global Variable Creation

When a local mel variable is created, it is under the context of the task that runs the mel variable declaration statement. What is the task context for a global mel variable? Let's modify our Producer/Consumer example again:
<mel> int GlobalStore;    /* global variable */
main () {
    if ( GlobalStore<error> ) {
        printf ("[GlobalStore] creation failed on error [%d] due to [%s]\n",
            GlobalStore<error>, GlobalStore<why>);
        exit (911);
    }
    <!> Producer ();
    <!> Consumer ();
}
void Producer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore = Produce() );
}
void Consumer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore )
        Consume ( GlobalStore<value> );
}
Like a local mel variable, a global mel variable declaration:
<mel> int GlobalStore;
is not only a compile-time statement but also a run-time operation to allocate shared resource to set up a mel element at a cel location. (In the above example, since the at attribute is not specified, the CHAOS runtime will allocate the mel element at a default cel that hosts generic shared resources.) This remote mel element is then represented for program use by the eponymous mel variable GlobalStore.

With the global mel variable GlobalStore defined outside of any function, its resident context is of a hidden startup task. This is the one that the CHAOS runtime uses to invoke the main task. It also passes to the main task any global mel variables that it creates. The properties of these global mel variables have the same initial property values for newly created local mel variables.

The hidden startup task is not meant to run a programmer's custom code. Therefore, if a programmer needs to check the result of a global mel creation, especially over an unstable runtime environment such as a wide area network (WAN) environment, this check has to be done in an explicit task. The main task is logically the closest one to the global mel creations, so it is it that usually does the checking, as in the example above. However any task that makes use of a global mel variable, can also does its own checking before use.

Multiple Global Variables

Multiple global mel variables can be declared for compile-time scope and run-time creation:
<mel> int GlobalStore1, GlobalStore2[5];
main () {
    if ( GlobalStore1<error> ) {
        printf ("[GlobalStore1] creation failed on error [%d] due to [%s]\n",
            GlobalStore1<error>, GlobalStore1<why>);
        exit (911);
    }
    if ( GlobalStore2<error> ){
         printf ("[GlobalStore2] creation failed on error [%d] due to [%s]\n",
            GlobalStore2<error>, GlobalStore2<why>);
         exit (911+1);
    }
}

No Exception Handling

The result of a local mel variable creation can be checked against the <error> property or caught via a mel exception. However due to the global mel creation being outside of any code block, the exception method is not available for global mel variables.

In the code snippet below, we can use the exception handling for the local mel creation but not for the global one:
<mel> int GlobalStore;
main () {
    if ( GlobalStore<error> ) {
        printf ("[GlobalStore] global creation failed on error [%d] due to [%s]\n",
            GlobalStore<error>, GlobalStore<why>);
        exit (911);
    }
    try { <mel> int localStore;
    } catch ( localStore<...> ) {
        printf ("[localStore] local creation failed on error [%d] due to [%s]\n",
            localStore<error>, localStore<why>);
        exit (911+2);
    }
}
For the global mel variable, we can only use the <error> property checking.

No Extern Statement

As said above, global mel variables are created under the context of the hidden startup task and then "passed naturally" to the main task. There is no need for an <extern> statement in the main task to receive all the declared global mel variables. This is not true when main needs to "pass" the global mel variables to its children tasks, as we will see next.


Global Variable Extern Statement

Except for the main task which receives the global mel variables from the hidden startup task, other tasks that need to use a global mel variable, have to explicitly invoke an <extern> statement on that global mel variable. Unlike the C language extern statement which is solely a compile-time statement to resolve scope, the NERWous C <extern> statement is also a run-time statement. It creates in the context of the task that runs that statement, a new mel variable that points to the same remote mel element as any other mel variable of the same name that appears in the context of any other task in that particular NERWous C program.

Let's modify our Producer/Consumer example to illustrate the <extern> use:
<mel> int GlobalStore;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore = Produce() );
    printReport ();
}
void DuoConsumers () {
    <!> Consumer();
    <!> Consumer();
}
void Consumer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore )
        Consume ( GlobalStore<value> );
    printReport ();
}
void printReport () {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        GlobalStore<status>, GlobalStore<condition>);
}
As said above, the <extern> statement in Producer creates a new GlobalStore mel variable in that task's context. How does that mel variable get initialized?

Extern Statement Signature

The Producer task indicates its intention to access the global mel variable GlobalStore via the statement
<extern> GlobalStore;
This statement updates the signature of the task Producer. As we will see in the pel basics chapter, a signature of a task consists of:
  1. Return values
  2. Explicit arguments
  3. Extern global variables
With its signature such updated, the Producer task requires any parent task that invokes it, to pass the global mel variable that the parent task maintains in its own context. Extern global variables can be viewed as implicit arguments, to complement the explicit functional arguments. (Neither the Producer nor Consumer versions here have functional arguments, see this example for the versions that have store as the functional argument.)

The <extern> statement is both declarative and operative. During compilation time, the <extern> declaration collects the type of the mel variable (such as int), and checks for the scope rules. During run time, the <extern> operation re-creates the global mel variable for the resident task. Like the local mel variables passed as functional arguments, the global mel variables created by the <extern> operation, will be initialized by value from the parent task.

In the latest example, there are five separate GlobalStore global mel variables. The first one is created in the context of the hidden startup task. It is then passed to the main task. This second version and the first version could be the same, depending on the NERWous C compilation suite. The third version is in the context of the Producer task. The fourth and fifth versions are in the context of each of the two Consumer tasks.

All the versions of the global mel variable GlobalStore have the same CORE properties since they access the same remote mel element. The other properties may diverge depending on what interactions the individual tasks have with the remote mel element.

All tasks share the same serial function printReport. It prints out the status and condition of the global mel variable in the context of the invoking task. It is invoked four times in the above example by different tasks, and the printf outputs can all be different.

Extern Operation Translated

NERWous C seeks to process global mel variables as much the same way as local mel variables. In fact, a NERWous C compilation suite may have a translation pass to transform global mel variables into local mel variables. A hypothetical translation could be as follows:
<mel> int GlobalStore;
main (int argc, char **argv, char **envp, char **nerw) {
    <mel> int __GlobalStore = nerw[0];
    <!> __Producer (__GlobalStore);
    <!> DuoConsumers ();
    __printReport (__GlobalStore);
}
void __Producer (<mel> int __GlobalStore) {
    if ( __GlobalStore == null ) <snapshot on="GlobalStore"> __GlobalStore;
    while ( <?>__GlobalStore = Produce() );
    __printReport (__GlobalStore);
}
void DuoConsumers () {
    <!> __Consumer(null);
    <!> __Consumer(null);
}
void __Consumer (<mel> int __GlobalStore) {
    if ( __GlobalStore == null ) <snapshot on="GlobalStore"> __GlobalStore;
    while ( <?>__GlobalStore )
        Consume ( __GlobalStore<value> );
    __printReport (__GlobalStore);
}
void __printReport (<mel> int __GlobalStore) {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        __GlobalStore<status>, __GlobalStore<condition>);
}
The hypothetical translation can help explain the working of the <extern> mel operation:
  1. The CHAOS runtime allocates shared resources for the mel element GlobalStore, and creates the eponymous mel variable with global scope.
     
  2. The CHAOS runtime passes this mel variable to the main task via the hidden nerw argument. This argument is similar to the customary argc, argv or envp that a C language runtime passes to the main function.
     
  3. The mel variable __GlobalStore that is passed to the main task contains properties with the creation initial values.
     
  4. With the appearance of the <extern> statement, the task function Producer is translated to be __Producer with <mel> int __GlobalStore as an explicit argument.
     
  5. When the main task invokes __Producer, it passes by value its __GlobalStore local variable to __Producer via the functional argument conduit.
     
  6. The task __Producer re-creates the __GlobalStore local mel variable in its own context, with properties set to passing initial values.
     
  7. When the task __Producer runs, it detects that there is a __GlobalStore mel argument passed to it, disowning the check for the null value and thus avoiding the call to the <snapshot> operation.
     
  8. The hypothetical NERWous C translator also detects the use of the global variable GlobalStore in the serial function printReport. It translates it to __printReport with the local mel variable __GlobalStore passed as argument. The serial function __printReport runs in the same task context as __Producer, and thus receives __GlobalStore by reference.
     
  9. The task function DuoConsumer does not have an <extern> statement. The hypothetical NERWous C translator does not rename it, and the main task invokes it without passing its local mel variable __GlobalStore over.
     
  10. On the other hand, the task function Consumer does include the <extern> statement, and has been translated it to be __Consumer with <mel> int __GlobalStore as its functional argument.
     
  11. Since DuoConsumer does not have a __GlobalStore local mel variable in its context, it invokes its two __Consumer tasks with the null value for the function argument.
     
  12. When each __Consumer task runs, it detects that there is no __GlobalStore passed to it. The check for the null value becomes affirmative, requiring the <snapshot> operation to be invoked with the on attribute.
     
  13. The value of the on attribute is GlobalStore, the official name of the globally scoped mel element. This instructs the CHAOS runtime to scan the NERWous C program's universe for a mel variable of the same name. If one is found, the local mel variable, __GlobalStore, is initialized with the snapshot values of that mel element.

An actual NERWous C compilation suite may or may not do the above translation. Nonetheless, the above translation describes the conceptual behavior of a global mel variable in NERWous C, as a roadmap for actual implementations.

Extern Statement Chain

The behavior of a global mel variable in NERWous C is like the behavior a local mel variable that is passed from task to task. This forms an extern statement chain. When the chain is broken, like the Duoconsumer above that skips the <extern> statement, the subsequent downstream task (here it is Consumer) has to run a discovery process to find out how to connect to the remote mel element represented by the global mel variable.

This discovery process is done under-the-hood by the CHAOS runtime via the Name Discovery process. This process could be quite expensive. If this were to impact the performance of a NERWous C program, it could be better to change the signature of the DuoConsumer task function so that the extern statement chain is maintained:
void DuoConsumers () {
    <extern> GlobalStore;
    <!> Consumer();
    <!> Consumer();
}

Extern Statement Short Passing

At run time, the <extern> statement re-creates the global mel variable in the context of the resident task. By default, this global mel variable will be created in full with the <value> and associated properties copied from the parent task (via explicit passing) or from the mel element itself by the CHAOS runtime (via the Name Discovery process). In many times, this copy is wasted since the resident task only needs some basic contact information to reach out to the remote mel element.

Like the short passing for local mel variables, a global mel variable can be initialized with short passing, by using the pass attribute to the <extern> statement:
<mel> int GlobalStore;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern pass="short"> GlobalStore;
    while ( <?>GlobalStore = Produce() );
    printReport ();
}
void DuoConsumers () {
    <!> Consumer();
    <!> Consumer();
}
void Consumer () {
    <extern short> GlobalStore;
    while ( <?>GlobalStore )
        Consume (GlobalStore<value>);
    printReport ();
}
void printReport () {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        GlobalStore<status>, GlobalStore<condition>);
}
The term pass is optional, as seen in the <extern> statement for the task function Consumer.

For the Producer task, the main task is the parent task, and will pass the contents of its resident global mel variable GlobalStore over without the <value> and associated properties. The Producer task now has its own global mel variable GlobalStore re-created with the Short Passed Mel Argument Initial Property Values.

The Consumer task has its resident global mel variable GlobalStore re-created by the CHAOS runtime, also with the Short Passed Mel Argument Initial Property Values. This time, the initial values come fresh from the mel element itself, and not potentially stale from any parent task.

Extern Statement Multiple Values

Multiple global mel variables of the same data type and passing attribute can be grouped with the same <extern> statement.
<mel> int GlobalStore1, GlobalStore2, GlobalStore3;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern> GlobalStore1, GlobalStore2;
    <extern pass=short> GlobalStore3;
    while ( <?>GlobalStore1 = Produce() &&
    <?>GlobalStore2 = Produce() );
    <?>GlobalStore3 = CloseEndProduce();
    printReport ();
}
void DuoConsumers () {
    <extern short> GlobalStore1, GlobalStore2, GlobalStore3;
    <!> Consumer(GlobalStore1);
    <!> Consumer(GlobalStore2);
    CloseEndConsume (<?>GlobalStore3);
    printReport ();
}
void CloseEndConsume (lastitem) {
 Consume (lastitem);
 <close> GlobalStore1;
 <close> GlobalStore2;
}
void Consumer (<mel> int store) {
    while ( <?>store )
        Consume ( store<value> );
}
void printReport () {
    printf ("[GlobalStore1] status [%d], condition [%d]\n",
        GlobalStore1<status>, GlobalStore1<condition>);
    printf ("[GlobalStore2] status [%d], condition [%d]\n",
        GlobalStore2<status>, GlobalStore2<condition>);
    printf ("[GlobalStore3] status [%d], condition [%d]\n",
        GlobalStore3<status>, GlobalStore3<condition>);
}
On each iteration, the single Producer task Produces two items and deposits them into the global mel elements GlobalStore1 and GlobalStore2. There are two Consumer tasks: one to Consume from GlobalStore1, and the other from GlobalStore2. When Producer ends its run, it generates a last product via CloseEndProduce. This product is not consumed by a Consume task, but by the DuoConsumers task.

After forking the two Consumer children tasks, the DuoConsumers task waits for the Producer task's CloseEndProduce run, via the GlobalStore3 global variable. It the invokes the CloseEndConsume serial function, that Consumes this last product, and invokes closure on the global variables that its Consumer child tasks are waiting for. This allows the Consumer tasks to break out of their infinite while loop via the CLOSURE exception.

Getting back to the Producer task, even though it only needs short passing for all three global mel variables, for illustration purpose, the above example has it make use of the full value passing for GlobalStore1 and GlobalStore2, and only short passing for GlobalStore3.

Extern Statement Macro

To keep the Extern Statement Chain unbroken, they need to be repeated in child tasks. To alleviate typing repetitions, C language macros can be used to group same-data-type global mel variables.
#define MYGLOBALS GlobalStore11,GlobalStore2,GlobalStore3
<mel> int MYGLOBALS;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern> MYGLOBALS;
    while ( <?>GlobalStore1 = Produce() &&
    <?>GlobalStore2 = Produce() );
    <?>GlobalStore3 = CloseEndProduce();
    printReport ();
}
void DuoConsumers () {
    <extern short> MYGLOBALS;
    <!> Consumer(GlobalStore1);
    <!> Consumer(GlobalStore2);
    CloseEndConsume (<?>GlobalStore3);
    printReport ();
}
For illustration purpose, the Producer task requests full value passing from the parent task, while the DuoConsumer task only requests short passing. Both tasks make use of the macro MYGLOBALS to list the needed global mel variables.


Global Variable Closure

Any task that maintains its own resident global mel variable can invoke the <close> operation to close that global mel element. The first task that invokes the <close> operation removes all the resources that the CHAOS runtime has allocated to support shared access to the global-scoped mel element. Any subsequent access to the closed mel element by this task or any other task will result in a CLOSED mel operation exception.

We have seen the use of global mel closure in the CloseEndConsume function of an earlier example. Let's discuss closure with this simpler example:
<mel> int GlobalStore;
main () {
    <!> Producer ();
    <!> Consumer ();
}
void Producer () {
    <extern> GlobalStore;
    int c;
    while ( c = Produce() )
       <?>GlobalStore = c;

    <close>GlobalStore;
}
void Consumer () {
    <extern> GlobalStore;
    try {
        while ( 1 )
            Consume(<?>GlobalStore);
     } catch ( GlobalStore<CLOSED> ) {
         printf ("Consumer ends due to GlobalStore closed by Producer");
         return;
     }
}
The Producer and Consumer tasks communicate via the global mel element GlobalStore. Each maintains its own resident global mel variable that connects to the same remote mel element. When the Producer generates a 0-valued product, it stops Produce'ing more items. To inform the Consumer task that there are no more forthcoming products, it invokes the <close> operation on GlobalStore.

The Consumer keeps Consume'ing whatever coming up at the global mel element GlobalStore, until it gets hit by a <CLOSED> exception. With its understanding of the Producer behavior, the Consumer task knows that there will be no more products for it to Consume; it thus makes a printf notification, and then ends itself via the return statement.

The main task forks the Producer and Consumer tasks, then ends. The Producer task after closing GlobalStore also ends. The Consumer task after handling the <CLOSED> exception, also ends. With all the tasks having ended, the CHAOS runtime terminates the NERWous C program.


Previous Next Top

Sunday, December 2, 2018

Mel Operations

Welcome » NERWous C » Mel
  1. Mel Operations List
  2. Mel Creation Operation
  3. Mel Passing Operation
    1. Passing By Value
    2. Short Passing By Value
    3. Passing By Reference
    4. No Pointers
  4. Mel Operations Error Codes
    1. Errors In Creation
    2. Errors In Reads
    3. Errors In Writes
    4. No Errors
  5. Mel Operations Error Descriptions
  6. Mel Operations Exceptions
    1. Catch-All Exception
    2. Exception Handler Matching
    3. Exception Escalation
    4. Exceptions Clearing
    5. Exceptions vs. Errors
  7. Mel Snapshot Operation
    1. Snapshot Updated Properties
    2. Snapshot Monitoring Example
    3. Snapshot Operation Attributes


Mel Operations List

An operation is a request that a task sends to the CHAOS runtime to do something with a mel element. Mel operations are specified in lower case within the < > markers, and placed before a mel variable. (If placed after a mel variable, it would be a property.) In the following table, the operations are for the mel element represented by the mel variable store. The abbreviation R/W stands for Read/Write:

OperationSynopsis
<close>storeDeallocate the mel element store
<extern>storeRe-create the mel global variable store for a task
<quit>storeEnd the R/W subscriptions to the mel element store
<rebuffer>storeResize the R/W buffer of the mel element store
<resume>Resume R/W waiting on the mel element that causes an exception
<snapshot>storeSnapshot the mel element to update the mel variable

There are two Read / Write mel operations. They make use of the mel wait operator <?>:

OperationSynopsis
<?>storeWait for the mel element store to be available and filled, and retrieve the value of the top slot from the reader's side of the mel buffer.
<?>store =Wait for the mel element store to be available and vacant, and deposit a new value to the next slot from the writer's side of the mel buffer.

Another set of mel operations do not follow the < > markers format above in order to blend with existing C language constructs:

OperationSynopsis
Mel CreationAllocate a mel element and set up a resident mel variable
Mel Argument PassingPass a mel variable by value from one task to another
Mel Global VariablePass a global mel variable from one task to another



Mel Creation Operation

In order to do something with a remote mel element, a task has to first create a resident mel variable to represent the communication channel between itself and the mel element:
<mel> int store1;
<mel buffer=2 at=ssd_drive mode=readonly"> int store2;
The first statement creates a store1 mel variable with default attributes. The second statement creates store2 with explicit attributes:

OperationAttributesSynopsis
<mel> int store Mel creation operation
buffer
=n
Create a buffered mel element with n slots
at
=cel
Create the mel element at the specific cel location
mode
=setting
Create the mel element with this mode setting:
reader: this is the default mode.
readonly: the mel access is always read-only

A mel creation statement is both a compile-time declaration and a run-time operation. Being the latter, it can fail in real time. There are several ways to capture potential mel creation failures:
  1. Check the mel <error> property for non-0 value.
     
  2. Capture a mel exception.
If the mel creation operation is successful, the requesting task has now a functional mel variable with mel properties set to support subsequent mel operations. If the mel creation operation fails, the remote mel element is not allocated, but a resident mel variable is still created to allow the program to check for errors.

The mel variable created after a mel creation operation (either successful or failed), has the following initial values:

Mel Variable Creation Initial Property Values
PropertyValueSynopsis
IN Properties
<priority>0 CHAOS sets the priority property to 0 (starting default).
<timeout>0 CHAOS sets the timeout property to 0 (starting default).
OUT Properties
<value>null The <value> property will be set later by a read operation.
<count>0 The <count> property will be set later by a R/W or <snapshot> operation.
<sequence>0 The <sequence> property will be set later by a R/W or <snapshot> operation.
<status>set The <status> property is NERW_STATUS_OPEN if the mel creation is successful, and NERW_STATUS_CLOSED if the operation has failed.
<condition>0 The <condition> property will be set later by a R/W mel operation.
<error>set The <error> property is set to NERW_ERROR_NONE if the mel creation is successful, or NERW_ERROR_FAILED if the operation has failed.
<why>set The <why> property is null if the mel creation is successful, or contains the reason of the operation failure.
SET Properties
<buffer>set The <buffer> property is set to 1 by default, or to the value of the buffer attribute if specified.
<location>set The <location> property is set by the CHAOS runtime, or to the value of the at attribute if specified.
<readers>-1 The readers subscriptions property is set to -1 for undefined, since no reader task has yet accessed this mel element.
<writers>-1 The writers subscriptions property is set to -1 for undefined, since no writer task has yet accessed this mel element.
CORE Properties
<id>set The <id> property is set by the CHAOS runtime at creation time, and stays the same during the life of the mel element.
<url>set The <url> property is set by the CHAOS runtime at creation time, and stays the same during the life of the mel element.



Mel Passing Operation

Once a mel variable has been created, it can be used in the context of the creating task for any mel operation towards the mel element. If this task forks other tasks to run in parallel, and these tasks also need to access the same mel element, the forking parent task can pass its mel variable to the forked children tasks so that all the involved tasks refer to the same mel element.

Let's revisit the Producer/Consumer example:
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     if ( store<error> ) {
          printf ("Mel [store] invalid due to [%s]", store<why>);
          return;
     }
     while ( <?>store = Produce() );
}
void Consumer (<mel> int store) {
     if ( store<error>  ) {
          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 is one mel element store, but three different mel variables. The main task creates the first mel variable, and then passes it to the Producer and Consumer tasks. Each of these children tasks creates its own mel variable whose properties having their initial values either reset to default or copied from the parent task. The following table shows how the properties are initialized in the mel variable resident in a child task:

Mel Argument Passing Initial Property Values
PropertyValueSynopsis
IN Properties
<priority>Reset CHAOS resets the priority property to 0 (starting default).
<timeout>Reset CHAOS resets the timeout property to 0 (starting default).
OUT Properties
<value>Copied The <value> property is copied from the parent task.
<count>Copied The <count> property is copied from the parent task.
<sequence>Copied The <sequence> property is copied from the parent task.
<status>Copied The <status> property is copied from the parent task.
<condition>Copied The <condition> property is copied from the parent task.
<error>Copied The <error> property is copied from the parent task.
<why>Copied The <why> property is copied from the parent task.
SET Properties
<buffer>Copied The <buffer> property is copied from the parent task.
<location>Copied The <location> property is copied from the parent task.
<readers>Copied The readers subscriptions property is copied from the parent task.
<writers>Copied The writers subscriptions property is copied from the parent task.
CORE Properties
<id>Common The id property is fixed by the CHAOS runtime at creation time, and is common to all mel variables.
<url>Common The url property is fixed by the CHAOS runtime at creation time, and is common to all mel variables.

As the child task conducts its own mel operations towards the mel element, the property values of its resident mel variable will start to diverge from the ones maintained by the parent task and by other child tasks.

Passing By Value

Except for a few properties (<priority> and <timeout>) which are reset, all the other properties have their values copied from the parent task's properties. This is why in NERWous C, mel variables are said to be passed by value between tasks.

The above example shows the pass-by-value in action with the validity checking of the mel variable. If the main task has failed to create the mel element, the <error> property of the eponymous mel variable in the main task context will be set to a non-0 error code and the <why> property contains the description of the error. When the main task forks the Producer and Consumer tasks, the <error> and <why> properties are copied from the main task to the mel variables resident in the Producer and Consumer tasks. These tasks use these valued properties to do the error checking. If the failure checking returns positive, both tasks will end right away without doing their assigned work.

Short Passing By Value

Many times a child task receives all the values of a mel variable passed over by a parent task, but does not care about the stale <value> property that the parent task has cached from its earlier operations against the mel element. This <value> property can be substantial in size, and requires more time to transfer over the nerw network, impacting the performance of the program.

The only information that all child tasks demand from the parent task are those properties that help them connect to the remote mel element. When the parent task just passes this minimum requested properties, the pass is called a short pass. A programmer can specify short passing by using the short attribute:
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel pass=short> int store) {
     if ( store<error> ) {
          printf ("Mel [store] invalid due to [%s]", store<why>);
          return;
     }
    while ( <?>store = Produce() );
}
void Consumer (<mel short> int store) {
     if ( store<error> ) {
          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>);
}
The Producer and Consumer functional tasks now explicitly request short passing for the mel variable argument store. The attribute keyword pass is optional -- while Producer mentions it, Consumer skips it.

These are the initial values of a short-passed mel variable on a child task:

Short Passed Mel Argument Initial Property Values
PropertyValueSynopsis
IN Properties
<priority>Reset CHAOS resets the priority property to 0 (starting default).
<timeout>Reset CHAOS resets the timeout property to 0 (starting default).
OUT Properties
<value>Reset CHAOS resets the <value> property to null.
<count>Reset CHAOS resets the <count> property to 0.
<sequence>Reset CHAOS resets the <sequence> property to 0.
<status>Copied The <status> property is copied from the parent task.
<condition>Copied The <condition> property is copied from the parent task.
<error>Copied The <error> property is copied from the parent task.
<why>Copied The <why> property is copied from the parent task.
SET Properties
<buffer>Copied The <buffer> property is copied from the parent task.
<location>Copied The <location> property is copied from the parent task.
<readers>Copied The readers subscriptions property is copied from the parent task.
<writers>Copied The writers subscriptions property is copied from the parent task.
CORE Properties
<id>Common The id property is fixed by the CHAOS runtime at creation time, and is common to all mel variables.
<url>Common The url property is fixed by the CHAOS runtime at creation time, and is common to all mel variables.

As seen in the table above, the difference between a mel argument regular passing and short passing is the properties related to <value>, such as the <value> property itself and the related properties of <count> and <sequence>.
Side note: The use of short passing above does not much change the performance of our current example because as the parent task, the main task does not make a read to the mel element and therefore still has a null <value> property in its store mel variable.

It is possible that a NERWous compilation system includes an optimizer that analyzes functional task codes and automatically imposes an implicit short passing if the tasks do not make use of the <value>, <count> nor <sequence> property before a read, write or snapshot operation to the mel element. With such optimizer in place, there will be no need to make the code more verbose with explicit short attributes.

Passing By Reference

While mel variable passing between tasks is by value, mel variable passing between functions within a task is by reference. A task has only one mel variable to represent the communication channel between itself and the remote mel element. If the task calls sequential functions that refer to that mel element, these functions will have the resident mel variable passed by reference, forcing all the code within the task to share the same resident mel variable. In the Consumer task above, the function DoConsume updates the same mel variable, and that update is reflected back in the Consumer code block -- both printf the same <status> value.

No Pointers

It is worth mentioning again that there is no concept of a pointer to a mel variable. Between tasks, mel variables are always passed by value, and within tasks, mel variables are always used by reference.


Mel Operations Error Codes

Since a mel operation is a run time activity, it can fail. In NERWous C, such failure can be detected by the programmer by checking the <error> property of the mel variable immediately after a mel operation. When a task requests something to be done with a mel element, it sends the request to the CHAOS runtime via the mel variable. The CHAOS runtime sets the <error> property to 0, carries the operation, and reports any error code back in the same <error> property. If the <error> property remains 0, the operation has been successful. Otherwise, the operation has failed somehow, and the error code of the failure is reflected in the <error> property.

The type of the <error> property is an int. If a programmer wants to work with these error codes specifically (instead of just 0 or not 0), the NERWous C include file nerw.h has the predefined constants.

ExceptionsError CodesSynopsis
NERW_ERROR_NONEThere is no error raised against this successful mel operation.
TIMEDOUTNERW_ERROR_TIMEDOUTThe TIMEDOUT error occurs when the mel operation takes longer than specified.
CLOSEDNERW_ERROR_CLOSEDThe CLOSED error occurs when the mel element has been closed.
NOREADERSNERW_ERROR_NOREADERSThe NOREADERS error occurs when a write operation is requested to a mel element that does not have any subscribing readers.
NOWRITERSNERW_ERROR_NOWRITERSThe NOWRITERS error occurs when a read operation is requested to a mel element that does not have any subscribing writers.
...NERW_ERROR_FAILEDThis is a concatenation of all errors, including errors listed above and undocumented errors.

The value of NERW_ERROR_NONE is 0. The value of NERW_ERROR_FAILED is NERW_ERROR_TIMEDOUT | NERW_ERROR_CLOSED | NERW_ERROR_NOREADERS | NERW_ERROR_NOWRITERS | other undocumented errors. Thus the following statements are equivalent:
if ( store<error> ) printf ("error found");
if ( store<error> != NERW_ERROR_NONE ) printf ("error found");
if ( store<error> & NERW_ERROR_FAILED ) printf ("error found");
To check for a particular error, such as a NERW_ERROR_TIMEDOUT, use the following statements are equivalent:
if ( store<error> & NERW_ERROR_TIMEDOUT ) printf ("timeout error found");
if ( store<error> == NERW_ERROR_TIMEDOUT ) printf ("timeout error found");
The CHAOS runtime will abort an operation on the first error. Since it does not try to find all the possible errors and accumulate the error codes, checking the <error> attribute with bit-wise "and" operator or the equality operator are equivalent. The equality operator checking allows the use of the C switch statement, as will be seen in the Errors in Writes example later.

Errors In Creation

This is an example of using <error> to check the result of a mel creation operation:
main () {
    <mel> int store1, store2;
    if ( store1<error> ) {
        printf ("Failed to create [store1]: [%d] due to [%s]",
            store1<error>, store1<why>);
        exit 411;
    }
    <!> DoubleStoreProcessor(store1, store2);
}
void DoubeStoreProcessor (<mel> in store1, <mel> int store2) {
    if ( store2<error> ) {
        printf ("Failed to create [store2]: [%d] due to [%s]",
            store2<error>, store2<why>);
        return;
    }
    processStore (store1);
    processStore (store2);
}
The task main creates two mel variables, store1 and store2. It handles the error checking for store1, but passes the error checking for store2 to the forked child task, DoubleStoreProcessor.

Errors In Reads

This is an example of using <error> in a mel read operation, with specific mel error codes:
#include <nerw.h>
void Consumer (<mel> int store) {
    while ( 1 ) {
        <? timeout>store;
        if ( store<error> & NERW_ERROR_TIMEDOUT ) {
            printf ("Cannot wait longer. Do something else. ");
            doSomethingElse ();
            printf ("Now try again. ");
            continue;
        }
        else if ( store<error> & NERW_ERROR_FAILED ) {
            printf ("Give up on error [%d] due to [%s],
                    store<error>, store<why>);
            break;
        }
        else if ( store<value> == 0 ) {
            return;     /* stop consumption on 0 value */
        }
        else {
            Consume (store<value>);
            continue;
        }
    }
}
This time we need to include the nerw.h because we are itemizing some of the error codes. This read operation includes a timeout attribute to break a mel wait if the mel element remains empty for too long. Note the use of the <value> property if the mel read is successful, and the <why> property if the read is unsuccessful due to failures besides timeout.

Errors In Writes

This is an example of using <error> in a mel write operation:
void Producer (<mel> int store) {
    int c;
    while ( ( c = Produce() ) ) {
        <? timeout>store = c;
        switch ( store<error> ) {
          case 0: case NERW_ERROR_NONE:
            break;
          case NERW_ERROR_TIMEDOUT:
            printf ("Cannot wait longer. Do something else . ");
            doSomethingElse ();
            printf ("Now try again. ");
            break;
          default:
            printf ("Give up on error [%d] due to [%s],
              store<error>, store<why>);
            return;
        }
    }
}
The Producer task keeps Produce'ing up until a 0-valued product. With a non-0 product, the task sends it to the mel element via a <?>= write statement. Like the Consumer task, this Producer also makes use of the timeout attribute to limit its wait in case the mel buffer remains full for too long, preventing a new product deposit.
Side note: An astute programmer will notice that in the NERW_ERROR_TIMEDOUT case, the break statement will cause the rerun of the Produce function in the while loop, which results in a new product and the loss of the existing product that got timed out. If we don't want to loose the existing product that fails to be written due to the timeout, we can use the NERWous C <resume> operation which attempts to redo the timed-out write operation with the existing product.

No Errors

If the mel operation is successful, the CHAOS runtime will set the <error> property to NERW_ERROR_NONE.


Mel Operations Error Descriptions

When a mel operation failure is detected, the CHAOS runtime sets the <error> property with an error code, and tries to collect more information about the failure. Sometimes there is none, most often the underlying environment will provide a fair amount of information. Whatever CHAOS can collect will be written into the <why> property, overwriting any previous value.

The following example shows a Consumer that collects all the errors during its read access attempts to the mel element:
void Consumer (<mel> int store) {
    char allerr[HUGE];
    while ( 1 ) {
      allerr[0] = '\0';
         int c = <?>store;
         if ( !c ) break;
         if ( store<error> ) {
             strcat (allerr, store<why>);
         else
             Consume (c);
    }
    printf ("All errors [%s]", allerr);
}
The above consumer uses the local allerr to concatenate all the <why> values since a later failure will override the reason of a previous failure in the <why> property.

Before a mel operation, the <why> property is set to an empty string. If the mel operation is successful, the <why> property remains an empty string. If an error has happened, CHAOS will update the <why> property with the cause of the error. If CHAOS runtime cannot collect additional detail about the failure from the underlying environment, it will update the <why> property with a generic note such as "Error detected". Thus, an empty-stringed <why> property can be used to check if an operation is successful or not.


Mel Operations Exceptions

Let's revisit the Consumer task where we check for different error codes.
#include <nerw.h>
void Consumer (<mel> int store) {
    while ( 1 ) {
        <? timeout>store;
        switch ( store<error> ) {
          case NERW_ERROR_NONE:
            if ( store<value> == 0 )
                return;     /* stop on 0 value */
            Consume (store<value>);
            break;
          case NERW_ERROR_TIMEDOUT:
            printf ("Cannot wait longer. Do something else. ");
            doSomethingElse ();
            printf ("Now try again. ");
            <resume>;    /* redo the mel wait */
          case NERW_ERROR_CLOSED:
            printf ("Stop since the mel access has been closed");
            return;
          default:
            printf ("Give up on error [%d] due to [%s],
                store<error>, store<why>);
            return;
        }
     }
}
If there is no error (NERW_ERROR_NONE), the Consumer task checks if the just read product is the last one (e.g. it has a 0 value, per convention with the Producer task). If it is, the task ends; otherwise it consumes the read product. If the wait for the product takes longer than the default timeout (NERW_ERROR_TIMEDOUT), the task runs doSomethingElse and <resume> the wait on the mel element. This Consumer task also adds a check for the mel being closed (NERW_ERROR_CLOSED), so that it can end itself. Other errors are handled in the default case where the task displays all the information it has about the error before calling return to end itself.

Let's rewrite the above example using exceptions:
#include <nerw.h>
void Consumer (<mel> int store) {
    while ( 1 ) {
      try {
          Consume (<? timeout>store);
      }
      catch ( store<TIMEDOUT> )  {
          printf ("Cannot wait longer. Do something else. ");
          doSomethingElse ();
          printf ("Now try again. ");
          <resume>;
      }
      catch ( store<CLOSED> )  {
          printf ("Stop since the mel access has been closed");
          return;
      }
      catch ( store<...> )  {
          printf ("Give up on error [%d] due to [%s],
              store<error>, store<why>);
          return;
      }
    }
}
The list of exceptions corresponds to the list of error codes. A mel exception, such as store<TIMEDOUT> or store<CLOSED>, is specified within the < > markers, and placed after a mel variable, like a mel property. Unlike mel properties which are lower cased, mel exceptions are upper cased.

To follow the footsteps of C derivative languages (such as C++ or C#), NERWous C exceptions are handled by the try/catch construct. With this construct, all the good behaviors can be concentrated in the try section allowing the main logic of the program to be followed without error-checking interruptions; and all the bad behaviors are delegated into various catch sections to ensure that the program is properly strengthened for production runs. For example, let's look at the following code snippet:
    while ( 1 ) {
      try {
          Consume (<? timeout>store1);
          Consume (<?>store2);
      }
      catch ( store1<TIMEDOUT> )  {
          printf ("Cannot wait longer. Do something else. ");
          doSomethingElse ();
          printf ("Now try again. ");
          <resume>;
      }
      catch ( store1<...> )  {
          printf ("Give up on [store1] due to error [%d] with cause [%s],
              store1<error>, store1<why>);
          return;
      }
      catch ( store2<...> )  {
           printf ("Give up on [store2] due to error [%d] with cause [%s],
               store2<error>, store2<why>);
          return;
      }
    }
}
We can easily see that the logic of the code snippet is to Consume the values of two mel elements, with all the error handling listed in the catch sections, some for the mel variable store1, and some for store2. Note that the <TIMEDOUT> exception is only for store1 since its read operation requests a timeout instead of waiting indefinitely as in store2.

Catch-All Exception

The exception <...> is called the catch-all exception. Any exception that is not specifically handled by an explicit catch section goes to the catch-all exception. In the list of exceptions and error codes, the catch-all exception is listed in the same row as the error code NERW_ERROR_FAILED.

There are two types of catch-all handlers: the single mel catch-all and the all mel catch-all.

Single Mel Catch-All

Two examples of the single mel catch-all handler are found in the previous sample:
catch ( store1<...> )
and
catch ( store2<...> )
This type of handler matches the identified mel with all possible exceptions. To find what error triggers the exception to that mel, the <error> and <why> properties are used, such as in the code example above:
catch ( store1<...> ) {
    printf ("Give up on [store1] due to error [%d] with cause [%s],
        store1<error>, store1<why>);
    return;
}

All Mel Catch-All

This type of handler matches all mel elements with all possible exceptions. To find what mel triggers the exception, the as attribute and the <name> property are used. To find what error against that mel causes the exception, the <error> and <why> properties are used:
catch ( <...> as store ) {
    printf ("[%s] encounters error [%d] due to [%s],
        store<name>, store<error>, store<why>);
}
The store mel variable is called a stand-in variable. Any unique name can be used, but whatever that name is, it can only be used inside the handler to represent the actual mel that causes the exception. We will see the as attribute in play in a subsequent example.

Exception Handler Matching

Once an error happens to a mel operation, CHAOS will find a matching handler to process that exception. The matching follows these rules:
  1. The handler must specify the mel under exception
  2. The handler must cover the exception
  3. If multiple handlers match, the more restrictive one is selected.
For example in the previous code snippet, if store1 wait gets timed out, the handler catch ( store1<TIMEDOUT> ) gets selected since it satisfies rules 1 and 2, and is more restrictive than catch ( store1<...> ).

We will revisit the restrictive rule in the chapter on combined reads where we introduce AND and OR combination handlers that vie with the single handlers we see here for matching selection.

Exception Escalation

When an exception occurs, CHAOS will attempt to find a matching exception handler associated with the mel operation. If there is no matching catch handler, CHAOS will escalate the exception within the task. For example, many tasks are written with an envelop try / catch (...) so that there is a last-resort catch-all handler to catch any un-matched exception, and allow the task to end graciously. If there is no matching handler within the task, CHAOS will abort the task.

Let's take a look at this Consumer example:
void Consumer (<mel> int store1, <mel> int store2) {
  try {
    while ( 1 ) {
      try {
          Consume (<? timeout>store1);
          Consume (<?>store2);
      }
      catch ( store1<TIMEDOUT> ) {
          printf ("Cannot wait longer. Do something else. ");
          doSomethingElse ();
          printf ("Now try again. ");
          <resume>;
      }
      catch ( store1<...> ) {
          printf ("Give up on [store1] due to error [%d] with cause [%s],
              store1<error>, store1<why>);
          break;
      }
    }
    return;    /* normal task ending */
  }
  catch ( <...> as store )  {
    printf ("[%s] encounters error [%d] due to [%s],
       store<name>, store<error>, store<why>);
    return;   /* graceful task ending */
  }
}
Inside the while loop, the above Consumer has a specific handler for the TIMEDOUT exception on store1 and a catch-all handler for any other store1 exception. It does not have any handler for store2. If there is an exception on store2 during the read statement
<?>store2
CHAOS will force Consumer to exit the while loop to look for a matching handler. In fact, there is one. The envelop catch-all handler
catch ( <...> as store )
matches all exceptions of all mels, and will process the store2 exception. Using the as attribute (with store as the stand-in mel), this handler can display store2 name, the error code and the reason for the error.

If there is no matching handler within the task, CHAOS will end the task abnormally, generating a pel exception. This exception will be caught by any task doing a pel wait on the abnormally ending task. Right now we are focusing on memory elements (mels) and their exceptions. Processing elements (pels) and related exceptions will be the topics of future chapters.

Exceptions Clearing

Once an exception is matched with a handler and processed, the exception is cleared. If the mel operation is inside a loop, the exception from the last iteration will not cause a jump into a handler on the next iteration. Let's look at the following example:
void Consumer (<mel> int store) {
    while ( 1 ) {
      try {
          Consume (<? timeout>store);
      }
      catch ( store<TIMEDOUT> ) {
          printf ("Cannot wait longer. Do something else. ");
          doSomethingElse ();
          printf ("Now try again. ");
      }
      catch ( store<CLOSED> ) {
          printf ("WARNING: the mel access has been closed");
      }
      continue;   /* loop back */
    }
}
The above Consumer task runs an infinite loop. If during an iteration, the mel store gets longer to be valued, and a TIMEDOUT exception occurs, the TIMEDOUT handler will be invoked. On jump-out of the handler, the TIMEDOUT exception is cleared. The continue statement loops back the while loop, to wait for the mel store anew. Any TIMEDOUT exception now is due to this wait, and not from the previous mel wait.

If the mel store has been closed, future iterations will get the CLOSED exception again, and the warning message keeps getting printf'ed. However this is not due to the previous CLOSED exception not being cleared, but due to CHAOS detecting that the mel has been closed on the new mel wait. In other words, while the CLOSED exception has been cleared on jump-out of the handler, the state of the mel being closed still remains, causing recurring jump-ins into the handler. The programmer should have a break statement at the end of the CLOSED handler to get off the while loop.

Exceptions vs. Errors

When a failure occurs, the mel operation is aborted and an error is generated. If the error is within a try / catch construct, an exception is generated, and the exception escalation process takes over. If the mel operation is not wrapped with a try / catch construct, exception handling does not come into play, and code fortification will depend solely on <error> property checking.

Let's repeat this earlier example:
main () {
    <mel> int store1, store2;
    if ( store1<error> ) {
        printf ("Failed to create [store1]: [%d] due to [%s]",
            store1<error>, store1<why>);
        exit 411;
    }
    <!> DoubleStoreProcessor(store1, store2);
}
void DoubeStoreProcessor (<mel> in store1, <mel> int store2) {
    if ( store2<error> ) {
        printf ("Failed to create [store2]: [%d] due to [%s]",
            store2<error>, store2<why>);
        return;
    }
    processStore (store1);
    processStore (store2);
}
The task main creates two mel variables, store1 and store2. It handles the error checking for store1 creation failures, but passes the error checking for store2 to the forked child task, DoubleStoreProcessor.

Let's now modify the main task by wrapping its code within a try / catch envelop:
main () {
  try {
    <mel> int store1, store2;
    if ( store1<error> ) {
        printf ("Failed to create [store1]: [%d] due to [%s]",
            store1<error>, store1<why>);
        exit 411;
    }
    <!> DoubleStoreProcessor(store1, store2);
  }
  catch (<...> as store) {
      printf ("Failed to create [%s]: [%d] due to [%s]",
          store<name>, store<error>, store<why>);
      exit 911;
  }
}
If store1 fails to be created, the code block of if ( store1<error> ) { ... } with a 411 exit, is never entered again. Instead, the exception escalation process takes over, and the program flow jumps into the catch-all handler with a 911 exit.

If store2 fails to be created, the failure is not propagated to DoubleStoreProcessor. Instead, the exception handler of the main task takes over, and the program flow again flows into the catch-all handler with the 911 exit.

Adding a try / catch structure does not change the positive flow of the main task, when everything flows flawlessly. It does change the negative flow when there is an error happening. Any code that is introduced by or depends on checking the <error> property may be subsumed by an exception handling code.


Mel Snapshot Operation

The mel properties of a mel variable are automatically updated after a mel operation, and then cached within the context of the requesting task until the task makes another mel operation request. Between this time, the remote mel element has probably changed due to its interactions with other tasks. To capture these changes without doing an intrusive mel operation, an inquisitive task can invoke the non-intrusive <snapshot> operation.

Snapshot Updated Properties

The following table shows what properties will be updated by <snapshot>:

PropertyUpdated?Synopsis
IN Properties
<priority>Yes CHAOS resets the priority property to 0 since the <snapshot> operation does not run with a priority.
<timeout>Yes CHAOS resets the timeout property to 0 since the <snapshot> operation does not run with a timeout.
OUT Properties
<value>Yes The top value at the reader's end of the mel buffer is returned, without that value being removed from the buffer as in a read operation. CHAOS sets this property to NULL if the mel element is EMPTY.
<count>Yes If the returned <value> is an array, the number of items in the array is reflected in the <count> property.
<sequence>Yes If <value> is updated, this property contains its version identifier. Otherwise CHAOS resets it to 0.
<status>Yes Current state of the mel element
<condition>Yes Current state of the mel element (same as <status>)
<error>Yes Error code of the <snapshot> operation. If the operation is successful, CHAOS sets this property to NERW_ERROR_NONE.
<why>Yes Failure reason of the <snapshot> operation. If the operation is successful, CHAOS resets this property to an empty string.
SET Properties
<buffer>Yes A <rebuffer> operation may have resized the mel buffer.
<location>Yes The current CHAOS-defined locality of the mel element. On platforms with high reliability, this value may have changed if the original locality has encountered problems and the platform fails over to another locality.
<readers>Yes The number of readers subscriptions may have changed.
<writers>Yes The number of writers subscriptions may have changed.
CORE Properties
<id>No This property is fixed by the CHAOS runtime.
<url>No This property is fixed by the CHAOS runtime.

A <snapshot> operation looks similar to a readonly operation, with some big differences. The <snapshot> request goes to the snapshot queue which is the highest priority queue of the mel element, and this request does not wait for the mel element to be FILLED -- if there is no value to be read, it returns right away with a NULL value for the <value> property, and the NERW_STATUS_EMPTY for the <status> and <condition> properties. On the other hand, the readonly operation goes to the readonly queue which is at a lower priority than the snapshot queue, and it waits until there is a value in the mel buffer to be read (or until the specified timeout occurs).

A Snapshot Monitoring Example

The simplistic task below illustrates the use of <snapshot> to monitor the state of a mel element.
void Monitor (<mel> int store) {
   while (1) {
      <snapshot> store;
      if ( store<status> &  NERW_STATUS_EMPTY ) {
          printf ("The mel buffer is empty\n");
          printf ("A reader task will likely have to wait\n");
      }
      else {
          printf ("The mel buffer contains a value\n");
          printf ("A reader task will likely not have to wait\n");
      }

      if ( store<status> &  NERW_STATUS_FULL ) {
          printf ("The mel buffer is full\n");
          printf ("A writer task will likely have to wait\n");
      }
      else {
          printf ("The mel buffer has an available slot\n");
          printf ("A writer task will likely not have to wait\n");
      }

      if ( store<status> &  NERW_STATUS_CLOSED ) {
          printf ("The mel element is closed\n");
          printf ("All tasks will have their mel requests failed\n");
      }

      sleep (60000);    /* wait 1 min and check again */
   }
}
The above Monitor is a sampling monitor. Between the one-minute gaps, there could be writers that deposit new values to the mel store, and readers to take them out. The Monitor only captures the state of the mel element at its one-minute marks.

Snapshot Operation Attributes

The <snapshot> operation has only one attribute:

OperationAttributesSynopsis
<snapshot> store Mel snapshot operation
on
=store2
Duplicate the properties of store2 onto store.

The <snapshot> default operation (without the on attribute) re-initializes the given mel variable with values direct from the associated remote mel element. When used with the on attribute, the <snapshot> operation initializes (or re-initializes) the given mel variable with property values from another mel variable.

The mel variable argument to on must be in scope of the invoking task. This means that it can only be a local mel variable resident in the same task, or a global variable known by all tasks. What it cannot be is a local mel variable of another task.

The on attribute is seldom used explicitly in a program code. It is available for behind-the-scene support for the <extern> statement for global mel variables.

It is worth reminding that NERWous C does not support pointers to mel variables. The <snapshot> on duplicates the properties of a mel variable (most importantly the CORE properties), resulting in two separate mel variables associating themselves to the same remote mel element.


Previous Next Top