Pages

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

No comments:

Post a Comment