Pages

Tuesday, April 12, 2016

Mel Readonly Access

Welcome » NERWous C » Mel
  1. Readonly Statement
  2. Readonly Abbreviation
  3. Readonly Queue
  4. Sequence Property
  5. Readonly On Read
  6. Readonly Mels
  7. Readonly Access to Exclusive Zones


Readonly Statement

In the previous section, we have an Inspector task that changes the sequence of produced items for consumption. In this section, let's introduce a non-intrusive Monitor task that preserves the sequence and the value at the mel variable.
main () {
    <mel> int store;
    <!> Producer (store);
    <!> Consumer (store);
    <!> Monitor (store);
}
void Producer (<mel> int store) {
    int n = random();
    while ( --n ) {
        try {
            <?>store = Produce();
        }
        catch ( store<NOREADERS || CLOSED> ) break;
    }
    printf ("Done Producing");
}
void Consumer (<mel> int store) {
    int n = random();
    while ( --n ) {
        try { 
            Consume (<?>store);
        }
        catch ( store<NOWRITERS || CLOSED> ) break;
    }   
    printf ("Done Consuming");
}   
void Monitor(<mel> int store) {
    try {
       <? mode=readonly timeout>store;
        if ( !Examine (store<value>) ) {   // examine it
            printf ("Stop the line -- value [%d] does not pass inspection", store<value>));
            <close>store;
            return;
        }
        printf ("Value [%d] is good for consumption", store<value>);
        <resume>;
    }
    catch ( store<NOWRITERS> ) return;
    catch ( store<TIMEOUT> ) <resume>;

    printf ("Done Monitoring");
}
We have a Producer that Produces a random number of items before ending. Likewise, we have a Consumer that Consumes a random number of items before ending. We also have a new task, Monitor, that runs until there is no more produced item to Examine.

The Monitor task accesses the mel store via a readonly operation, by adding the attribute mode with the value readonly to the wait operator ?. When Monitor comes to the mel and does not see a new item, it will wait. Whenever there is a new item, it will get its value. While the usual read operation gets the value and removes the item from the mel, the readonly operation gets the value but leaves the item back in the mel variable. Other readonly operations can subsequently get that value, until a read operation comes along and removes the item from the mel variable.

Because of the potential wait, the mel readonly statement supports the timeout attribute. (In the above example, Monitor catches the TIMEOUT exception but does nothing special except retries again.) However the mel readonly statement does not support the priority attribute because a readonly wait is already at the highest priority, as explained later in the Readonly Queue section.


Readonly Abbreviation

The readonly operation can be popular in a NERWous C program since not all tasks are reader tasks like our consumer tasks which remove a read value from the mel variable. For example the result of a parallel computation is a mel variable to allow the parallel tasks to build up that result. Once generated, this result will be accessed by downstream tasks. These tasks should access the result mel variable as readonly operations to allow the value to remain at the mel variable for all.

Like any other code>mode attributes, the mode=readonly attribute can be shortened with the removal of the mode keyword. Thus:
<? mode=readonly timeout>store;
can be written as:
<?readonly timeout>store;


Readonly Queue

When a readonly task comes to a mel and finds it empty, it will wait at the readonly queue. There is only one such a queue for each mel, unlike the many priority read queues used by the read operations. When a writer task deposits a new item into the mel, all tasks waiting in the readonly queue will get this item's value, remove themselves from the queue and get off the readonly wait. Once all the tasks waiting in the readonly queue have been served, the task at the top of the highest priority read queue can take away the item from the mel.

When a readonly task comes to the mel, and finds an item in the mel, how does it know if this item is new or stale? A stale item is one that it has accessed before, and is still available in the mel because no reader has come to retrieve it yet. This is where the mel sequence number comes into play. When a mel is first created, its sequence number is 0. When a writer deposits a new item into a mel, it increases that sequence number. In NERWous C, the data type of the mel sequence number is long long. Once the maximum of this huge number is attained, the sequence number is rewound to 0.

When a readonly task comes to a mel, it carries along the sequence number of its last read/readonly item. This value is 0 if the task has not accessed the item before. If this cached sequence number is different from the actual sequence number of the mel, the mel is new as far as the readonly task is concerned. The readonly task then simply takes a snapshot of the mel value and is done with the readonly operation.

If the two sequence numbers are the same, the mel is deemed stale, and the readonly task will put itself into the readonly queue of the mel, and waits. It first waits for a reader to come along to take away the stale mel. Then it looks a new mel item. A new mel item can come from the next item already in the mel buffer. If the mel buffer is empty, the readonly task has to wait for a writer to deposit a new item into the mel buffer.

If the mel is buffered, newly unread items may already be present in the buffer while the top item of the buffer (i.e. the item to be read) is stale. However these items are not available to the readonly tasks. If they detect the top item to be stale, they still have to get into the readonly queue until a reader task removes the top item off the mel buffer and exposes the next item. The NERW concurrency model does not allow readonly tasks to read ahead.

The same readonly queue is used on two occasions - first when the mel is empty, and secondly when the mel has an item. In the first case, all the readonly tasks that come for the mel will automatically go into the readonly queue. In the second case, only the readonly tasks that find the mel to be stale, go into the readonly queue. For both cases, all the waiting readonly tasks can only leave the readonly queue when there is a new item deposited into the mel.


Sequence Property

As mentioned above, when a writer task deposits an item into a mel, it increases the mel sequence number. A consumer tasks that gets the value of a mel (either via a read, readonly, or a no-wait snapshot operation), will retrieve this sequence number along with the mel value. This sequence number is cached locally in the mel property, sequence.

Let's modify Monitor to display the read values and their sequence numbers:
void Monitor(<mel> int store) {
    try {
        <?readonly timeout>store;
        printf ("Read value [%d] with sequence [%lld]\n", store<value>, store<sequence>);
        if ( !Examine (store<value>) ) {   // examine it
            printf ("Stop the line -- value [%d] does not pass inspection", store<value>);
            <close>store;
            return;
        }
        printf ("Value [%d] is good for consumption", store<value>);
        <resume>;
      }
      catch ( store<NOWRITERS> ) return;
      catch ( store<TIMEOUT> ) <resume>;
    }
    printf ("Done Monitoring");
}
Since the mel sequence number is a long long, the locally cached sequence property is also a long long.


Readonly On Read

As we already know, the tasks waiting on the readonly queue will access a newly deposited value before all reader tasks even those in the highest-priority queue. However it does not mean that a readonly task will see all the mel values. Let's take the Producer/Monitor/Consumer example above. The task Monitor can possibly miss Produce'd values that the task Consumer does not, even though the readonly queue used by Monitor is processed before any priority queue that Consumer does a read on. This will happen if the Monitor iteration is slower than the Consumer iteration. In this case, by the time Monitor goes to the mel store for another readonly, the Consumer may have time to access the mel store to read out one or multiple mel values, and Monitor is none of the wiser.

A second thing we already know is that a readonly task depends on a reader task in order to proceed, due to the case of a stale value. Even if a writer task has slotted a new value in the mel buffer, if there is no reader task to remove the existing stale value at the top of the buffer, the readonly task is stuck waiting.


Readonly Mels

A mel can be declared as a readonly mel so that all accesses to it are readonly:
<mel mode=readonly>int store;
The mode attribute can be omitted:
<mel readonly>int store;
Only readonly accesses are permitted to a readonly mel. Thus a <?>store is a syntax error since it is using a normal read operation. On the other hand, < mode=readonly>store and <?readonly>store are valid.

Since all read accesses to a readonly mel is readonly, there is no reader to take away a stale value. This responsibility is now assumed by a writer. When a writer deposits a new value to a readonly mel, all the tasks waiting in the readonly queue will get that value and can get off their wait. Contrast this to a readonly access to a regular mel: a writer can only unblock readonly tasks if the latter are waiting on an empty mel, but not on a stale mel.

Readonly mels are the default mode for returned values of a pel task to be explored later.


Readonly Access to Exclusive Zones

So far we have seen mel variables being blocked just enough for a read or write operation to complete. Later, we will be acquainted to exclusive zones where a mel variable can be held up for a longer period of time to allow the blocking task to run though its logic without other mel access interference. While a reader task has to wait for the exclusive zone to be available, a readonly task can get to the value of the mel variable at any time if this value is not stale. Readonly access to exclusive zones is described as part of the exclusive zone discussion.


Previous Next Top

Sunday, April 10, 2016

Mel Subscriptions

Welcome » NERWous C » Mel
  1. Writers Subscriptions
  2. Quit vs. Close
  3. Readers Subscriptions
  4. Dual Subscriptions
  5. Quit Attributes


Writers Subscriptions

The previous example introduces the NOWRITERs exception. This exception makes use of the writers subscription feature of the mel.

In the beginning, when a mel is created, its writers subscription is undefined. The first producer task that executes a write operation (such as <?>store = 10) is said to have subscribed to the mel for writing. The writers subscription is now 1. Other producer tasks that come along later will add 1 each to the writers subscription.

For each task, the writers subscription is a one-time event. As said above, the first write operation by the task will record its subscription. Any subsequent write operations by the same task will not add another subscription. New subscriptions only occur when new producer tasks access the mel for writing the first time to that mel.

When the task ends, the CHAOS runtime environment will automatically unsubscribe the task from the mel. If the task has a writers subscription, this action will decrease the writers subscription of the mel by 1. A task can also explicitly unsubscribe by invoking the quit operation, as shown in the example below.

Any consumer task that accesses the mel entity for reading will receive the writers subscription number from the mel entity. This number is cached locally in the mel proprety, writers. If the consumer has a catch on the NOWRITERS exception, several things happen when the consumer does a wait operation for reading. If the writers subscription is 0, the wait is aborted and the consumer task will have the NOWRITERS exception raised with the mel property writers updated to 0. If the writers subscription is undefined or greater than 0, the consumer task will stand in the mel queue and continues with the mel read wait process. If the wait becomes successful and the consumer task gets a value, the local mel property writers is updated to the actual writers subscription number at that time. If during the wait, the writers subscription becomes 0, then the wait is broken and the consumer task will have the NOWRITERS exception raised with the mel property writers updated to 0.

It is worthwhile to re-emphasize that the NOWRITERS exception is optional. If the reader task does not define a NOWRITERS exception handler, than even when the mel property writers is found to be 0, no NOWRITERS exception will be raised for the task. Such a reader task expects writer tasks to come and go, and there will be periods of times where there are no writers at all to deposit new values. In those times, the reader task simply waits at the mel read wait statement.

The following example shows the use of the quit operation, the NOWRITERS exception, and the writers property:
main () {
   <mel> int store;
   <! name="Producer1"> Producer (store);
   <! name="Producer2"> Producer (store);
   <!> Consumer (store);
}
void Producer (<mel> int store) {
   var n = random();
   while ( --n ) {
      <?>store = Produce();
   }
   <quit>store;
   DoSomethingElse();
}
void Consumer (<mel> int store) {
   while (1) {
      try {
         Consume (<?>store);
         printf ("There are %d producers serving me", store<writers>);
      }
      catch ( store<NOWRITERS> ) {
         printf ("No more producers - let's exit the grind");
         break;
      }
   }
   printf ("All consumption done");
}
We have two Producers running the same code. They Produce a random number of items then stop and DoSomethingElse. We have one common Consumer that gets whatever the Producers generate via the shared mel store.

The Consumer watches for the NOWRITERS exception. Without it, it will while loop forever after both Producers having stopped Produce'ing.

The Producer makes use of the quit operation to increase concurrency. Without this operation, the unsubscription to the mel store has to wait for the DoSomethingElse to be done and the Producer task to stop execution for the automatic unsubscription to take place. Here, the NOWRITERS exception will be raised as soon as both Producers have quittted, and the All consumption done message can be printed sooner.


Quit vs. Close

The mel close operation is a more drastic and global way for the Producer to mark that it is done with the mel store. If it is used in the above example instead of quit, the first Producer that ends will cause the other Producer task to get a mel CLOSED exception on the write operation operation and stop Produce'ing. The Consumer task will also get a CLOSED exception on its read operation operation, forcing it to abort abnormally. The use of close will prevent Consumer to consume whatever products the remaining Producer has left to Produce.

The first Producer that calls quit will have no impact on Consumer. The writers subscription of store will be down to 1, which is still greater than 0, so the NOWRITERS exception is not raised on Consumer. When the remaining Producer also calls quit or is terminated, then the NOWRITERS exception is raised and Consumer can wrap up within the NOWRITERS exception handler and then ends itself.


Readers Subscriptions

The readers subscription is similar to the writers subscription, but for the read operations.

A mel when first created has its readers subscription undefined. Any task that makes use of the read operation (such as c=<?>store) will increase the readers subscription by 1. The readers subscription is a one-time event. Any subsequent read operations by the same task will not add another subscription. When a subscribed task calls quit or has stopped running, the readers subscription is decremented by 1.

Any producer task that accesses the mel entity for writing will receive the readers subscription number from the mel entity. This number is cached locally in the mel proprety, readers. Any producer task that has the NOREADERS exception set up, will have this exception raised when it detects that the readers subscription has become 0.

Like the NOWRITERS exception, the NOREADERS exception is optional. If the writer task does not define a NOREADERS exception handler, than even when the mel property readers goes down to 0, no NOREADERS exception will be raised for the writer task. Such a writer task expects reader tasks to come and go, and there will be periods of times where there are no readers to empty the mel buffers so that it can deposit new products. In those times, the writer task just waits at the mel write wait statement.

The following example shows the use of the quit operation, the NOREADERS exception, and the readers property:
main () {
   <mel> int store;
   <!> Producer (store);
   <! name="Consumer1"> Consumer (store);
   <! name="Consumer2"> Consumer (store);
}
void Producer (<mel> int store) {
   while (1) {
      try {
         <?>store = Produce();
         printf ("There are %d consumers serving me", store<readers>);
      }
      catch ( store<NOREADERS> ) {
         printf ("No more consumers - let's exit the grind");
         break;
      }
   }
   printf ("All consumption done");
}
void Consumer (<mel> int store) {
   var n = random();
   while ( --n ) {
      Consume (<?>store);
   }
   <quit>store;
   DoSomethingElse();
}
We have above 2 Consumer tasks that Consume a random number of produced items, before quitting. Once both Consumer tasks have quitted, the readers subscription to the mel store becomes 0, causing the NOREADERS exception to be raised, allowing Producer to wrap up.


Dual Subscriptions

A task can have both readers and writers subscriptions.

We will now modify the example above by introducing an Inspector task that randomly withdraws the next produced item from the pipeline, examines it, and puts it back for consumption if it passes inspection.
main () {
   <mel> int store;
   <!> Producer (store);
   <! name="Consumer1"> Consumer (store);
   <! name="Consumer2"> Consumer (store);
   <!> Inspector (store);
}
void Inspector (<mel> int store) {
   int c;
   while ( IsMyShift() ) {
      c = <?>store;      // withdraw product from pipeline
      if ( !Examine (c) ) {   // examine it
        printf ("Stop the line -- value [%d] does not pass inspection", c);
        <close>store;
        return;
      }
      printf ("Value [%d] is good for consumption", c);
      <?>store = c;     // put it back to pipeline
      sleep (random());      // to do the inspection randomly
   }
   <quit>store;
   WriteShiftReport ();
}
Because the Inspector has a read operation and write operation, it has both the readers and writers subscriptions. At the end of its shift, it calls quit to unsubscribe to both subscriptions. However, during its shift, if it detects a bad product, it will close the mel, triggering CLOSED exceptions to all running processes that access the mel store. Since neither Producer nor Consumer has code to handle the CLOSED exception, this exception will abort those tasks. With all tasks not ended, the program also exits, causing the "line to be stopped" just as Inspector wants it.


Quit Attributes

The above implementation of Inspector has a big flaw. It prevents Producer to detect the NOREADERS exception, since Inspector itself is a readers subscriber. As long as Inspector is still in its shift, it will prevent the readers subscription number to go to 0 even if all the Consumer tasks have already quittted. Let's modify Inspector:
void Inspector (<mel> int store) {
   int c;
   while ( IsMyShift() ) {
      c = <?>store;     // withdraw product from pipeline
      <quit readers>;
      if ( !Examine (c) ) {   // examine it
        printf ("Stop the line -- value [%d] does not pass inspection", c);
        <close>store;
        return;
      }
      printf ("Value [%d] is good for consumption", c);
      <?>store = c;    // put it back to pipeline
      <quit writers>;
      sleep (random());      // to do the inspection randomly
   }
   WriteShiftReport ();
}
After a read operation, Inspector unsubscribes to the readers subscription; and after a write operation, to the writers subscription. If all Consumer tasks have quitted, and there is a Producer waiting at the mel queue for a buffer slot to free up so it can deposit its product, the <quit readers> command from Inspector will allow the readers subscription number to go to 0. This will relieve Producer from waiting and the NOREADERS exception can be raised.

The same argument applies similarly for the <quit writers> command, any waiting Consumer tasks and their NOWRITERS exception handling.

Astute readers of the Inspector code above may claim that we would not need the attribute readers and writers to the quit operation. And they are right. When Inspector starts, it does not have any subscriptions. On its first read operation statement (c = <?>store;), it gains a readers subscription. Since it has only one subscription, the more generic <quit> statement will generate the same result as the specific <quit readers> statement used here. The same logic applies for the <quit writers> statement.


Previous Next Top