Pages

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

No comments:

Post a Comment