Pages

Friday, September 6, 2019

Mel Combined Reads

Welcome » NERWous C » Mel
  1. Mel Multiple Reads
  2. Mel OR Reads
    1. Parallel OR Read
    2. Serial OR Read
    3. OR Read Methods Summary
  3. OR Read Exceptions
    1. OR Read TIMEDOUT Exceptions
    2. OR Read CLOSED Exceptions
  4. Asynchronous Mel OR Reads
  5. Mel AND Reads
  6. Cumulative AND Reads
    1. Cumulative AND Reads Timeouts
    2. Cumulative AND Reads Closeouts
  7. Simultaneous AND Reads
    1. Simultaneous AND Reads Timeouts
    2. Simultaneous AND Reads Closeouts
  8. Asynchronous Mel AND Reads
    1. Error Handling Asynchronous AND Reads
    2. Exception Handling Asynchronous AND Reads


Mel Multiple Reads

Let's create a greedy Consumer that Consumes from two Producer tasks at the same time:
main () {
     <mel> int store1;
     <mel> int store2;
     <!> Producer (store1);
     <!> Producer (store2);
     <!> Consumer (store1, store2);
}
void Producer (<mel> int store) {
     while ( <?>store = Produce() );
     <close>store;
}
void Consumer (<mel> int store1, <mel> int store2) {
     bool store1valid = true;
     bool store2valid = true;

     while ( 1 ) {
        if ( store1valid ) {
           try {  Consume(<? timeout>store1); }
           catch ( store1<TIMEDOUT> ) {}
           catch ( store1<CLOSED> ) { store1valid = false; }
        }

        if ( store2valid ) {
           try {  Consume(<? timeout>store2); }
           catch ( store2<TIMEDOUT> ) {}
           catch ( store2<CLOSED> ) { store2valid = false; }
        }

        if (!store1valid && !store2valid) break;     // no more producers
     }
}
The Consumer uses a default timeout in both its mel reads. If the first Producer is too slow, the timeout exception fires and allows the Consumer to break out from the mel wait. It then tries the other Producer.

The Consumer gets a CLOSED exception if the corresponding Producer has closed the mel element. The Consumer remembers this CLOSED status in a local variable. On future iterations, the Consumer skips the closed mel element and works with the other Producer only. When the Consumer has detected that both mel elements have been closed, it will break out of the infinite loop and ends itself.

For a more compact code albeit less efficient, we can check the mel property CLOSED instead of using a local variable:
/* VERSION A */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try { Consume(<? timeout>store1); }
    catch ( store1<TIMEDOUT> ) {}
    catch ( store1<CLOSED> ) {}

    try { Consume(<? timeout>store2); }
    catch ( store2<TIMEDOUT> ) {}
    catch ( store2<CLOSED> ) {}

    if (store1<CLOSED> && store2<CLOSED>) break;
  }
}
The cleaner code is easier to follow. The price paid is a performance reduction. Even though a mel element has been closed, the Consumer will continue to check it at each iteration and immediately gets the CLOSED exception that it has to handle again. All this repetitive computation takes more time than simply checking a local variable that "remembers" the closed status.

A CLOSED or TIMEDOUT exception to a mel wait is cleared when the task jumps out of the corresponding handler. For example, if Consumer has detected that store1 has closed, the store1<CLOSED> exception is raised during that iteration. The corresponding CLOSED handler does not have any visible code, but it does serve two purposes: it prevents the exception to become escalated, and it clears the exception. On the next iteration of the loop, when Consumer invokes Consume(<? timeout>store1) again, the store1<CLOSED> exception is raised again when the mel wait detects that the mel element has indeed been closed. This back-and-forth processing continues until the Consumer task detects that both mel elements have been closed. Only at that time, it breaks out of the while loop, and ends.


Mel OR Reads

The Version A of Consumer is very deterministic: it Consumes store1 then store2. If a mel element is empty, it depends on the TIMEDOUT exception to stop the wait on that mel element and switch to the other mel wait. In Version B below, the reading from two (or more) elements can be written more compactly using the mel OR read feature.
/* VERSION B */
void Consumer (<mel> int store1, <mel> int store2) {
  int c;
  while ( 1 ) {
    try {
      c = <? timeout>(store1 || store2);
      Consume(c);
    }
    catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
An even more compact version of Consumer removes the intermediary local variable c:
/* VERSION C */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try { Consume(<? timeout>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
Parallel OR Read

The default method for a mel OR read is the parallel method. This method checks all the listed mel elements at the same time, and pick the first one that is read ready.

This is its behind-the-scene process for the sample statement below:
<? timeout>(store1 || store2):
  1. The Consumer task puts itself to both store1 and store2 default readers' queues with the default timeout value.
     
  2. If the mel READ conditions are met at, for example store1, the Consumer retrieves the mel value from that mel buffer, gets off the store1 queue, aborts its wait on the store2 mel by getting off the store2 queue, exits the mel OR wait statement, and Consumes the store1 retrieved mel value. Similar processing is done if the mel READ conditions are met first at store2.
     
  3. If the mel READ conditions are not met at neither store1 nor store2, the Consumer will wait on both queues. When the mel READ conditions become true for either one of the mel variables, the Consumer task retrieves the mel value from that mel buffer, gets off both queues, exits the mel OR wait statement, and Consumes the retrieved mel value.
     
  4. If it happens that the mel READ conditions become true at both mel elements at the same time, the CHAOS runtime will ensure that Consumer will retrieve value from one or the other mel element, and not both.
     
  5. If the mel READ conditions are not met at neither store1 nor store2 by the timeout period, the Consumer task will get off both queues, and raises the <TIMEOUT> exception. This will be explored in a later section.
Serial OR Read

NERWous C supports another method called the OR serial read method. Here, the first listed mel is checked first. If it is not read ready, then the next listed mel is checked. Each mel is checked serially in the listed order until one is found read ready, or none is. If none of the mels are ready on the first pass, the task will do the OR parallel read and wait for the first mel that is read ready.

To re-emphasize, the serial checking is applicable only on the first pass. After that, the serial method behaves like the parallel method.

OR Read Methods Summary

Before moving on, let's summarize about the methods available for use in a mel OR read. The selected method is specified via the method attribute:
/* VERSION D */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try { Consume(<? timeout method=serial>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
This table summarizes the values of the method attribute:

Mel OR Read Methods
MethodDescription
parallelThis is the default method if the method attribute is not specified. It gives all the listed mel elements a fair chance to be picked.
serialTo be in use, this method must be explicitly specified with the method attribute. This method establishes a hierarchy so that the first listed mel is the primary source, and the subsequent mels are backup sources. It is possible that none of the subsequent mels will be picked if the first listed mel is always read ready on the first pass.
customA NERWous implementation may support a custom method specific to a particular underlying platform. If a custom method is not recognized, NERWous C will fall back to the default method.

Since the values for the method behavior are reserved, their appearance in a mel OR read statement is sufficient without the use of the keyword method. We can simplify Version D as follows:
/* VERSION E */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try { Consume(<? timeout serial>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
The examples for the mel OR reads make use of exception handling. This is now time to explore them in details.


OR Read Exceptions

Exceptions against mel OR reads are handled following these rules:
  • Skipping rule: if an exception happens to a mel element, and there is no specific handler for that mel exception, the CHAOS runtime will remove the task from waiting on that mel element. The mel OR read continues with the mel elements that have not had the exception. In other words, the program flow of the task still remains in the mel OR read operation.
     
  • Aborting rule: if there is a specific handler for that mel exception, the mel OR read is aborted. The task gets off all the waiting mel queues, including those that belong to mel elements that do not have exceptions; and the task then processes the identified handler. In other words, the program flow of the task moves out of the mel OR read operation into the exception handler.
     
  • Escalation rule: if an exception happens to the last remaining mel element in a mel OR read, and there is no handler for that exception, the mel OR read is aborted with an unhandled exception. This exception escalates up the program flow until there is a handler. If no handler is found, the escalation will cause the task to abort (i.e. end abnormally).
     
  • Precedence rule: when an exception can be processed by multiple handlers, the more restrictive handler gets the work. Let's use the TIMEDOUT exception with two mel elements, store1 and store2, as an example:

    1. If there is one, a matching AND combination exception handler, such as:
      catch ( (store1 && store2)<TIMEDOUT> ) { ... }
      will be picked first. This is the most restrictive handler since all the mels in the AND list must have that exception.
       
    2. The next handlers are single mel handlers, such as:
      catch ( store1<TIMEDOUT> ) { ...}
      catch ( store2<TIMEDOUT> ) { ... } 
      If more than one single mel handlers match, CHAOS will arbitrarily pick one to proceed with the abortion rule.
       
    3. The last handlers are the OR combination exception handlers, such as:
      catch ( (store1 || store2)<TIMEDOUT> ) { ... }
      These are the least restrictive handlers since they match any of the listed mels, here either store1 or store2.

We will get to know more about these rules by looking at the TIMEDOUT and CLOSED exceptions during mel OR reads.

OR Read TIMEDOUT Exceptions

Common Timeout

When a task requests a mel OR read, it can include a timeout value, such as in:
Consume(< timeout>(store1 || store2))
Consume (< timeout=1000>(store1 || store2))
Only one single timeout can be specified, and this same timeout value applies to all the mel waits in the OR list. W hen the time is up, all the mel waits will trigger the TIMEDOUT exception. If there are many matching single mel exceptions, CHAOS will arbitrarily pick one handler to process, as stated by the Precedence rule.

Timeout Extension

Let's modify the Consumer example again:
/* VERSION F */
void Consumer (<mel> int store1, <mel> int store2) {
  int mypatience = 10; /* msec */
  while ( 1 ) {
    try { Consume(<? timeout=mypatience>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) {
       printf ("Increase my patience");
       mypatience += 5;
    }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
On the first round, Consumer is willing to wait for 10 msec for either store1 or store2 to be valued. If the timeout occurs before this can happen, Consumer jumps into the OR combination TIMEDOUT handler. It displays a printf message, and increases the amount it is willing to wait for another 5 msec. When it gets off the handler, Consumer iterates the while loop to retry the mel OR read, now with the expanded timeout value.

Timeout Combination Handler

In the above example, Consumer uses an OR combination handler to catch the TIMEDOUT exception of a mel OR read. It could also use an AND combination handler, as seen in the following example:
/* VERSION G */
void Consumer (<mel> int store1, <mel> int store2) {
  int mypatience = 10; /* msec */
  while ( 1 ) {
    try { Consume(<? timeout=mypatience>(store1 || store2)); }
    catch ( (store1 && store2)<TIMEDOUT> ) {
       printf ("Increase my patience");
       mypatience += 5;
    }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
An AND combination handler is entered only if all the listed mel elements have caught the exception. Due to the timeout being common, all mel waits time out at the same time, satisfying the AND condition. Thus, for the TIMEDOUT exception, the OR catch and the AND catch are equivalent. This may not be the case for other types of exceptions, such as the CLOSED exception we will discuss in the next section.

What happens if the Consumer task has both the OR combination and the AND combination TIMEDOUT handlers, like this version:
/* VERSION H */
void Consumer (<mel> int store1, <mel> int store2) {
  int mypatience = 10; /* msec */
  while ( 1 ) {
    try { Consume(<? timeout=mypatience>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) {
       printf ("Increase my patience a lot ");
       mypatience += 500;
    }
    catch ( (store1 && store2)<TIMEDOUT> )
       printf ("Increase my patience a little");
       mypatience += 5;
    }
    catch ( (store1 && store2)<CLOSED> ) { break; }
  }
}
Based on the Precedence rule, the AND combination handler takes precedence over the OR combination handler since it is more restrictive by nature. Thus, the timeout value is increased by 5 instead of 500 in the above example.

OR Read CLOSED Exceptions

Let's revisit our full Producer/Consumer example:
main () {
  <mel> int store1;
  <mel> int store2;
  <!> Producer (store1);
  <!> Producer (store2);
  <!> Consumer (store1, store2);
}
void Producer (<mel> int store) {
  while ( <?>store = Produce() );
  <close>store;
}

/* VERSION I */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    printf ("Start new iteration");
    try {  Consume(<? timeout>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) {
      printf ("Wait too long"};
      <resume>
    }
    catch ( (store1 && store2)<CLOSED> ) {
      break;
    }
  }
}
In the above example, we have two separate Producer tasks, one Produce'ing for store1, and the other for store2. We have one single Consumer task that Consumes either from store1 or store2, whatever is available at the moment. This Consumer has only one exception handler for the CLOSED exception:
catch ( (store1 && store2)<CLOSED> )
Independent Closeouts

Unlike the TIMEDOUT exception case where one mel wait timing out means that all the mel waits have also timed out, the CLOSED exception happens individually for each mel wait. One Producer task may have closed its associated "store" mel element, but the other Producer may still merrily Produce'ing for the other "store".

When CHAOS detects that a mel has closed, it looks for a matching CLOSED exception to process. In our example here, there is none since the specified AND combination handler requires both mels to be closed. In this case, CHAOS will apply the Skipping rule for mel OR reads. It removes the task from the waiting queue of the closed mel, but keeps the task waiting for the other active mel element(s). Since a mel OR read only needs one mel to satisfy the mel READ conditions, the task can stay with the mel OR read operation, instead of being aborted.

In our example, let's assume that store1 has been closed. The programmed AND combination handler is not matched since it requires that both store1 and store2 to be closed, and store2 is still operative. Eventually, store2 is valued and the Consumer task will Consume this value, and iterates the while loop. The mel OR read is again attempted. This time, CHAOS notices that store1 has been closed, so it will not put the Consumer task into store1 waiting queue. In this and subsequent iterations, the mel OR read on store1 and store2 is practically a single mel read on store2.

Specific Closeouts

With the Skipping rule, Consumer may not care if a mel in the OR list has closed. What if it does? Let's modify Consumer again:
/* VERSION J */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    printf ("Start new iteration");
    try {  Consume(<? timeout>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) {
      printf ("Wait too long"};
      <resume>;
    }
    catch ( store1<CLOSED> ) {
      printf ("[store1] has closed");
      <resume>;
    }
    catch ( store2<CLOSED> ) {
      printf ("[store2] has closed");
      continue;
    }
    catch ( (store1 && store2)<CLOSED> ) {
      break;
    }
  }
}
The new version of Consumer wants to do a printf of any mel in the OR list that has closed. It achieves this by having two single mel exception handlers, one for store1<CLOSED> and the other for store2<CLOSED>.

After processing the single exception handler, the Consumer task still wants to wait for a value from the other operative mel element so that it can eventually Consume that mel's value. The store1<CLOSED> handler uses the NERWous C <resume> operation to directly retry the mel OR wait operation, bypassing the while loop iteration. The store2<CLOSED> handler uses the C language continue statement which iterates the while loop, to allow it to printf another "Start new iteration" message.

In both single handler cases, the Aborting rule will cause Consumer to also abort the waiting on the other mel that has not closed, and the program flow goes back to the Consumer task. If there is no matching handler, then the Skipping rule takes precedence. The program flow stays with the CHAOS runtime, which simply skips the wait on any closed mels. Only when the last mel is closed, then CHAOS releases the program flow back to the task. Unless the task has a matching handler (such as an AND combination CLOSED handler), the exception will escalate per Escalation rule.

Combined Closeouts

An alternative to develop single CLOSED exception handlers for each mel element in the OR list, is to write an OR combination CLOSED handler. Let's modify Consumer again:
/* VERSION K */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    printf ("Start new iteration");
    try {  Consume(<? timeout>(store1 || store2)); }
    catch ( (store1 || store2)<TIMEDOUT> ) {
      printf ("Wait too long"};
      <resume>;
    }
    catch ( (store1 || store2)<CLOSED as=store> ) {
      printf ("[%s] has closed", store<name>);
      <resume>;
    }
    catch ( (store1 && store2)<CLOSED> ) {
      break;
    }
  }
}
The OR combination CLOSED handler makes use of the as attribute and the <name> property. When either store1 or store2 is closed, this handler is matched, and the stand-in store variable will be mapped to the mel variable of the closed mel. This allows the handler to use the <name> property to show the name of the closed mel.

On the first mel that has closed, the OR combination CLOSED handler is matched, and the handler wraps up with a <resume> operation to continue the wait on the other still operative mel element. If later on, this mel element is closed too, the AND combination CLOSED handler is then matched to break out of the infinite while loop.


Asynchronous Mel OR Reads

Like the single mel asynchronous reads, the combination mel OR reads can also be invoked asynchronously. Let's look at this code snippet:
<? async parallel as=store>(store1 || store2);
doSomething ();
Consume (<?>store);
The first thing to notice is that the mode and method keywords have been omitted, since their values can uniquely identify them. The full mel OR read statement is as follows:
<? mode=async method=parallel as=store>(store1 || store2);
When a mel OR read is invoked asynchronously, the as attribute with a stand-in value (here it is store) is required. This allows the task to use the stand-in mel variable to check for the outcome of outsourcing the mel OR wait to the CHAOS runtime.

In the code snippet above, the task invokes the mel OR read asynchronously, and while CHAOS does the mel OR wait for its stead, the task does a doSomething chore. Once the chore is done, it issues the read statement <?>store against the stand-in mel to check for the result of the mel OR read. If CHAOS has successfully done the read, the task will get the value from the local store<value> property. Because this is an OR read, this value can come from either the mel element store1 or store2. If CHAOS cannot do the read yet, then the task takes over the mel OR read of store1 and store2. Instead of re-issuing the full mel OR read list, the task uses the stand-in store mel variable.

Instead of taking back the mel OR wait too soon, the task may be able to conduct several rounds of doSomething by polling the wait:
<? async as=store>(store1 || store2);
while ( store<error> == NERW_ERROR_PENDING ) {
   doSomething ();
}
Consume (store<value>);
printf ("CHAOS gets the value from [%s]", store<name>);
In the new code snippet, the task after issuing the asynchronous mel OR read, keeps polling for the success of the read by checking the <error> property of the stand-in mel variable. On entry of a mel OR read, the <error> property is set to NERW_ERROR_PENDING.
Side note: Without the stand-in variable, the polling check is more verbose:
while ( (store1<error> == NERW_ERROR_PENDING ) && (store2<error> == NERW_ERROR_PENDING) )
The verbose statement is also error-prone, with an OR (||) clause wrongly used instead of the correct AND (&&).
If CHAOS is still working on the request, the task can do its doSomething chore. It checks back at the end of the chore, and keeps doing doSomething for each while loop iteration.

When CHAOS has done with the waiting in one mel, it changes the <error> property of the corresponding mel variable from NERW_ERROR_PENDING to NERW_ERROR_NONE if the read is successful, or to one of the NERW_ERROR_FAILED code to mark failure.

In the above snippet, the Consumer task assumes the asynchronous mel OR read is always successful; thus it expects a read value in the stand-in mel variable's <value> property. It can also know from which mel variable the read value comes from by checking the <name> property -- it is set to the string value "store1" or "store2".

Always assuming success is not robust code. Let's throw in some error checking in the code snippet:
<? async as=store>(store1 || store2);
while ( store<error> != NERW_ERROR_NONE ) {
   doSomething ();
   if ( store<error> & NERW_ERROR_FAILED ) break;
}
if ( store<error> == NERW_ERROR_NONE ) {
   Consume (store<value>);
   printf ("CHAOS gets the value from [%s]", store<name>);
}
else {
   printf ("Mel OR Read has failed on [%s] due to [%s]",
       store<error>, store<why>);
}
We can also use the try/catch exception method:
<? async as=store>(store1 || store2);
try {
   while ( store<error> != NERW_ERROR_NONE ) {
      doSomething ();
   }
   Consume (store<value>);
   printf ("CHAOS gets the value from [%s]", store<name>);
}
catch ( store<...> ) {
   printf ("Mel OR Read has failed on [%s] due to [%s]",
       store<error>, store<why>);
}
If there is an error during the asynchronous mel OR read, the exception is triggered when the task checks on the store<error>. It is not triggered during the doSomething chore.


Mel AND Reads

While the mel OR reads are for getting a value from one of the listed mel elements, the mel AND reads are for getting values from all of them. The AND mel read supports the following methods to process the listed mel elements:

Mel AND Read Methods
Method ValueDescription
CumulWith the cumulative method, the task checks all the mel elements in the AND list, and accumulates the read values as they become available. This method is efficient, and is the default method.
SimulWith the simultaneous method, the task checks all the mel elements in the AND list, wait for all of them to be available, then reads all of them at once. This method is more complex but does prevent deadlocks.
customA NERWous implementation may support a custom method specific to a particular underlying platform. If a custom method is not recognized, NERWous C will fall back to the default method.

To illustrate the mel AND read, let's first replace the Consume function which takes one argument, with the ConsumeTwo function which takes two arguments:
main () {
  <mel> int store1;
  <mel> int store2;
  <!> Producer (store1);
  <!> Producer (store2);
  <!> Consumer (store1, store2);
}
void Producer (<mel> int store) {
  while ( <?>store = Produce() );
  <close>store;
}

/* VERSION 1 */
void Consumer (<mel> int store1, <mel> int store2) {
  int c1, c2;
  while ( 1 ) {
    try {
      c1 = <? timeout>store1;
      c2 = <? timeout>store2;
      ConsumeTwo (c1, c2);
    }
    catch ( store1<TIMEDOUT> ) {
      printf ("Can't get store1 in time");
      <resume>;  /* retry store1 */
    }
    catch ( store2<TIMEDOUT> ) {
      printf ("Got store1 value of [%d]\n", store1<value>);
      printf ("Can't get store2 in time");
      <resume>;  /* retry store2 */
    }
    catch ( (store1 || store2)<CLOSED> ) {
      break;  /* stop on first closure */
    }
  }
}
The Consumer task first waits for store1. Once it gets the value from store1, it waits for store2. Once it gets both values, it will invoke the serial function ConsumeTwo with the two values as input arguments.

We now make Consumer more compact by using the mel AND read construct:
/* VERSION 2 */
void Consumer (<mel> int store1, <mel> int store2) {
  int c1, c2;
  while ( 1 ) {
    try {
      /* The mel AND wait returns two values to initialize c1 and c2 */
      (c1, c2) = <? timeout as=store>(store1 && store2);
      ConsumeTwo (c1, c2);
    }
    catch ( store<TIMEDOUT> ) {
      printf ("Can't get %s in time", store<name>);
      <resume>;  /* retry store1 or store2 or both */
    }
    catch ( store<CLOSED> ) {
      printf ("%s has closed", store<name>);
      break;  /* stop on first closure */
    }
  }
}
Comparing the two versions of Consumer, the AND mel read version offers the benefit that all listed mels are waited in parallel, instead of serially one after the other, thus potentially cutting down on the overall wait time.
Side note: A computer linguist will note that the C language does not support the return of multiple values. A C function returns at most one value. Let's say that NERWous C has taken the liberty to expand the language with the support of multiple returns from a function. The multiple returns are enclosed within the ( ) markers. When checking for true and false, if all the returned values are true then the multiple return is true. If any returned value is false, then the multiple return is false.
Notice the use of the stand-in mel variable, store. This allows the condition for a handler exception to be written more succinctly. For example, instead of catch (store1 || store2)<TIMEDOUT>, we have catch (store<TIMEDOUT>). The identity of the mel that causes the exception can be found via the stand-in mel variable properties, <name> and <id>.

The version above can be further shortened:
/* VERSION 3 */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try {
      ConsumeTwo (<? timeout as=store>(store1 && store2));
    }
    catch ( store<TIMEDOUT> ) {
      printf ("Can't get %s in time", store<name>);
      <resume>;  /* retry store1 or store2 or both */
    }
    catch ( store<CLOSED> ) {
      printf ("%s has closed", store<name>);
      break;  /* stop on first closure */
    }
  }
}
Without an explicit method, the AND mel wait uses the default cumulative method. The following statements are equivalent:
<?>(store1 && store2);
<? cumul>(store1 && store2);
<? method=cumul>(store1 && store2);
If the simultaneous method is desired, it has to be explicitly specified. The following statements are equivalent:
<? simul>(store1 && store2);
<? method=simul>(store1 && store2);
Since cumul and simul are unique keywords, NERWous C can associate them with the attribute method, which can then be dropped.


Cumulative AND Reads

Let's look at the behind-the-scene process of the cumulative method of a mel AND read with a timeout specified, such as:
<? timeout>(store1 && store2);
  1. The task puts itself to the default readers' queues of all the listed mels, with the specified timeout value. All the corresponding mel variables have their <error> property reset to NERW_ERROR_PENDING, per On Read Entry setting.
     
  2. If the mel READ conditions are met for one of the mel elements, CHAOS will do the read operation on that element. The top value of the mel buffer is removed and deposited into the <value> property of the corresponding mel variable. Its <error> property is changed to NERW_ERROR_NONE to denote that the read has been successful. The task takes itself off the wait queue of that mel element.
     
  3. The task collects available read values as they happen, and continues to wait at the other mels' queues until those mels also meet the mel READ conditions.
     
  4. Once the task gets a value of all the mels in the AND list, it gets off the AND mel read, and resumes the program's code flow. All the corresponding mel variables have their <value> property updated and their <error> property set to NERW_ERROR_NONE to mark all successful reads.
     
  5. The AND mel read can also ends with a failure, with the task not receiving all the values from the requested mels in the AND list. The first failure from any mel will cause the AND mel read to fail. For a cumulative method, when the task gets off an AND mel read with a failure, the mel variables of the listed mels have the following properties settings:

    Successful reads
    Mels that have been successfully read have their corresponding mel variables with <value> property updated and <error> property changed to NERW_ERROR_NONE.
    Failed reads
    The mel that causes the failure has its corresponding mel variable with <value> property unchanged from entry, and <error> property set to the error code of the failure.
    Un-filled reads
    Mels that are still waiting to be read have their corresponding mel variables with <value> property unchanged from entry, and <error> property remaining at NERW_ERROR_PENDING.
Let's explore how a task handles some of the failures with the cumulative method:

Cumulative AND Reads Timeouts

Like all mel reads, timeouts are optional for mel AND reads. The task has to request it by specifying the timeout attribute in the mel AND read, such as:
<? timeout=10>(store1 && store2);
These are some notes about timeouts used with cumulative AND reads:
  1. A single timeout is applied to all the mel waits.
     
  2. Any mels that have been read before the timeout occurs, will be removed from the timeout consideration. Their corresponding mel variables have the <value> property updated and the <error> property changed to NERW_ERROR_NONE.
     
  3. When the timeout occurs, all remaining mel waits will time out, due to the single common timeout attribute. Their corresponding mel variables have the <value> property unchanged and the <error> property set to NERW_ERROR_TIMEDOUT.
     
  4. When a task gets off an AND mel read due to a timeout exception, the <error> property of the mel variables are either NERW_ERROR_NONE if a read has been done, or NERW_ERROR_TIMEDOUT if a read has not happened yet. There will be no mel variable with the <error> setting of NERW_ERROR_PENDING.
     
  5. When a timeout occurs, the task gets off all the remaining mel waits, and CHAOS raises a TIMEDOUT exception against the task. If the mel AND read has a matching handler, the programming flow will jump to the handler code. Otherwise, the exception is escalated to a higher exception handler of the task. If there is none, or none matching, the task is aborted and a pel exception is generated.
     
  6. CHAOS raises a TIMEDOUT exception to all the remaining mel waits. However since a program does not know which mels have been read and which ones not, the recommended TIMEDOUT handler for a cumulative mel AND read is the OR combination handler, such as:
    catch ( (store1 || store2)<TIMEDOUT> ) {  }
    If a stand-in mel variable is specified as an attribute to mel AND wait, the OR combination handler can be abbreviated to:
    catch ( store<TIMEDOUT> ) { }
  7. If the logic of the program demands, single TIMEDOUT handlers can be added. For example:
    catch ( store1<TIMEDOUT> ) {
        printf ("Store 1 has timed out");
        alertTheProducerOfStore1ToSpeedUp ();
        <resume>;
    }
    
    The Precedence rule also applies to cumulative AND reads. When a timeout occurs, CHAOS needs to pick a handler to process that exception. If there are multiple matching handlers, CHAOS will pick the more restrictive one. Thus, if there are both matching single handlers and an OR combination handler, CHAOS will pick the single handler. If there are multiple single handlers that match, CHAOS will arbitrarily pick one.
     
  8. It is not recommended to have an AND combination handler for cumulative AND reads for TIMEDOUT exceptions, such as:
    catch ( (store1 && store2)<TIMEDOUT> ) {
        printf ("All the mel waits time out at the same time");
        <resume>;
    }
    
    That handler does not work if either store1 or store2 has been successfully read already. It can only work if neither has been read. However, in this case, the OR handler is still better since it also works with the "either" case.
Let's modify the Consumer task to see how it gets off a cumulative AND read TIMEDOUT exception handler:
/* VERSION 4 */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try {
      ConsumeTwo (<? timeout as=store>(store1 && store2));
    }
    catch ( store1<TIMEDOUT> ) {
      printf ("[Store1] has timed out");
      informProducerToSpeedUp (1);
      <resume>;  /* retry store1 or both */
    }
    catch ( store<TIMEDOUT> ) {
      printf ("Can't get %s in time", store<name>);
      <resume>;  /* retry store1 or store2 or both */
    }
    catch ( store<CLOSED> ) {
     printf ("[%s] has closed", store<name>);
     break;  /* stop on first closure */
    }
  }
}
Let's run through some scenarios.
store1 times out
The Consumer task gets off all the queues it is still waiting on, and jumps into the single exception handler catch ( store1<TIMEDOUT> ). The reason for this handler is that the task can ask the Producer of store1 to speed up. The handler then invokes the <resume> operation.

The <resume> operation does a fresh cumulative mel AND read, without iterating through the while loop. For store1 that times out, the Consumer task puts itself back into its waiting queue. For store2, it is a little more complicated. If store2 has been successfully read, then Consumer does not wait for it anymore. Otherwise, Consumer also puts itself to the bottom start of store2 waiting queue.
 
store2 times out
The Consumer task gets off all the queues it is still waiting on, and jumps into the OR combination exception handler represented by the stand-in mel variable store:
catch ( store<TIMEDOUT> )
After printing the store2 information, the handler invokes the <resume> operation.

The <resume> operation redoes the cumulative mel AND read. For store2 that times out, the Consumer task puts itself back into the waiting queue. For store1, Consumer redoes the mel wait only if store1 has not been successfully read.
In both scenarios, if instead of <resume>, the handler were to use continue to iterate the while loop, then the mel AND read would also be invoked again, but in this new invocation, the Consumer task would be automatically put into both mels waiting queues. If during the previous invocation (which has timed out), a value had been successfully read, this value would be irrevocably lost. On the other hand, the <resume> operation intends to make good on the existing cumulative mel AND read. Accumulated values are retained, and only un-filled values will be waited again.

Cumulative AND Reads Closeouts

Another cause of failure is that one mel is closed. Since any mel in the AND list can be closed at any time, the recommended handler is the OR combination, and the get-off strategy is a break. For example:
catch ( (store1 || store2)<CLOSED>) ) {
    break;  /* stop on first closure */
}
In the above example, the Consumer task also wants to know which mel element has timed out. This is resolved by using the stand-in mel variable store:
catch ( store<CLOSED> ) {
    printf ("[%s] has closed", store<name>);
    break;  /* stop on first closure */
}

Let's consider some other scenarios:
Single CLOSED exception handler
Per Precedence rule, if a single handler exists and matches, it will be processed over the recommended OR combination handler:
catch ( store1<CLOSED> ) {
    printf ("[store1] has closed");
    break;
}
The above handler only matches the store1 closeout. In our example, if store2 is closed, it will be handled by the OR combination handler.
 
AND combination CLOSED exception handler
This handler:
catch ( (store1 && store2)<CLOSED> )
is more problematic than it is usually worth. Normally this handler will not get matched since both mels must be closed at the same time, which rarely happens if ever, because mel closures are usually independent from one another, and not synchronized.

One contrived scenario that can cause the AND combination CLOSED handler to ever get matched, is when there is a single handler that gets off with a <resume> operation. The AND mel wait is resumed, CHAOS detects that one mel has closed, the single handler is re-matched, the program flow jumps back into that single handler again, which again does a <resume> back into the mel AND read; this livelock spin prevents the task to do other work, and continues until the rest of the mels get closed by other task or tasks. Only at that time, due to the precedence rule, the AND combination CLOSED handler, which is more restrictive than any single handler, will get picked.
 
No resumption on closeouts
An AND mel read is successful if all the listed mels are successfully read. If one fails due to closure, the AND mel read will definitely fail. Like all exceptions, a CLOSED exception is cleared on exit of the serving handler. However while a timeout period is restarted on re-entry of a mel read, a mel closure condition remains and is detected immediately again, causing the CLOSED handler to be re-entered, resulting in a livelock spin.

If the AND mel read is inside an infinite loop, it is also not recommended to use the continue get-off strategy. Let's look at this example:
/* VERSION 5 */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try {
      ConsumeTwo (<?>(store1 && store2));
    }
    catch ( (store1 || store2)<CLOSED>) ) {
      continue;
    }
  }
}
The continue statement will loop back the while loop, and a new mel AND read is attempted again. CHAOS will again see that one of the mels (or both) has closed, and transfer the program flow of the task to the CLOSED exception handler again, which again loops back to the while loop. Again, we have a spinning livelock.

The only viable get-off strategy in case of a mel closure during an AND mel read is to get out of the read, and do not attempt to retry it via resumption or iteration.
 
Orphan reads on closeouts
A mel AND read has many mels listed. Some of these mels may have been successfully read before one of the remaining mels gets closed, which usually triggers the task to get off the mel AND read. What happens then to the values that have been read?

This is up to the task what to do with these values. Let's explore the scenarios:
  • Mels with successful reads:
    Mels that have been successfully read have their corresponding mel variables with <value> property updated and <error> property changed to NERW_ERROR_NONE.
  • Closed mels:
    Mels that are closed, have their corresponding mel variables with <value> property unchanged and <error> property set NERW_ERROR_CLOSED.
  • Mels still in waiting:
    Mels that are still waiting to be read have their corresponding mel variables with <value> property unchanged and <error> property remaining at NERW_ERROR_PENDING.

The following example shows a Consumer that does not want to lose any read values:
/* VERSION 6 */
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try {
      ConsumeTwo (<? as=store>(store1 && store2));
    }
    catch ( store<CLOSED>) ) {
      if ( store1<error> == NERW_ERROR_NONE )
        Consume (store1<value>);
      if ( store2<error> == NERW_ERROR_NONE )
        Consume (store2<value>);

      break;
    }
  }
}
If the OR combination CLOSED handler is entered because of either store1 or store2 closing, it checks if either mel variables have been successfully read, and if so, it will Consume the read value, before breaking off the while loop.


Simultaneous AND Reads

The simultaneous method is a very special case of mel AND reads. It is used when there is a potential for circular deadlocks. To resolve such possibilities, it adds another layer between the mel readers' queues and the mel variables of the requesting tasks.

Let's look at the following example:
<mel> int store;
main () {
  <mel> int leftkey = 1;
  <mel> int rightkey = 1;

  <!>Producer ();
  <!>ConsumerRH (rightkey, leftkey);
  <!>ConsumerLH (leftkey, rightkey);
}
void Producer () {
  while ( <?>store = Produce() );
  <close>store;
}
void ConsumerRH (<mel> rightkey, <mel>leftkey) {
  while ( 1 ) {
    try {
      <? method=simul>(rightkey && leftkey);
      ConsumeRH (<?>store ) {
      <?>rightkey = 1;
      <?>leftkey = 1;
      }
    }
    catch ( store<CLOSED> ) {
      break;
    }
  }
}
void ConsumerLH (<mel>leftkey, <mel> rightkey) {
  while ( 1 ) {
    try {
      <? simul>(leftkey && rightkey);
      ConsumeLH (<?>store ) {
      <?>leftkey = 1;
      <?>rightkey = 1;
      }
    }
    catch ( store<CLOSED> ) {
      break;
    }
  }
}
Both "Consumer" tasks issue a mel AND read for the "right and left keys". The ConsumerRH task uses the keyword method to introduce the simultaneous method. As said previously, this keyword can be omitted since its values are well-known. The ConsumeLH task takes advantage of this permission, and just uses the value simul.

If the simultaneous method is not specified, the mel AND reads default to the cumulative method, and this situation can happen:
  1. The main task initializes the leftkey and rigthkey
  2. The ConsumerRH task grasps the rightkey value, causing the rightkey mel to become empty.
  3. The ConsumerLH task grasps the leftkey value, causing the leftkey mel to become empty.
  4. When ConsumerRH tries to grasp the leftkey, it finds leftkey to be empty.
  5. When ConsumerLH tries to grasp the rightkey, it finds rightkey to be empty.
  6. The mel AND reads at ConsumerRH and ConsumerLH are stuck in a circular deadlock./li>
The use of the simultaneous method prevents this circular deadlock to happen, or resolves it if it has already happened. The simultaneous method has only one requirement - the all-or-nothing requirement. When a simultaneous mel AND read ends, either it ends successfully with all the mel variables valued, or it ends unsuccessfully with none of the mel variables getting valued. In contrast, a cumulative mel AND read can end with a partial success, with some mel variables valued, and some not.

NERWous C does not define an implementation for the simultaneous method for mel AND reads. The computer literature contains a great number of discussions on resolving circular deadlocks due to competition of shared resources. These discussions usually relate to the Dining Philosophers problem. Depending on the physical and runtime environment, a particular algorithm may be a better fit than another; thus, the implementation is left to the designers of an actual NERWous C compilation or translation tool set.

In the above example, suppose that the ConsumerRH task already gets hold to the rightkey mel value, and is waiting for the leftkey value. If this value is given to the task ConsumerLH then a deadlock will result, with each task holding a key and waiting for the other key, and no ex-machina producer task to generate new values for those keys. An implementation of the simultaneous method either prevents the deadlock to happen in the first place (e.g., by giving the leftkey to ConsumerRH to have one less mel AND wait cases pending), or resolves the deadlock after it happens (e.g., by yanking the rightkey from ConsumerRH and giving it to ConsumerLH).

Simultaneous AND Reads Timeouts

The above example has the mel AND reads without timeouts. Let's now introduce this facility for the ConsumerLH task:
void ConsumerLH (<mel>leftkey, <mel> rightkey) {
  while ( 1 ) {
    try {
      <? simul timeout as=key>(leftkey && rightkey);
      ConsumeLH (<?>store ) {
      <?>leftkey = 1;
      <?>rightkey = 1;
      }
    }
    catch ( key<TIMEDOUT> ) {
      printf ("Got a timeout after [%d]\n", key<timeout> );
      <resume timeout+10>;
    }
    catch ( store<CLOSED> ) {
      break;
    }
  }
}
With this change, the task ConsumerLH wants to have a checkpoint TIMEDOUT exception after the default timeout is reached. The exception entry key<TIMEDOUT> uses the stand-in mel variable key, and is the short-cut for (rightkey || leftkey)<TIMEDOUT>. After the checkpoint printf, the ConsumerLH task increases the current timeout period by 10 msec, and <resume>s the mel AND wait at the starting positions in both mel queues.

There are similarities between timeouts for cumulative and simultaneous mel AND reads:
  • Timeouts are optional.
    If no timeout is specified, a simultaneous AND read will wait forever until all the listed mel elements are successfully read, or until one mel read fails due to other failures other than timeouts.
     
  • The single timeout is applicable to all.
    All mel waits use the same timeout value specified in the simultaneous mel AND read. This is true on the original timeout and also on updated timeouts. For example, the ConsumerLH task, in its TIMEDOUT exception handler, increases the amount of timeout by 10 msec for a subsequent resumption:
    <resume timeout+10>;
    This new timeout value will be applicable to all mels in the simultaneous mel AND wait.
     
There are differences between timeouts for cumulative and simultaneous mel AND reads:
  • All timeouts happen at the same time.
    The simultaneous mel AND read does not collect mel values one at a time as they meet the mel READ conditions. The implemented AND read underlying algorithm may collect the mel values that way, but it only gives these values to a waiting task if all the mel values have been assembled. In other words, it is all or nothing. (If there are tasks that compete for circular shared resources, the task that receives all the waited mel values is the one that the algorithm decides that will prevent a deadlock to occur, or the one that resolves an occurring deadlock.)

    Since a task cannot see a mel value until it sees all of them, a timeout that occurs in one mel wait, is applicable to all mels in the AND list. In the example above, ConsumerRH catches the exception with an OR combination handler:
    catch ( (rightkey || leftkey)<TIMEDOUT> )
    which is abbreviated to this statement due to the availability of the stand-in mel variable key:
    catch ( key<TIMEDOUT> )
    The ConsumerLH can also use the more restrictive AND combination handler:
    catch ( (rightkey && leftkey)<TIMEDOUT> )
    Due to the all-or-nothing nature of the simultaneous method, both the OR or AND combination handlers will properly catch the TIMEDOUT exception.
     
  • Available values still time out.
    A peculiarity of the simultaneous mel AND read is that a mel that satisfies the mel READ conditions of a task, can still cause that task to time out. This is because the mel value is read by the implemented AND read algorithm, and this algorithm has decided to not give that value to the task due the potential of a deadlock. The algorithm may be waiting for other mel values to be available and give all of them to the task in one shot.

    If a timed-out task redoes the simultaneous mel AND read, either via a resumption or an iteration, it may have the chance to get the mel value that is not granted to it the first time. If the value is still available, the algorithm may now find the conditions good enough for the task to receive that value and the rest.
     
  • Resumption is for all mel queues.
    With the cumulative mel AND read, a <resume> operation from a TIMEDOUT exception handler puts the task back into the queues of mel elements that the task has not accumulated the values yet. For the simultaneous mel AND read, its all-or-none nature has the task being put back into all the queues of requested mels.

    When the task is put back into a queue, it begins the wait at the bottom of the queue (i.e. the starting position). It has to move up to the top position of the queue, to satisfy one of the conditions of the mel READ conditions, before it can be considered by the implemented AND read algorithm. If there are other tasks in the queue waiting on other mel reads, the task will have to wait for its due time.
     
  • Available mel values may not be read
    With the cumulative mel AND read, once a value shows up on the mel buffer, it will likely be read by one the requesting tasks that has met the mel READ conditions. For the simultaneous mel AND read, the implemented AND read algorithm will take over the reading. It may read out the value into some internal holding area. This will allow other tasks in the mel queue to get to subsequent values in the mel buffer. If the algorithm does not support a holding area, it may leave the task at the top of the queue to hold up the mel buffer value. In this case, any queued tasks have to wait for the algorithm to resolve the no-deadlock AND wait problem before they can access the mel buffer.
While the simultaneous mel AND read looks easy to use from the coder level -- just add the method keyword simul, things get more complicated at the runtime level. The implemented AND read algorithm may require additional resources, and in general will cause mel waits to be lengthened. The simultaneous mel AND read should not be used unless warranted.

Simultaneous AND Read Closeouts

While the simultaneous AND wait is going on, one of the waited-on mel elements may get closed by some other task. Like for the closeouts for cumulative AND waits, the recommended handler for the <CLOSED> exception is the OR combination:
catch ( (rightkey || leftkey)<CLOSED> )
To prevent livelocks, the handler should not get off with a resumption or iteration attempt, since any subsequent attempt to do a simultaneous mel AND read that includes a mel that has been closed, will result in another <CLOSED> exception.

While there are many similarities between a cumulative and simultaneous mel AND read, there are many differences between them too.
No accumulated values
A task doing a cumulative mel AND wait, may be able to read out some values for its mel variables from the mel request list before it gets a <CLOSED> exception handler. If it does a simultaneous mel AND read, the all-or-nothing nature of the read insures that the task will have none of its mel variables valued. Even if there are values to be had, the implemented AND read algorithm still holds them back.
 
Lost accumulated values
The implemented AND read algorithm holds back the generated mel values until it can give to a task all the values it requests in the mel AND list. If a mel gets closed, the task will abort the mel AND read with the <CLOSED> exception, getting none of the accumulated values. What happens to these accumulated values then? They are simply available to other tasks that also do simultaneous AND read. If the implemented AND read algorithm cannot find any task to give the accumulated values, they will be discarded at the end of the NERWous C program.

When the <CLOSED> exception happens to a task doing a cumulative mel AND read, it gets granted the accumulated mel values, and in the <CLOSED> exception handler, it can decide to discard these values or make some remedial use. With the accumulated values being held by the implemented simultaneous AND read algorithm, there is no such option.



Asynchronous Mel AND Reads

We have seen that the asynchronous read mode is applicable to mel OR reads. It is also applicable to mel AND reads, for both the cumulative and simultaneous methods. Let's take a look at this Consumer example:
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    <? mode=async>(store1 && store2);
    doSomething ();
    ConsumeTwo (<?>store1, <?>store2);
  }
}
The Consumer above needs the values of the mel elements store1 and store2 for its ConsumeTwo chore. Instead of immediately waiting for them, it outsources the AND mel wait to the CHAOS runtime by issuing an early asynchronous mel AND read request:
<? mode=async>(store1 && store2);
While CHAOS stands in line in the mel queues, Consumer uses that time to doSomething. Once done, it issues the mel read requests on the mel elements. Although the mel store1 is processed first in this statement
ConsumeTwo (<?>store1, <?>store2);
the mel AND read will not complete until all the listed mel values are available. In other words, due to the default cumulative method used, CHAOS can accumulate the value of store1, but it will not give it to the Consumer task until the value of store2 is also accumulated.

As in the asynchronous read of single mels, the <?>store1 and <?>store2 requests mean that the Consumer task will use the <value> properties of the local mel variables if CHAOS has successfully carried out the mel AND wait; otherwise Consumer will relieve CHAOS and take over the wait.
Side note: As a reminder, the asynchronous mel AND wait is most useful if there are many readers vying for the same mel elements. This allows a task to put itself in the queue as early as possible to "stake its position". If the task is the only consumer of the mel resources, there is no need for a "pay someone to stand in line for me" scheme. In our Producer/Consumer example, since Consumer is the only reader task, Consumer can actually be written as simply as follows:
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    doSomething ();
    ConsumeTwo (<?>store1, <?>store2);
  }
}
We are making things more complicated than necessary here for the sake of discussion.

The default AND read method is the cumulative method. If we want the Consumer task to use the simultaneous method, we have to specify it:
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    <? mode=async method=simul>(store1 && store2);
    doSomething ();
    ConsumeTwo (<?>store1, <?>store2);
  }
}
We can omit the attribute keywords, mode and method:
<? async simul>(store1 && store2);

Error Handling For Asynchronous Mel AND Reads

Let's modify Consumer to add some error handling:
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    <? async timeout as=store>(store1 && store2);

    while ( store<error> == NERW_ERROR_PENDING )
      doSomething ();

    switch ( store<error> ) {
      case NERW_ERROR_NONE:  /* success */
        ConsumeTwo (store1<value>, store2<value>);
        continue;  /* while(1) loop */

      case NERW_ERROR_TIMEDOUT:
        printf ("[%s] has timed out - TAKE OVER READ\n", store<name>);
        ConsumeTwo (<?>(store1 && store2));  /* take over the wait */
        continue;  /* while(1) loop */

      case NERW_ERROR_CLOSED:
         printf ("[%s] has closed - STOP READ\n", store<name>);

         /* fall through the default case */
      default:
         printf ("[%s] fails on [%d] due to [%s] - STOP READ\n",
           store<name>, store<error>, store<why> );

         if ( store1<error> == NERW_ERROR_NONE )
           Consume (store1<value>);
         if ( store2<error> == NERW_ERROR_NONE )
           Consume (store2<value>);

         break;  /* out of while(1) loop */
    }
  }
}
The above Consumer task first requests an asynchronous mel AND read with timeout. While the CHAOS runtime does the waiting, the task runs the doSomething chore. It keeps doing the doSomething until it detects that CHAOS has ended the mel AND read. The ending may be a success or failure. If it is a success, the task runs ConsumeTwo to process the read values of the mel stores, and iterates the while loop to repeat the same process. If the asynchronous mel AND read times out, the Consumer task, not having anything better to do, takes over the mel AND read from CHAOS. Once it gets the mel values, Consumer runs ConsumerTwo, then iterates the while loop to repeat the process. For other kinds of failures, the Consumer task gets off the while loop. But before doing that, since the mel AND read is a cumulative one, one of the two mel reads may have been successful. Instead of forfeiting this mel value, Consumer salvages it with the single-parameter Consume function.

The Consumer task achieves the above by using the <error> mel variable. First, it sets the store mel variable as the stand-in for the mel AND read of store1 and store2. The invoked asynchronous mel AND read sets the <error> property of both mel variables to NERW_ERROR_PENDING. While the <error> variables have this value, CHAOS is still running the mel AND read on behalf of Consumer. This allows Consumer to run doSomething. The check in the while loop:
store<error> == NERW_ERROR_PENDING
uses the stand-in variable store as a short-cut way for:
store1<error> == NERW_ERROR_PENDING || store2<error> == NERW_ERROR_PENDING
Since the mel AND read is invoked with the default cumulative method, the variables can have their <error> values changed at separate times. When both values have been changed, the Consumer task knows that CHAOS is done with the mel AND read. To know if this read is successful or not, Consumer again makes use of the <error> property. The construct:
switch ( store<error> ) {
  case NERW_ERROR_NONE: ...
  case NERW_ERROR_TIMEDOUT: ...
  case NERW_ERROR_CLOSED: ...
  default: ...
}
is a short-cut for:
int _err1 = store1<error>, _err2 = store2<error>;
if ( _err1 == NERW_ERROR_NONE && _err2 == NERW_ERROR_NONE ) { ... }
else if ( _err1 == NERW_ERROR_TIMEDOUT || _err2 == NERW_ERROR_TIMEDOUT ) { ... }
else if ( _err1 == NERW_ERROR_CLOSED || _err2 == NERW_ERROR_CLOSED ) { ... }
else { ... }
For a successful read, both <error> values must be NERW_ERROR_NONE. For a failed read, only one <error> variable needs to have a failed error code. Using the stand-in store mel variable ensures that the AND (&&) and OR (||) operators are applied correctly, besides resulting in a more concise code.

For a successful read, the local <value> properties contain the read values, and Consumer uses them directly for the ConsumeTwo chore. For generality, Consumer can also use the mel AND wait construct:
ConsumeTwo (<?>(store1 && store2));
The above wait construct first checks the local mel <value> properties. If fresh values are found there, the wait construct will return them immediately, instead of going out to the remote mels and doing mel waits. In the case above, we are sure to find fresh values, so it is more efficient to use the <value> properties directly.

For the timeout failure, if the method used for mel AND read were the simultaneous method, its all-or-nothing would have both mel variables timed out. Since the cumulative method is in use here, one mel variable may already be valued (with its <error> property set to NERW_ERROR_NONE), and the other mel variable timed out. The task Consumer knows which mel variable whose timeout causes the NERW_TIMEDOUT_ERROR case, by checking the <name> property of the stand-in variable store.

Once a timeout has been detected, Consumer chooses to take over the wait from CHAOS. It issues a mel AND wait, and will be willing to wait until both of the requested mels are valued. Note that if one mel variable is already valued (by the CHAOS runtime from the asynchronous mel AND read), the new mel AND read will wait only on the other mel.
Side note: An astute programmer will notice that there is no error checking for this second mel AND read. This statement:
ConsumeTwo (<?>(store1 && store2));
is a concise way to write:
<?>(store1 && store2);
if ( store1<error> == NERW_ERROR_NONE && store2<error> == NERW_ERROR_NONE )
    ConsumeTwo (store1<value>, store2<value>);
What happens if the <error> properties are not NERW_ERROR_NONE? If this is the case, the mel AND read has failed, the ConsumeTwo function will not be invoked. The Consumer task gets off the case NERW_ERROR_TIMEDOUT with a continue to iterate the loop. The asynchronous mel AND read is requested again; and this time, any mel deficiency discovered from the previous iteration, will be handled by this iteration's switch/case statements.
For the CLOSED and other error conditions, the Consumer task breaks off the while loop. Before doing that, the task tries to salvage any values that have been successfully read during the asynchronous attempt. Those values are in the <value> properties of the mel variables whose <error> properties have been changed from NERW_ERROR_PENDING to NERW_ERROR_NONE.
Side note: This salvage work would not make sense if the AND read method were simultaneous. With its all-or-none nature, there would not be any successful partial reads on failure.

Exception Handling For Asynchronous Mel AND Reads

An alternative to using the <error> property for checking for mel AND read results is to work with exceptions. Let's modify the Consumer code again:
void Consumer (<mel> int store1, <mel> int store2) {
  while ( 1 ) {
    try {
      while ( <? async simul timeout as=store>(store1 && store2) )
        doSomething ();
      ConsumeTwo (<? tag=TAKEOVER as=store>(store1 && store2));
    }
    catch ( store<TIMEDOUT> ) {
      printf ("[%s] has timed out - TAKEOVER READ\n", store<name>);
      <resume tag=TAKEOVER>;  /* take over the wait */
    }
    catch ( store<...> ) {
      if ( store<error> == NERW_ERROR_CLOSED )
        printf ("[%s] has closed - STOP READ\n", store<name>);
      else
        printf ("[%s] fails on [%d] due to [%s ] - STOP READ\n",
          store<name>, store<error>, store<why>);

      break;  /* stop on first failure */
    }
  }
This time, we use the simultaneous method with the keyword simul. The statement:
while ( <? async simul timeout as=store>(store1 && store2) ) doSomething;
is a concise way to write:
<? async  simul timeout as=store>(store1 && store2);
while ( store<error> == NERW_ERROR_PENDING ) doSomething;
The statement
ConsumeTwo (<? tag=TAKEOVER as=store>(store1 && store2));
can be entered two ways. The first way is when the asynchronous mel AND read is successful, and the arguments that ConsumeTwo takes in are in fact store1<value> and store2<value>. There is no waiting involved even though the mel wait ? is present.

The second way that the ConsumeTwo statement is entered is via the <resume tag=TAKEOVER> statement invoked inside the store<TIMEDOUT> exception handler. The Consumer task now wants to take over the mel AND read from the CHAOS runtime. By default, the <resume> operation picks up on the last mel operation, which is the asynchronous mel read statement:
<? async simul timeout as=store>(store1 && store2)
which generates the timeout exception. By specifying the tag attribute in <resume>, the Consumer task requests the mel AND wait to be resumed at the ConsumeTwo statement which has the same tag attribute. (Without using tag, the <resume> operation would redo the asynchronous mel AND wait.)

In the catch-all store<...> handler, Consumer makes the wise decision to break out of the infinite while loop since future iterations will likely fail on the AND mel read. Before the breakout, Consumer can use the stand-in store variable to display information about the failure. It is not in the code above, but if Consumer wants to do something special with store1, it can via the if statement:
if ( store<name> == "store1" ) { ... }


Previous Next Top