Pages

Thursday, March 3, 2016

Mel Timeouts

Welcome » NERWous C » Mel
  1. Mel Waits with Timeouts
  2. Run-Time Timeouts
  3. Mel Default Timeout
  4. Mel Timeout Settings
  5. Mel Timeout Special Values
  6. Mel Timeout Property


Mel Waits with Timeouts

In the Producer / Consumer previous example, the Consumer task will wait at the <?>store statement until a product shows up or a CLOSED exception happens. If the Producer task is slow, then this wait time is wasted by the Consumer, especially if it can do something else meaningful. We can mitigate this waste by having a timeout in the mel wait:
void Consumer (<mel> int store) {
     while ( 1 ) {
         try {
             Consume(<? timeout=10>store);
         }
         catch ( store<TIMEDOUT> ) {
             doSomethingElse();
             continue;
         }
         catch ( store<CLOSED> ) {
             break;
         }
     }
}
Here, the Consumer is willing to wait only 10 milliseconds. If no product shows up, the store<TIMEDOUT> exception is raised, and the Consumer can doSomethingElse. Once this is done, it will go back and check on the mel store again. This cycle repeats until the mel store is closed (by the Producer in our example).


Run-Time Timeouts

The timeout value can be a variable that changes at each mel wait iteration. For example, the following Consumer would like to adjust its timeout value to try to match the production rate of the Producer.
void Consumer (<mel> int store) {
     int waitfor = 10;
     while ( 1 ) {
         try {
             Consume(<? timeout=waitfor>store);
         }
         catch ( store<TIMEDOUT> ) {
             waitfor += 5;
             continue;
         }
         catch ( store<CLOSED> ) {
             break;
         }
     }
}
The Consumer starts with a 10 msec timeout. If this is not sufficient, and the store<TIMEDOUT> exception occurs, it raises the timeout another 5 msec for the next <?>store mel wait.


Mel Default Timeout

A default timeout can be specified for any task who wants to do a mel wait with a timeout but does not need to specify the value of that timeout. NERWous C does not define where the default timeout value is defined. It is left as an implementation issue. The default timeout is invoked in the mel wait with the keyword timeout without a value:
     <? timeout>store;
The default timeout is convenient when a program needs only two types of mel element access: one that "waits forever", and one that "waits up to certain amount of time".


Mel Timeout Settings

Timeout settings can be done using calculations. When calculating a subsequent timeout, the keyword timeout stands for the current timeout, not the default timeout as in the original setting. Any calculation resulting in a negative timeout will negate the timeout (i.e. timeout is 0).

In the example below, let's assume that the default timeout is 5.
    int n = 10;
    <? timeout>store;    // access with default timeout 5 msec
    <? timeout=n>store;    // access with timeout 10 msec
    <? timeout+n>store;    // access with current timeout plus 10, thus 20 msec
    <? timeout+timeout>store;    // access with double the current timeout, thus 40 msec
    <? timeout-100>store;    // access with no timeout since 40-100 is negative
    <? timeout=-100>store;    // still no timeout due to negative value assignment
    <? timeout+8>store;    // access with timeout 8 msec since current timeout is 0
    int timeout = 20;
    <? timeout+timeout>store;    // access with double the current timeout, thus 16 msec, not 40 msec
The keyword timeout represents the current timeout within the < and > NERW markers. Thus the C variable timeout is ignored, while the variable n is accepted.


Mel Timeout Special Values

Besides the default timeout and explicit numerical settings, there are special pre-defined values for timeouts:

ValueSynopsis
NERW_TIME_NONEThis is the same as the value of 0. It indicates the mel operation is requested without a timeout, and the requesting task is willing to wait until the operation either succeeds or fails.
NERW_TIME_REMAINThis value is used if a task wants to take back an asynchronous mel request from the CHAOS runtime, and makes use of the remaining timeout, instead of starting a new timeout or no timeout.

The above values are defined in the file nerw.h that is included in all NERWous C programs. (For brevity, this include file is omitted in most examples.)


Mel Timeout Property

After a successful mel access, a program can check the timeout used via the <timeout> mel property.
     <? timeout>store;
     int deftimeout = store<timeout>;
     printf ("The default timeout setting is %d", deftimeout );
     <? timeout+20>store;
     printf ("The last access has default timeout + 20 or %d", store<timeout>);
     <?>store;
     printf ("Last access is without timeout, so value %d must be 0", store<timeout>);
Unlike the <value> property or the <buffer> property, the <timeout> property is not associated with the remote mel element. It is part of the IN Properties which belong solely to the task.

The <timeout> property is never stale since it always reflect the timeout setting to the last access (read or write) from the task to the remote mel element. (If the access is without timeout, the <timeout> property is reset to 0.) It is also not shared between tasks -- for each mel access, each task has its own <timeout> property inside its own mel variable.


Previous Next Top

Mel Priorities

Welcome » NERWous C » Mel
  1. Mel Access Priority
  2. Mel Priority Queues
  3. Mel Default Priority
  4. Mel Priority Settings
  5. Mel Priority Property


Mel Access Priority

The following example shows the use of priority for a mel read and write:
#include 
main () {
    <mel> int store;
    <!> Producer (store);
    <!> Consumer (store);
    <!> ProducerPrio (store);
    <!> ConsumerPrio (store);
}
void Producer (<mel> int store) {
    while ( <?>store = Produce() );
    <close>store;
}
void Consumer (<mel> int store) {
    while ( 1 ) {
       try {  Consume(<?>store); }
       catch ( store<CLOSED> ) { break; }
    }
}
void ProducerPrio (<mel> int store) {
   int myprio = NERW_PRIORITY_DEFAULT;
   while ( 1 ) {
      try {
         <? timeout priority=myprio>store = Produce();
      }
      catch ( store<TIMEDOUT> ) { ++myprio; }
   }
   <close>store;
}
void ConsumerPrio (<mel> int store) {
   int myprio = NERW_PRIORITY_DEFAULT;
   while ( 1 ) {
      try {
         Consume (<? timeout priority=myprio>store;
      }
      catch ( store<TIMEDOUT> ) { ++myprio; }
      catch ( store<CLOSED> ) { break; }
   }
}
The same mel store is shared between all the tasks that the main task pels out. The Producer and Consumer tasks use the default priority since they do not specify any. The ProducerPrio and ConsumerPrio tasks make use the priority attribute to specify the mel access priority. Both tasks start out with the same default priority (as indicated by the NERW_PRIOIRTY_DEFAULT value). If they fail to access the mel store within the timeout period, they will increment their priority value so they have the "upper hand" in the mel access as compared with the plain Producer and Consumer.

The pump-up-the-priority strategy that ProducerPrio and ConsumerPrio use above may not work if Producer and Consumer also adopt the same strategy. The management of the priorities is not part of the NERW model. It is the responsibility of the code developers to engineer the mel access priorities to fit the problem on hand with the underlying run-time platform.


Mel Priority Queues

With a couple of exceptions, accesses to mel variables are though mel queues. The queues serialize the access to the mel shared resource so that all waiting tasks have a chance. All the queues are First-In-First-Out (FIFO) so that tasks that request access first will be served first. However, all the tasks at the higher priority queue must be served first before any task at the lower priority queue.

Read access uses readers' queues which are separate from writers' queues used for write access. Each mel variable has one default readers' queue and one default writers' queue. These queues have the default priority. When a task requests a mel access and does not specify a priority or specifies the NERW_PRIORITY_DEFAULT priority, the default queue is used. If a task comes in with a different priority, then the <?> operation will find a queue with that priority or creates one if not found, to put the task in.

Once a mel variable is closed, all the queues are deleted allowing the resources used to be recollected. These are the queues:
  1. The default readers' queue
  2. The default writers' queue
  3. Any priority queues
  4. The readonly queue if exists



Mel Default Priority

Although the NERW Concurrency Model does not limit the possible range of priority values, the NERWous C limits priorities to a positive integer range, with 0 as the lowest priority, known as the base priority. The CHAOS runtime environment may further limit the possible values to a discreet number of values (for example, just HIGH, NORMAL and LOW) to reflect the actual physical platform.

The default priority is the priority used by the default readers' queue and writers' queue, and can be set to any non-negative value. It can be preset at compile-time, or customized at program invocation via a configuration file, or fixed by the CHAOS runtime environment. If the default priority is set to a non-0 value, it allows tasks to have a lower priority than the default priority. If no default priority is configured, it is set to be 0, and tasks can only have higher priorities than the default priority.

The following mel accesses use the default priority;
<?>store;    /* no prority read */
<? priority>store;   /* read with priority attribute without value */
<? priority=NERW_PRIORITY_DEFAULT>store;    /* read with priority set to default */

><?>store = 10;    /* no prority write */
<? priority>store = 10;   /* write with priority attribute without value */
<? priority=NERW_PRIORITY_DEFAULT>store = 10;    /* write with priority set to default */


Mel Priority Settings

Priorities can be set via calculations. When calculating a subsequent priority, the keyword priority stands for the current priority, not the default priority as in the original setting. Any calculation resulting in a negative priority will cause the priority to be reset to the base priority which is 0.

In the example below, let's assume that the default priority is 5.
    int n = 10;
    <? priority>store;    // access with default priority 5
    <? priority=n>store;    // access with new priority 10
    <? priority+n>store;    // access with current priority plus 10, thus 20
    <? priority+priority>store;    // access with double the current priority, thus 40
    <? priority-100>store;    // access with the base priority 0 since 40-100 is negative
    <? priority=-100>store;    // access with same base priority 0 due to negative assignment
    <? priority=100>store;    // access with new priority 100
    int priority = 20;
    <? priority+priority>store;    // access with double the current priority, thus 200
The keyword priority represents the current priority within the < and > NERW markers when used in calculation. Thus the C variable priority is ignored, while the variable n is accepted.


Mel Priority Property

After a successful mel access, a program can check the priority used via the <priority> property of the mel.
     <? priority>store;
     int defprio = store<priority>;
     printf ("The default priority used is %d", defprio);
     <? priority+20>store;
     printf ("The last access has default priority + 20 or %d", store<priority>);
Whenever a mel is accessed, the run-time environment stores the priority value used (or 0 if no priority) in the local <priority> property,


Previous Next Top

Wednesday, March 2, 2016

Mel Reads

Welcome » NERWous C » Mel
  1. Mel Read Attributes
  2. Mel Reader Mode
  3. Mel Read Waits
    1. Mel Read Process
    2. Mel Read Conditions
  4. Mel Properties On Reads
    1. On Read Entry
    2. On Read Success
    3. On Read Failure
  5. Agent Mel Property
  6. Mel Asynchronous Reads
    1. Asynchronous Read Basic
    2. Asynchronous Read With Error Checking
    3. Asynchronous Read With Exceptions


Mel Read Attributes

Let's consider these two mel read statements:
int c1 = <?>store1;
int c2 = <? priority timeout mode=readonly>store2;
The first statement is a simple mel read statement with no attributes. The second statement is a mel read statement with attributes stated:

OperationAttributesSynopsis
<?>store Mel read operation on the mel variable store
priority
=n
Access the mel for reading with priority n
timeout
=msec
Wait for the mel to have a value for reading for msec milliseconds before aborting
mode
=setting
Request read access to the mel with this mode setting:
reader: request a read access (default)
async: request an asynchronous read access
readonly: request read-only access
method
=setting
Method used for mel combined reads: mel OR reads and mel AND reads, to be explored in the next chapter.
as
=var
Set up var as the stand-in mel variable for the mel read operation. It is mostly used in combination mel reads and mel reader zones.

Reader Mode
A mel read statement is not only about accessing the remote mel element, but also about waiting for the task's turn to access in one of the mel reader's queues, and then waiting again for the mel element to have a filled slot, and finally removing the read item from the mel buffer. The last action prevents the read value to be read again by another task. These are the mel read conditions to be formalized later.

Asynchronous Mode
Using the asynchronous mode, the task can continue doing other things while the mel read conditions are being met. When the task finally needs the read value, it can check if a value has been successfully read. We will explore asynchronous reads in a later section.

Readonly Mode
It is possible to read the mel value and leave it back in the mel buffer for other reader tasks by using the mode attribute readonly. We will explore the readonly access in a future chapter.


Mel Reader Mode

A mel read operation accesses a mel variable at the reader's end of the mel buffer. For example to read the mel variable store into a local variable c:
c = <? mode=reader> store;
The mode keyword can always be omitted:
c = <? reader> store;
Finally, the attribute value reader can also be omitted:
c = <?> store;
Due to the position of the mel variable at the right-hand side of the assignment (=) operator, a mel read operation can be identified at compile time without the verbose use of the reader mel buffer access mode. However, even without the = operator, the reader mode can also be omitted because it is the default mel buffer access mode. The following statements are equivalent:
<?> store;
<? reader> store;
<? mode=reader> store;


Mel Read Waits

Let's start with a simple Producer/Consumer example with a focus on the Consumer side to explore the mel read access:
main () {
    <mel> int store;
    <!> Producer (store);
    <!> Consumer (store);
}
void Producer (<mel> int store) {
    while ( <?>store = Produce() );
    <close>store;
}
void Consumer (<mel> int store) {
    while ( 1 ) {
       try {  Consume(<? timeout>store); }
       catch ( store<TIMEDOUT> ) {
          printf ("Timeout - try again");
          continue;
       }
       catch ( store<CLOSED> ) {
          printf ("Store closed - get out");
          break;
       }
       catch ( store<...> ) {
          printf ("Unexpected error [%d] due to [%s]",
              store<error>, store<why>);
          break;
       }
    }
}
The Producer task keeps filling the mel store until it produces a 0 value. After depositing this 0 value to the mel element, it breaks out of the while loop, closes the mel store, and ends itself.

The purpose of the Consumer task is to Consume the items produced by Producer.

Mel Read Process

This is the behind-the-scene process for the statement
<? timeout>store
  1. The Consumer task starts the mel wait operation by setting the properties of the associated store mel variable, per on read entry setting. Of interest, the <error> property is reset to NERW_ERROR_PENDING.
     
  2. The Consumer task puts itself into one of many possible readers' queues of the mel store. The readers' queue the Consumer selects to stand in line is based on the priority of the mel read operation. Here, no priority attribute is explicit so the default readers' queue is used.
     
  3. If there are other reader tasks in the queue, the Consumer task moves up the queue as the front tasks get off the queue, until it reaches the top position of the queue.
     
  4. At the top position, the Consumer waits for the queue to have READ access to the mel store, based on the priority of the queue. All tasks in higher priority queues must be served before any tasks in a lower priority queue.

    In this example, since Consumer is the only reader task, there is no vying for the access from higher-priority queues. Therefore, although the default readers' queue has the lowest priority to access the mel buffer, it is immediately granted READ access.
     
  5. If there is a product in the store mel buffer (i.e. the mel store is filled), Consumer checks the staleness of this product through its mel sequence number. Here this check is procedural only since staleness occurs with readonly access, which is not the case here.
     
  6. If by some unexpected synchronization issue, the product does not pass the staleness test, the Consumer allows a task behind it to "jump the line" to check and retrieve this product item. (If the second task also finds the product stale, CHAOS keeps working down the waiting queue for an accommodating task.)

    A task that stands due to product staleness still keeps its position in the queue to wait for a non-stale product to arrive.
     
  7. Once Consumer has found a fresh product, it removes that product from the mel buffer, and initializes the <value> property of the associated mel variable with the value of that product. It resets the <error> property to NERW_ERROR_NONE, and sets the other properties of the mel variable per on read success setting. The task then gets off the mel read operation, and resumes the processing flow. Here it Consumes the retrieved product.

    The mel buffer now has a vacant slot for the Producer to deposit a new product.
     
  8. If there is no product in the store mel buffer (i.e. the mel store is empty), Consumer keeps the top position in the queue and waits until the mel store becomes filled. Then it processes as above.
     
  9. If Consumer cannot retrieve a product before the timeout, the TIMEDOUT exception is raised. The Consumer task sets the properties of the associated store mel variable per on read failure setting, especially setting the <error> property to NERW_ERROR_TIMEDOUT, gets off the mel wait operation, and processes the TIMEDOUT catch handler. Here, it just prints a statement, and then uses continue to iterate the while loop.
    Side note: An alternative to continue is <resume>. The continuation process loops back to the while loop and then issues a new mel read statement. On the other hand, the resumption process restarts the wait of the latest mel wait statement, skipping the while loop iteration. Which process to use depends on the logic of the program. In this example here, the resumption would be more efficient.

    Note that both methods will put Consumer back at the bottom of the in-use readers' queue. This allows waiting tasks already in the queue to move to the top. In the example here, since Consumer is the only reading task in our example, it will be back at the top of the readers' queue immediately.
  10. When Consumer is waiting for a value, and a 0-valued product arrives, Consumer will retrieve it and Consumes it like any other products. On the next iteration of the while loop, there will be no more products from the Producer. The Consumer waits at the mel queue until it receives the CLOSED exception due to the Producer having closed the mel store.
     
  11. When the Consumer receives the CLOSED exception, it sets the properties of the associated store mel variable per on read failure setting, especially setting the <error> property to NERW_ERROR_CLOSED, gets off the mel wait operation, and jumps into the CLOSED catch handler. In this example, the handler breaks out of the while loop, and with nothing else to do, the Consumer task ends itself.
If during an attempt to read from the mel store, the Consumer detects an exception other than TIMEDOUT or CLOSED, it will get off the mel wait operation, and jumps into the catch-all handler:
catch ( store<...> )
Here, it prints the Unexpected error statement with the error code and cause of the error, and then breaks off the while loop. Without the catch-all handler for store, any unexpected exception from store will cause the Consumer task to abort abruptly, instead of ending graciously.

Mel Read Conditions

As a summary, the following conditions must be met before a task can read out a mel value:
  1. The mel element is open and accepts read requests
  2. The task successfully joins a readers' queue of the mel
  3. The task has moved up to the top position of the readers' queue
  4. The readers' queue has been granted READ access to the mel element
  5. The mel element is filled, with a value in its mel buffer
  6. The value to be read is not stale for the task
The first condition checks if the mel has not been already closed.

The second condition concerns the priority attribute in the mel read statement. Each priority is associated with a separate readers' queue. Without the priority attribute, the default readers' queue is used. The selected queue must have enough queuing resources to take in the task.

The third condition is relevant when there are other tasks already in the readers' queue. All tasks join a readers' queue at the bottom, and moves up the queue. Only the task at the top of a readers' queue can retrieve the mel value.

The fourth condition is relevant when there are multiple readers' queues vying for access to the same mel element. A queue with a higher priority is granted access to the mel element before lower priority queues.

The fifth condition checks if the mel element is empty. The reader task must wait for a writer task to deposit a value to the mel buffer before it can read it.

The sixth condition is relevant when the reader task finds a value already in the mel element but this value is already read due to a previous read access. This happens when we repeatedly do a readonly access to a mel, and since a readonly access does not remove the mel value, subsequent readonly accesses may see a stale value. Staleness should not happen for normal reads due to the removal of the mel value. However the staleness check is included as part of the read conditions as an additional step to insure integrity, especially in heterogeneous system where different parts running at different speed and reliability, could introduce unexpected inconsistencies. When staleness is discovered, the task must wait first for another reader task to read out that stale value, and then for a writer task to deposit a fresh value to the mel buffer.


Mel Properties On Reads

Mel properties belong to mel variables. A mel variable represents a session between a task and the mel element due to a mel operation. Unlike the remote mel, the mel variable is resident to the task. For the mel read operation, properties are set on entry, and on success or failure.

On Read Entry

This table summarizes the properties settings on entry of a mel read operation:

On Read Entry
PropertyAttributeSynopsis
IN Properties
store<priority><? priority> store;The <priority> property is set to the value of the priority attribute. If the attribute is not specified, the property is set to default priority value. CHAOS has the task join the readers' queue corresponding to the requested priority.
store<timeout><? timeout> storeThe <timeout> property is set to the value of the timeout attribute. If the attribute is mentioned, but without a specific value, the timeout value is the system default value. If the attribute is not mentioned, the timeout value is 0, meaning that the read request does not have a timeout timer, and the task is willing to wait until the mel read conditions are satisfied.
CORE Properties
The task uses the CORE properties to connect to the remote mel element. The read operation does not modify the CORE properties.
OUT Properties
store<value>The mel read operation keep the current values of these properties on entry.
store<count>
store<sequence>
store<status>The mel read operation resets these two properties to NERW_STATUS_OPEN on entry.
store<condition>
store<error>The mel read operation resets the <error> property to NERW_ERROR_PENDING on entry.
store<why>The mel read operation resets the <why> property to an empty string on entry.
SET Properties
The mel read operation keep the current values of these properties on entry.


On Read Success

When a mel read operation is successful, the properties of the mel variable are set as follows:

On Read Success
PropertySynopsis
IN Properties
The IN properties retain the same values as in entry.
CORE Properties
The read operation does not modify the CORE properties.
OUT Properties
store<value>Value read from the remote mel buffer. This value can be a simple entity (like an int), an array or a structured entity.
store<count>Number of items in an array value. This value is null if the value is not an array.
store<sequence>Version identifier of the value returned from a mel read operation. This value should be higher than the value of the <sequence> property of the mel variable on entry.
store<status>Status of the mel element after the mel read operation. If the mel element has a default buffer of one slot, the <status> is NERW_STATUS_EMPTY if there is no writer task in the waiting. If there is a waiting writer task, the <status> will be NERW_STATUS_FILLED since the mel buffer is expected to be re-filled by that writer task.
store<condition>Status of the mel element before the mel read operation. If the <condition> is NERW_STATUS_FILLED, the mel buffer contains at least a value to be read. If the <condition> is NERW_STATUS_EMPTY, the mel buffer does not contain any value or fresh value to be read, and the task had to wait in the mel queue for a value to be deposited by a writer task.
store<error>Since this is a successful mel read, the <error> property is set to NERW_ERROR_NONE.
store<why>Since this is a successful mel read, the <why> property is an empty string.
SET Properties
Upon a successful mel read, the SET properties are updated with the corresponding values from the remote mel element.


On Read Failure

When a mel read operation fails, the properties of the mel variable are set as follows:

On Read Failure
PropertySynopsis
IN Properties
The IN properties retain the same values as in entry.
CORE Properties
The read operation does not modify the CORE properties.
OUT Properties
store<value>These properties retain their values they have on entry of the mel read operation.
store<count>
store<sequence>
store<status>
store<condition>
store<error>Since this is a failed mel read, the <error> property contains the error code of the failure.
store<why>Since this is a failed mel read, the <why> property contains a detailed explanation of the failure.
SET Properties
The SET properties retain the same values as in entry.



Agent Mel Property

The reader mode that has been discussed so far, is the default mode to read from mel elements. There is another mode which is the mel asynchronous read. Before we explore this mode, let's get introduced to the <agent> mel property.

The <agent> property is one of the IN properties. On entry to any mel operation (including the mel read operation), CHAOS initializes the <agent> property of the local mel variable with a value to indicate the execution entity that will run the mel operation. The <agent> property has the following values:

ValueSynopsis
self The mel operation is run under the context of the task itself. This is a direct run where the code flow of the task is suspended until the operation finishes with either success or failure.
system The mel operation is run under the context of the CHAOS runtime system. This is an asynchronous run with the task returning to its code flow immediately after issuing the mel operation request.

The <agent> property is set up differently depending on the reading mode used:

SettingReader ModeAsync ModeReadOnly
On Entryselfsystemself
On Success same as on entry
On Failure same as on entry

The <agent> property is only changed on entry of a mel operation. Afterwards it reflects the execution agent of the last mel operation.


Mel Asynchronous Reads

With the default reader mode, the task initiates the mel read operation and does the mel wait. We now introduce the asynchronous mode where the task initiates the mel read operation but has the CHAOS runtime does the mel wait for it. The task can use this time to do something else more productive.

Asynchronous Read Basic

Let's start with a Consumer task that uses the default reader mode:
void Consumer (<mel> int store) {
    Consume (<?> store);
}
The above reader task waits for a writer task to fill the shared mel store before it can do a read for its own Consume chore.

Let's now modify Consumer to take advantage of the asynchronous reader mode. It will offload the waiting part to CHAOS while it can do some other thing:
void Consumer (<mel> int store) {
    <? mode=async> store;
    doSomething ();
    if ( store<error> == NERW_ERROR_NONE )
        Consume (store<value>);
    else
        Consume (<?> store);
}
The Consumer task issues the asynchronous read statement:
<? mode=async> store;
to request CHAOS to wait for the mel read conditions on its behalf. Unlike the mel read statement that can suspend the task in a potential mel wait, the asynchronous read statement returns the processing flow to the task immediately after sending the asynchronous read request to CHAOS.

The Consumer after doSomething, is now ready to Consume a product from the mel store. It hopes that during the time it is doing doSomething, CHAOS has been able to successfully finish the mel wait and initialize the properties of the local mel variable store, per On Read Success settings. In this case, the <error> property, which was set to NERW_ERROR_PENDING on entry, has been reset to NERW_ERROR_NONE; the <value> property now contains the newly read value for Consume; and the rest of the properties adhere to the On Read Success setting.

On the other hand, CHAOS may still be in the mel waiting. The check for NERW_ERROR_NONE in <error> thus fails. The Consumer task could do something else to give CHAOS more time. In the above example, Consumer does not have anything else to do, so it decides to take over the wait. The mel wait request <?> store in Consume(<?>store), transfers the mel wait responsibility from CHAOS back to the Consumer task.

In fact, the check for NERW_ERROR_NONE in <error> is not necessary. We can simplify code Consumer as follows:
void Consumer (<mel> int store) {
    <? async> store;
    doSomething ();
    Consume (<?> store);
}
Note that the mode keyword is now omitted. Like in the mel reader mode, the mode keyword is optional since the word async can uniquely introduce the asynchronous mode.

The mel read request <?> store in Consume(<?>store) will check if a new value has been read by CHAOS via the asynchronous read request. If it is, the <value> property of the mel variable is immediately returned for Consume. Otherwise, the Consumer task will take over the wait.

The above Consumer code assumes that there is only success on the mel read, either asynchronously by CHAOS or directly by the task itself. A mel read can also fail. In the next section we will explore how this is handled with asynchronous mel reads.

Asynchronous Read With Error Checking

An asynchronous read operation can be invoked with other mel read attributes. Let's explore the behind-the-scene process of an asynchronous read with timeout and <error> property checking.
void Consumer (<mel> int store) {
    <? async timeout> store;
    doSomething ();

    switch ( store<error> ) {
      case NERW_ERROR_NONE:
        Consume (store<value>);
        break;
      case NERW_ERROR_TIMEDOUT:
        printf ("CHAOS fails the mel read due to timeout");
        Consume ( <?>store );
        break;
      case NERW_ERROR_CLOSED:
        printf ("The async read fails due to closure");
        break;
      case NERW_ERROR_PENDING:
        printf ("Take over the wait with remaining timeout");
        Consume (<? timeout=NERW_TIME_REMAIN> store);
        break;
      default:
        printf ("CHAOS has failed the mel wait due to [%d] on [%s]",
           store<error>, store<why>);
        Consume (<? timeout> store);
        break;
    }
}
The task Consumer now invokes the asynchronous mel wait with a timeout and checks the outcome of that wait.
  1. The timeout value in the async mel wait statement means that CHAOS has that much time to wait for a read from the mel store buffer. If it cannot achieve that, it will get off the mel wait and sets the <error> property to NERW_ERROR_TIMEDOUT, and other properties per On Read Failure.
     
  2. CHAOS receives the request, and starts the mel read process on behalf of the Consumer task. It sets the <agent> property to system. The other properties are set per On Read Entry.
     
  3. Consumer after requesting the asynchronous mel wait, runs doSomething, with the hope that once this chore is done, CHAOS has successfully read a store value.
     
  4. With doSomething done, Consumer is now ready to do Consume. It checks the <error> property of the local mel variable store. This property has been updated by CHAOS to mark its progress in its asynchronous mel wait.
     
  5. If <error> is NERW_ERROR_NONE, CHAOS has successfully done the mel read, and initializes the <value> property with the read value. Consumer then Consumes from the <value> property which is a local entity instead of accessing the remote mel element.
     
  6. If <error> is NERW_ERROR_TIMEDOUT, CHAOS has aborted the mel wait due to timeout. Consumer, after doing a printf message, decides to redo the mel wait by itself, this time without the timeout. Since this is a new mel wait, the Consumer task will join the store readers' queue at the bottom end behind existing waiting tasks, if any.
     
  7. If <error> is NERW_ERROR_CLOSED, CHAOS has aborted the mel wait due to its detection that the remote mel element has been closed. The Consumer task acknowledges this closure with a printf, does a break from the switch statement, and ends itself.
     
  8. If <error> remains at NERW_ERROR_PENDING, CHAOS is still waiting to read the mel store. The Consumer task has a number of options that it can take:

    OptionDescription
    <?> store The task takes over the wait from CHAOS. It keeps the current position that CHAOS has attained, in the mel readers' queue. It drops the timeout requirement and is willing to wait indefinitely.
    <? timeout=NERW_TIME_REMAIN> store The task takes over the wait from CHAOS. It keeps the current position that CHAOS has attained, in the mel readers' queue. Its wait is restricted to the remaining amount of time left from the original timeout value.
    <? timeout> store The task takes over the wait from CHAOS. It keeps the current position that CHAOS has attained, in the mel readers' queue. Its wait is reset to the default timeout value.
    <? timeout=1000> store The task takes over the wait from CHAOS. It keeps the current position that CHAOS has attained, in the mel readers' queue. Its wait will time out in 1000 msec.
    <? priority+1> store The task takes over the wait from CHAOS. It does not join the mel queue that CHAOS was standing in; instead it joins a higher priority readers' queue, starting at the bottom of that queue.
    <? priority timeout> store The task combines both priority and timeout processing as described above when it takes over the mel wait itself.
    Do nothing The task continues to let CHAOS do the mel waiting in its behalf, and will check back later. The task can use this time to do other worthwhile things.

    In this example, the Consumer task decides to take over the mel wait on the same mel readers' queue, keeping its position gained by CHAOS, and using the remaining timeout.
     
  9. If <error> is any other error (default case), CHAOS has aborted the mel wait due to that error. Consumer does a printf message for the error code and any cause reported by the underlying platform, then decides to redo the mel wait by itself, this time with a new default timeout. Since this is a new mel read, the Consumer task will join the store readers' queue at the bottom end behind existing waiting tasks, if any.

Asynchronous Read With Exceptions

A drawback with the asynchronous read with error checking is that once the Consumer task takes over the mel wait, this wait operation can also fail. Besides error checking code for asynchronous reads, we will need to add another set of error checking code for direct mel reads. Using mel operation exceptions, the <agent> property and the <resume> operation, we can handle error conditions for both the CHAOS asynchronous read and the direct mel read.
void Consumer (<mel> int store) {
  try {
    <? async timeout> store;
    doSomething ();
    Consume ( <? timeout=NERW_TIME_REMAIN>store );
  }
  catch ( store<TIMEDOUT> ) {
    if ( store<agent> == "system" ) {
      printf ("Retry if CHAOS times out");
      <resume timeout=NERW_TIME_NONE>;
    }
    else if ( store<agent> == "self" ) {
      printf ("Give up if Consumer times out");
      break;
  }
  catch ( store<CLOSED> ) {
    printf ("The async or direct read fails due to closure");
    break;
  }
  catch ( store<...> ) {
    printf ("The mel wait has failed due to [%d] on [%s]",
      store<error>, store<why>);
    if ( store<agent> == "system" )
      <resume>;  /* retry with leftover timeout */
    break;
  }
}
The strategy is similar to the earlier example. The Consumer task starts out with an asynchronous mel read, and uses the saved time to doSomething. Once it is ready for the Consume chore, it issues the standard mel read:
Consume ( <? timeout=NERW_TIME_REMAIN>store );
If at this time the asynchronous mel read has successfully completed, the mel read will return the cached <value> property for the Consume chore. Otherwise, the Consumer task will take over the mel wait from CHAOS. With the NERW_TIME_REMAIN setting, it will continue to use the remaining time left from the original asynchronous read.

The asynchronous mel read can fail, due to timeout, mel closure or other causes. If this ever happens, CHAOS will set the <error> and <why> properties of the mel variable, per On Read Failure settings. However CHAOS will not interrupt the Consumer task. The latter finishes its doSomething chore, and when it issues the mel read for the Consume chore, a pending error will trigger an exception. Note that the exception is triggered because the code is inside a try / catch construct. Without it, the task still sees the pending error, but need to process it via the error checking method.

The Consumer task has three exception handlers: the <TIMEDOUT>, <CLOSED> and catch-all handler. The <CLOSED> handler is common to both the asynchronous and direct mel reads. It just printf a common message and then allows the Consumer task to break out off the handler, and finding no subsequent code to run, the task will end itself.

Things are different with the <TIMEDOUT> handler. If the asynchronous mel read has timed out, the exception is triggered when the Consumer task does a mel read for the Consume chore. In the handler, the task does a printf acknowledging CHAOS work, and then resumes the last mel wait on the mel store, which is:
Consume ( <? timeout=NERW_TIME_REMAIN>store );
However, the resumption overwrites the NERW_TIME_REMAIN timeout attribute with a NERW_TIME_NONE value. This is to request a new mel read without timeout. In other words, the Consumer task, after taking over the mel read from CHAOS, is willing to wait as long as needs be for the mel read conditions to be realized. This kind of wait will not trigger a timeout.

If Consumer is willing to wait without timeout, why is there a check for <agent> to be self in the TIMEDOUT exception handler? This is due to the <resume> request in the catch-all handler.

The catch-all handler, catch ( store<...> ), displays the error code and the cause of failure for both the asynchronous mel read and subsequent direct mel reads. For a failure due to the asynchronous mel read, the Consumer task opts to resume the last mel read, which is the takeover of the mel wait by Consumer. The <resume> operation this time does not include a timeout attribute; therefore the direct mel wait will use the NERW_TIME_REMAIN value of the takeover mel read statement. If this direct mel wait times out, it will be handled by the TIMEDOUT handler inside the
else if ( store<agent> == "self" )
code block. Here, the Consumer just gives up on getting a store value for the Consume chore.


Previous Next Top

Tuesday, March 1, 2016

Mel Closures

Welcome » NERWous C » Mel
  1. Closing A Mel
  2. Multiple Mel Closures
  3. Closing At Task End


Closing A Mel

In the Producer / Consumer previous example, we use the value 0 to allow the Producer to indicate that it is done. The more generic way is for the Producer to close the mel element:
main () {
     <mel buffer=100> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     int c;
     while ( c = Produce() )
         <?>store = c;

     <close>store;
}
void Consumer (<mel> int store) {
     try {
         while ( 1 )
             Consume(<?>store);
     } catch ( store<CLOSED> ) {
         return;
     }
}
The Producer invokes the <close> operation on its resident mel variable store. The CHAOS runtime receives this request, puts the Producer job on hold, retrieves the CORE properties from the mel variable, reaches out to the remote mel element now identified, marks it as closed, and frees the Producer job to resume processing. To limit the requesting job from waiting too long, the CHAOS runtime will garbage-collect any released resources from the closed mel element at an opportune time.

Once a mel element is closed, any attempt to write to it or read from it will result in the CLOSED exception. This is what happens to the Consumer task when it attempts to read from the mel element store. The Consumer catches the exception and does a return. Since the Consumer is run under a task, a return will end the task.


Multiple Mel Closures

The Consumer can also close the mel element. For example, we would like our Consumer to consume up to 1000 items and no more. When this threshold is reached, the Consumer will close the mel element store to inform the Producer to stop producing.
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     int c;
     try {
         while ( c = Produce() )      // Produce until 0
             <?>store = c;

         <close>store;
     }
     catch ( store<CLOSED> ) {   // Or until the Consumer stops consuming
         return;
     }
}
void Consumer (<mel> int store) {
     int upto = 1000;
     try {
         while ( --upto >= 0 )
             Consume(<?>store);   // Consume until upto is reached

         <close>store;
     }
     catch ( store<CLOSED> ) {    // Or until the Producer stops producing
         return;
     }
}
If a mel element is already closed, apply another <close> operation to it will be silently ignored.


Closing At Task End

There is no automatic mel closing when the task that requests its creation, ends. When a mel element is created, its belongs to the whole NERWous C program, and not to a task. The only thing that the task has control of, is the resident mel variable that represents its access to the remote mel element. This mel variable will fall out of scope when the task ends. If a task wants to close the mel element when it ends, it has to specifically invoke the <close> operation.

When the NERWous C program exits, all mel elements are automatically closed. The CHAOS runtime then collects all the released resources for another program.


Previous Next Top

Mel Queues

Welcome » NERWous C » Mel
  1. Producer / Consumer
  2. Mel In Use
  3. Mel Termination
  4. Mel Operations
  5. Mel Properties


A Multi-Slotted Mel Example

To increase the concurrency between the Producer and Consumer from the previous example, we can increase the size of the mel variable:
main () {
     <mel size=100> int store;
     <pel> Producer (store);
     <pel> Consumer (store);
}
void Producer (<mel> int store) {
     int c;
     while ( c = Produce() )
         <?>store = c;

     <?>store = 0;
}
void Consumer (<mel> int store) {
     int c;
     while ( c = <?>store )
         Consume(c);
}
A faster Producer has to fill up the 100-slot buffer before it waits for the Consumer at the <?> construct.

The multi-slotted mel is a First-In-First-Out (FIFO) queue. When the Consumer looks up the store, it will pull the earliest produced item from this mel queue and assigns it to the local c variable. A faster Consumer has to consume all the items in the mel queue before it waits for the Producer at the <?> construct. (This statement is not 100% correct since the Producer will lock the mel queue when it deposits a new item, so there can be a small mutual exclusion wait for the Consumer if there is a contention.)

The FIFO nature of the mel queue is strictly enforced by the NERW Model. There is no construct to get the second or any subsequent item from the mel queue before the first one.


Run-Time Mel Queue Size

The NERW <mel> construct is a run-time operation that creates the mel during the execution of the program. It is not a static compile-time entity. Thus it is possible to specify the size of a mel queue using variables:
     int mysize = 100;
     <mel size=mysize> int store1;

     const DEFAULTSIZE = 100;
     <mel size=DEFAULTSIZE> int store2;
The size keyword can be omitted if it is the only attribute specified:
     <mel 100> int store;

     int mysize = 100;
     <mel mysize> int store1;

     const DEFAULTSIZE = 100;
     <mel DEFAULTSIZE> int store2;
There are other attributes for a mel entity besides size. Whenever these are used, the size keyword is required to differentiate between various attribute values.


Mel Queue Size Value

The size of a mel queue can be read:
     <mel 100> int store;
     int mysize = store<size>;  // mysize is equal to 100
Getting the size of a mel queue is a non-blocking operation. Thus there is no <?> wait construct, unlike getting a value from a mel queue which can block.


Mel Queue Resize

This operation may be very expensive or not supported at all on a particular platform; however the NERW Model allows a mel queue to change size during execution via the <resize> operation:
     <mel 100> int store;  // create a mel queue with 100 slots
     <resize 200> store;  // change to 200 slots, values in the first 100 slots are copied over
     <resize 50> store;  // change to 50 slots, values in the first 50 slots are retained
     <resize size+50> store;  // change to 100 slots, values in the last 50 slots are undefined
     <resize size-100> store;  // the mel queue becomes a mel variable with a single slot
     <resize size-50> store;  // the mel variable remains a mel variable with a single slot
     int mysize = 100;      // variable value
     <resize size+mysize> store;  // the mel variable becomes a mel queue with 100 slots (not 101!)
     int size = 75;
     <resize size+size> store;  // double the size of the mel queue to 200 - size is a keyword
The size of a mel entity cannot be 0 or negative. The minimum size is 1, which makes a mel queue a simple mel variable.


Previous Next Top

Wednesday, April 3, 2013

Starving Deadlocks

Starving deadlocks are handled by the mel exceptions, NOOUT for Producers and NOIN for Consumers. These exceptions must be specifically added by the programmers. If the programmers know that starving deadlock cannot occur, it is recommended that the exceptions should not be added since there may be a run-time penalty for checking for the exception conditions.

How long should a wait be for these exceptions to trigger?

Deadlocks

Deadlocks are well-known problems with concurrent programming. There are several types of deadlocks:
  1. Starving deadlocks apply to an execution thread where it does not get the necessary input to move on.
     
  2. Contention deadlocks happen when multiple shared resources are required by different execution threads, but they are distributed among these threads so that no one has all the resources necessary to move on.
     
Let's explore how NERWous C handle deadlocks.