Friday, July 22, 2016
Friday, June 17, 2016
Mel Reader Zones
Welcome
» NERWous C
» Mel
Reader Zones
The NERWous C examples we have seen so far have the tasks getting a lock on the mel variable long enough to put in a new value (
Let's modify our
The task
When a task uses a mel (exclusive) reader zone, the following behind-the-scene process occurs:
Conditions
Let us repeat the conditions that allow the task to get into the reader zone:
Eponymous Variables
When a task checks into the exclusive reader zone, the value of the mel variable is automatically copied to a local variable in the task's environment. This local variable is called the eponymous variable since it has the same name as the mel variable. Within the reader zone, the task does reads and writes to the local eponymous variable instead of to the remote mel variable. Since the access is local, there is no need for the wait operation (<?>).
When the task is in the exclusive reader zone, it can read from the eponymous variable as we see with the
When the task checks out of the reader zone, what happens to the value contained in the eponymous variable depends on the mode of the
Checkout Operation
A task checks into a reader zone exclusive to a mel variable, say
On checking into the reader zone, the value of the mel variable is copied to the eponymous variable maintained locally at the task. When the task checks out of the reader zone, what happens to the local mel properties and the remote mel variable depends on the
Let's take a look at this example, and compare the local <value> and <sequence> properties against the remote mel variable's value and sequence number, at the checkout of the reader zone.
Checkout Writeover
When a
The
The following code samples on the mel variable
Reader Zone Attributes
Like any mel wait operation, a mel reader zone wait has the following attributes:
To illustrate the use of the priority and timeout attributes, let's introduce a new task called the
Priority Attribute
The two
When
For all the three tasks, the local <sequence> property of the mel variable is updated, allowing them to wait for a new mel
Timeout Attribute
The
The timeout exception as used in this example may allow a
Mode Attribute
If a task checks into a reader zone and knows that most of the ways that it will check out of the zone is via a certain checkout mode (be it
When specifying the
Read Mode Attribute:
This is not necessary because the default mode is already the
Skip Mode Attribute:
Now let's rewrite the
Writeover Mode Attribute:
The
Reader Zone Resumptions
An astute reader will notice that there is a flaw in the VERSION 1 of the
In VERSION 1, after the reader zone checkout,
To resolve this flaw, we will use the
By invoking
Again the astute reader will notice a processing gap between the
Previous Next Top
Reader Zones
The NERWous C examples we have seen so far have the tasks getting a lock on the mel variable long enough to put in a new value (
Producer tasks), or read out the deposited mel value (Consumer tasks). We now introduce a new NERWous C construct called exclusive zones where the tasks can hold on to the mel variables as long as it needs to. We start with the reader zone in this chapter, and will explore the writer zone in a future chapter.
Let's modify our
Consumer task so that it only consumes an odd or even product depending on an argument request. To make sure that all products are consume'd, we will create two Consumer tasks, one for the even products, and the other for the odd products:
#define DO_EVEN 0
#define DO_ODD 1
main () {
<mel> int store;
<!>Producer (store);
<!>Consumer (store, DO_EVEN);
<!>Consumer (store, DO_ODD);
}
void Producer (<mel> int store) {
int c;
while ( 1 ) {
c = produce();
if ( c == 0 ) {
<close>store;
break;
}
<?>store = c;
}
}
void Consumer (<mel> int store, int evenodd) {
while ( 1 ) {
try {
printf ("Waiting to enter reader zone");
<?>store {
if ( ((evenodd == DO_EVEN) && (store % 2) == 0))
|| ((evenodd == DO_ODD) && (store % 2) != 0)) ) {
consume (store);
printf ("Consumed product [%d]", store);
}
else {
printf ("Skipped product [%d]", store);
<checkout skip>;
}
}
printf ("Stepped back from reader zone with value [%d]", store<value>);
}
catch ( store<CLOSED> ) {
printf ("Exit because the Store is closed");
break;
}
}
}
There is nothing special about Producer. It keeps produce'ing until a zero product is detected. It then closes the mel store and breaks from the infinite while loop. As it runs out of code to run, the task ends itself.
The task
Consumer is the more interesting one. The task main pels two Consumer tasks, one to consume the even products and the other, the odd products. The task Consumer implements its logic inside an exclusive reader zone code block, <?>store { ... }.
When a task uses a mel (exclusive) reader zone, the following behind-the-scene process occurs:
- The task puts itself in the mel readers' queue.
- In due course, the task moves up to the top position of the readers' queue. If the mel is still empty, the task will wait at this position, until the mel becomes valued.
- If and when the mel is valued, the task checks the mel sequence number. If this number is the same as the locally cached <sequence> property that the task keeps from the last access, the mel is stale, and the task continues to wait at the top of the queue position. During this wait, the task allows another reader task behind it to "jump the line" to get the stale mel.
-
When the task detects that the mel is valued with a new value, the task is ready to check in into the exclusive zone. See summary of conditions.
- With the task still keeping its top of the queue position, other incoming reader tasks must stand in line in various mel readers' priority queues. At this stage, the task does not allow subsequent reader tasks to "jump the line" to get to the mel value. The task now has exclusive read access to the mel variable.
- The check-in process has the task copy the mel value into a local eponymous variable but leave the mel value remaining at the remote mel variable.
Instead of working with the remote mel which requires potentially expensive network communication, the task works with the eponymous local variable. This is the reason within the reader zone, the variablestorecan be accessed without the <?> wait operation -- we are accessing the eponymous local variable of the same name, and not the remote mel variable.
-
While the task keeps other reader tasks at bay, it allows readonly access to the mel value left at the top slot of the mel buffer. If the readonly task returns for another readonly request, it may find that the mel value is still the same. In this case, the readonly task deems the mel variable stale and will wait at the readonly queue for a new value.
- If a writer task comes along, it may still able to deposit its item if the mel variable is buffered. When the mel buffer is full, the writer task will wait at the mel writers' queue until the exclusive zone task checks out off the exclusive zone and allows itself or another reader task to empty the top slot of the mel buffer.
- When the task is done with the reader zone, it either falls out of the reader zone's code block at the closing
}, or invokes thecheckoutoperation. The default behavior in both cases is to remove the mel variable off the top of the buffer. The value of the eponymous variable, that has been updated by the code inside the reader zone, is copied to the mel local <value> property. In addition, the mel local <sequence> property is also updated with the sequence number of the mel value that has been removed.
- Besides the default behavior, the task may choose other behaviors by calling the
checkoutoperation with an attribute. In the example above, theDO_EVENConsumertask does not process an odd product, and theDO_ODDone, an even product. The<checkout skip>operation has the task getting out of the reader zone without removing the top value from the mel variable buffer. Although there is no removal, the <value> property is still updated with the value of the eponymous variable. The <sequence> property is also updated with the sequence number of the mel variable (that has not been removed form the mel buffer) to prevent the task to re-process the just processed value in case of an iteration.
- After checking out off the reader zone, the task drops itself out of the mel readers' queue. Any reader task that has been waiting in the various mel priority queues can now has the chance to move up the queues and access the mel variable. Any writer task that has been waiting on the mel writers' queue because the buffer is full, can deposit its value if there is a new slot opened up from the default checkout behavior.
- After the reader zone checkout, our
Consumertask iterates the infinitewhileloop and vies with other reader tasks to get access to the mel variable. During this wait, thestore<CLOSED>exception can happen if the taskProducerhas closed the mel variablestore. At the end of the exception handler, the taskbreaks out of thewhileloop, and with nothing else to do, ends itself.
main after pelling the three tasks has already ended. With the Producer and the two Consumer tasks also ended, there are no more running tasks. The program itself then exits.
Conditions
Let us repeat the conditions that allow the task to get into the reader zone:
- The task must be at the top position of the reader's queue
- The mel variable has a value at the reader's end of the mel buffer
- This value is not stale
Eponymous Variables
When a task checks into the exclusive reader zone, the value of the mel variable is automatically copied to a local variable in the task's environment. This local variable is called the eponymous variable since it has the same name as the mel variable. Within the reader zone, the task does reads and writes to the local eponymous variable instead of to the remote mel variable. Since the access is local, there is no need for the wait operation (<?>).
When the task is in the exclusive reader zone, it can read from the eponymous variable as we see with the
Consumer task that consumes what it reads. It can also make change to the eponymous variable as we will see in the upcoming Manipulator task. Thus, the value of the eponymous variable can diverge from the original value of the mel variable.
When the task checks out of the reader zone, what happens to the value contained in the eponymous variable depends on the mode of the
checkout operation.
Checkout Operation
A task checks into a reader zone exclusive to a mel variable, say
store, via the <?>store { opening construct. It checks out of the zone at the closing curly bracket (}) or by explicitly invoking the checkout operation. Checking out via the closing bracket is the same as implicitly invoking the checkout operation without any attribute.
On checking into the reader zone, the value of the mel variable is copied to the eponymous variable maintained locally at the task. When the task checks out of the reader zone, what happens to the local mel properties and the remote mel variable depends on the
checkout operation attributes:
| Operation | Synopsis |
|---|---|
| <checkout> |
Checkout with default mode. If the default mode is not specified on check-in of the exclusive zone, the <checkout read> is assumed.
|
<checkout read> | This is the default behavior. The task removes the mel value off the mel buffer when checking out of the reader zone. This is similar to a mel read operation. The removal of the mel value frees the top slot of the mel buffer, allowing the next item in the buffer to move up and a writer task to deposit a new value at the writer's end of the buffer. |
| <checkout skip> | The task checks out of the reader zone without removing the value of the mel variable. The value the mel variable has on entry of the reader zone is retained. This value is then available for another reader task. Since the mel buffer is not emptied of its top slot, any writer task that is waiting due to a full buffer, will continue to wait. |
| <checkout writeover> | The task checks out of the reader zone without removing the value of the mel variable. However the value of the mel variable is updated with the value of the eponymous variable. This updated value is then available for another reader task. Since the mel buffer is not emptied of its top slot, any writer task that is waiting due to a full buffer, will continue to wait.
The sequence number is increased at both the remote mel variable and the local mel property <sequence> of the checkout task.
|
Let's take a look at this example, and compare the local <value> and <sequence> properties against the remote mel variable's value and sequence number, at the checkout of the reader zone.
extern int testcase;
<?>store = 10; /* mel variable */
printf ("Before value [%d], seqno [%lld]", store<value>, store<sequence>);
<> store {
store++; /* eponymous variable */
if ( testcase == 1 ) <checkout read>;
if ( testcase == 2 ) <checkout skip>;
if ( testcase == 3 ) <checkout writeover>;
}
printf ("After value [%d], seqno [%lld]", store<value>, store<sequence>);
On check-in to the reader zone, the value of the mel variable is set to 10, and let's assume that its sequence number is the number X. On checkout, we have this matrix, depending on the testcase taken:
| Local Properties | Mel Variable | |
|---|---|---|
| <checkout read> | The store<value> property has the value of the eponymous variable (hence, 11). The store<sequence> property has the value of the sequence number of the remote mel variable on check-in of the reader zone (hence X). |
The value of the remote mel variable is undefined since it has been removed by the <checkout read>. The sequence number of the remote mel variable does not change from the reader zone entry (hence X). |
| <checkout skip> | The store<value> property has the value of the mel variable on entry of the reader zone (hence, 10). The store<sequence> property also retains the sequence number got by reading the mel variable on check-in of the reader zone (hence X). |
The remote mel variable retains the same original mel value (hence 10) and sequence number (hence X) as on entrance of the reader zone. |
| <checkout writeover> | The store<value> property has the value of the eponymous variable (hence, 11). The store<sequence> property has the value of the sequence number of the remote mel variable on check-in of the reader zone plus one (hence X+1). |
The remote mel variable has the value of the eponymous variable (hence, 11). Its sequence number has been increased by one (hence X+1). |
Checkout Writeover
When a
checkout writeover is invoked, the sequence number of the remote mel variable is increased. This allows reader tasks that wait for a new mel value to get out of their slumber. The increased sequence number is also copied to the local <sequence> property of the task that does the checkout writeover. Identical sequence numbers indicate staleness, and this prevents this task to reprocess the mel variable in case the exclusive zone wait is resumed in an iterative loop. As a reminder, the local <sequence> property associated with this mel variable in other tasks is not updated until that particular task has successfully done a read or write operation to the mel variable; thus staleness does not apply to those other tasks -- they are free to read the new mel value.
The
checkout writeover mode is used frequently enough in NERWous C programs that it has a shortcut for a particular case. If a task enters a reader zone and knows that most of the ways that it will checkout of the zone is via the checkout writeover mode, it can specify this mode as the default mode on entrance of the reader zone.
The following code samples on the mel variable
store are equivalent:
/* Standard version for writeover mode */
<?>store {
store += 10; /* no matter what, alwsys increase store by 10 */
if ( !checkformore(store) ) <checkout writeover>;
store += 10; /* store can get another 10 */
<checkout writeover>;
}
---
/* Writeover mode default version */
<? mode=writeover>store {
store += 10; /* no matter what, alwsys increase store by 10 */
if ( !checkformore(store) ) <checkout>; /* writeover is assumed */
store += 10; /* store can get another 10 */
} /* <checkout writeover> is assumed
---
/* Omitted mode keyword */
<?writeover>store {
store += 10; /* no matter what, alwsys increase store by 10 */
if ( !checkformore(store) ) <checkout>; /* writeover is assumed */
store += 10; /* store can get another 10 */
} /* <checkout writeover> is assumed
Like the writeover mode for a mel variable wait, the mode keyword can be omitted for a mel zone wait.
Reader Zone Attributes
Like any mel wait operation, a mel reader zone wait has the following attributes:
| Attribute | Synopsis |
|---|---|
| mode | Default mode on checkouts |
| priority | Wait to check in using this priority |
| timeout | Wait to check in up to this timeout value. If the timeout is reached, a <TIMEOUT> exception will be raised. |
To illustrate the use of the priority and timeout attributes, let's introduce a new task called the
Manipulator. whose purpose is to surreptitiously convert any products from the Producer into an even number.
#include "nerw.h"
#define DO_EVEN 0
#define DO_ODD 1
main () {
<mel> int store;
<pel> p = <!>Manipulator (store);
<!>Producer (store);
<!>Consumer (store, DO_EVEN);
<!>Consumer (store, DO_ODD);
}
/* VERSION 1 */
void Manipulator(<mel> int store) {
while (1) {
try {
printf ("Yet another round of waiting...");
<? priority=NERW_PRIORITY_HIGHEST timeout+timeout>store {
store += store; // double store to make it even
<checkout writeover>;
}
}
catch ( store<CLOSED> ) { break; }
catch ( store<TIMEOUT> ) {
printf ("Is something wrong with Producer?");
continue;
}
}
}
The task main pels the task Manipulator and makes sure that is running by using the pel variable assignment. Then the task main pels the Producer and the two Consumer tasks like before. The codes for Producer and Consumer are the same as in the original example, and are not repeated here.
-
Side note:
The use of the pel variablepforManipulatoris to prevent a potential race condition. A pel operation (<!>) is only a request to run the code in a separate task, not an assurance that the task has actually run. Without the pel variable assignment, there is no guarantee that theManipulatorwill run before aConsumertask. If aConsumergets assigned to faster resources and starts to execute beforeManipulator, some initial products may go fromProducertoConsumerwithout beingManipulated.
Priority Attribute
The two
Consumer tasks and the Manipulator task vie to enter the exclusive reader zone. Since the Manipulator task waits at the highest possible priority for the run-time environment in use, it will be given first access. The Consumer tasks do not specify a priority and thus will be waiting in the default priority queue. The priority queues used for the reader zones are the same priority queues used for the regular mel reads.
When
Manipulator is in the reader zone, it turns the eponymous store value into an even number by doubling it. It then gets out of the zone with a <checkout writeover> operation, resulting in the mel variable value being replaced with the new double even value. The Consumer task that is first in line in the default queue is now given exclusive access to the mel variable. If this is the DO_EVEN task, it will consume the manipulated value, and removes that value off the mel variable. If this is the DO_ODD task, it will get off the reader zone with a <checkout skip> operation, leaving the mel variable value untouched for the upcoming DO_EVEN task.
For all the three tasks, the local <sequence> property of the mel variable is updated, allowing them to wait for a new mel
store value from the Producer instead of re-processing a stale value.
Timeout Attribute
The
Manipulator also makes use of the timeout attribute during the reader zone wait. If it cannot get into the zone after double the default system timeout (timeout + timeout), it will jump into the store<TIMEOUT> exception handler, prints a message, and iterates back to the while loop to wait again.
The timeout exception as used in this example may allow a
Consumer task to sneak in while the Manipulator is running the exception handler, and process an un-manipulated product from Producer.
Mode Attribute
If a task checks into a reader zone and knows that most of the ways that it will check out of the zone is via a certain checkout mode (be it
read, skip or writeover), it can specify this mode on the check-in of the reader zone, using the mode attribute.
When specifying the
mode attribute, the keyword mode can be ignored. Instead, just the value of the mode (either read, skip or writeover) is specified. In the examples below, we will use this condensed short-form format.
Read Mode Attribute:
This is not necessary because the default mode is already the
read mode. However a programmer may feel compel to mention it explicitly for emphasis, especially if one of the checkout cases will be in a different mode. Let's rewrite the Consumer example above with an explicit read mode:
void Consumer (<mel> int store, int evenodd) {
while ( 1 ) {
try {
printf ("Waiting to enter reader zone");
<?read>store {
if ( ((evenodd == DO_EVEN) && (store % 2) == 0))
|| ((evenodd == DO_ODD) && (store % 2) != 0)) ) {
consume (store);
printf ("Consumed product [%d]", store);
}
else {
printf ("Skipped product [%d]", store);
<checkout skip>;
}
}
printf ("Stepped back from reader zone with value [%d]", store<value>);
}
catch ( store<CLOSED> ) {
printf ("Exit because the Store is closed");
break;
}
}
}
Skip Mode Attribute:
Now let's rewrite the
Consumer example with the skip mode as the default mode:
void Consumer (<mel> int store, int evenodd) {
while ( 1 ) {
try {
printf ("Waiting to enter reader zone");
<?skip>store {
if ( ((evenodd == DO_EVEN) && (store % 2) == 0))
|| ((evenodd == DO_ODD) && (store % 2) != 0)) ) {
consume (store);
printf ("Consumed product [%d]", store);
}
else {
printf ("Skipped product [%d]", store);
<checkout>; /* this is now checkout skip */
}
}
printf ("Stepped back from reader zone with value [%d]", store<value>);
<checkout read>; /* need this */
}
catch ( store<CLOSED> ) {
printf ("Exit because the Store is closed");
break;
}
}
}
We need to add a checkout read statement before getting out of the reader zone because the implicit checkout now means checkout skip due to the default mode now set to skip.
Writeover Mode Attribute:
The
writeover mode has been discussed in the Checkout Writeover section:
void Consumer (<mel> int store, int evenodd) {
while ( 1 ) {
try {
printf ("Waiting to enter reader zone");
<?writeover>store {
if ( ((evenodd == DO_EVEN) && (store % 2) == 0))
|| ((evenodd == DO_ODD) && (store % 2) != 0)) ) {
consume (store);
printf ("Consumed product [%d]", store);
}
else {
printf ("Skipped product [%d]", store);
<checkout skip>; /* need to be specific */
}
}
printf ("Stepped back from reader zone with value [%d]", store<value>);
<checkout read>; /* need to be specific */
}
catch ( store<CLOSED> ) {
printf ("Exit because the Store is closed");
break;
}
}
}
While the default mode is writeover, none of the checkouts are writeover, thus the checkout modes must be specifically designated.
Reader Zone Resumptions
An astute reader will notice that there is a flaw in the VERSION 1 of the
Manipulator above. This flaw may allow a Consumer to access the store variable before Manipulator has a chance to modify it, even though Manipulator is on a higher priority queue.
In VERSION 1, after the reader zone checkout,
Manipulator is not on any mel waiting queue. It jumps back to the beginning of the while loop, checks the while loop condition (which is always true), prints to the console the "Yet another round of waiting..." message, and then finally gets back to the high priority mel waiting queue. During this interval, it may happen that the waiting Consumer task is a DO_EVEN one. It will get into the reader zone and processes the mel value that Manipulator has changed, and removes the mel value on checkout. If the mel variable is buffered, the Producer may have feed the subsequent slots. The processing gap that Manipulator has left open, may be enough for the DO_ODD Consumer that is next in line in the default queue to grasp the next item in the mel buffer. If this item is odd, it will be consume'd, defeating Manipulator's goal to manipulate all products to be even.
To resolve this flaw, we will use the
resume operation.
/* VERSION 2 */
void Manipulator(<mel> int store) {
while (1) {
try {
printf ("Yet another round of waiting...");
<? priority=NERW_PRIORITY_HIGHEST timeout+timeout>store {
store += store; // double store to make it even
<checkout writeover>;
} <resume>;
}
catch ( store<CLOSED> ) { break; }
catch ( store<TIMEOUT> ) {
printf ("Is something wrong with Producer?");
continue;
}
}
}
The <resume> operation must be invoked at the closing bracket of the reader zone code block. This instructs the CHAOS run-time to put the task right back to the waiting queue for re-entry to the reader zone instead of having it totally leave the queue. The task is put at the end of the waiting queue to give tasks already in the queue the chance to get into the reader zone.
By invoking
<resume>, the Manipulator task bypasses the while loop iteration and the "Yet another round of waiting..." printing. If this message is not necessary, we can simplify Manipulator by omitting the while loop and use only the resumption for iterations:
/* VERSION 3 */
void Manipulator(<mel> int store) {
try {
<? priority=NERW_PRIORITY_HIGHEST timeout+timeout>store {
store += store; // double store to make it even
<checkout writeover>;
} <resume>;
}
catch ( store<CLOSED> ) { }
catch ( store<TIMEOUT> ) {
printf ("Is something wrong with Producer?");
<resume>;
}
}
With the removal of the while loop, the break statement inside the <CLOSED> exception handler is unnecessary and is thus removed. On the other hand, the continue statement inside the <TIMEOUT> handler is replaced by a <resume> statement to allow Manipulator to resume its attempt to check into the reader zone again.
Again the astute reader will notice a processing gap between the
TIMEOUT exception occurrence and the resume invocation. When an exception occurs. the task is moved out of the waiting queue. By the time Manipulator puts itself back on the highest priority queue via resumption, one or more buffered store products may have been processed by the Consumer tasks. This is the characteristic of a resumption within an exception handler - NERWous C programmers must be aware of it and code the logic of their programs accordingly.
Previous Next Top
Tuesday, April 12, 2016
Mel Readonly Access
Welcome
» NERWous C
» Mel
Readonly Statement
In the previous section, we have an
The
Because of the potential wait, the mel readonly statement supports the
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
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
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,
Let's modify
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
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:
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
- Readonly Statement
- Readonly Abbreviation
- Readonly Queue
- Sequence Property
- Readonly On Read
- Readonly Mels
- 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
Writers Subscriptions
The previous example introduces the
In the beginning, when a mel is created, its writers subscription is undefined. The first producer task that executes a write operation (such as
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
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,
It is worthwhile to re-emphasize that the
The following example shows the use of the
The
The
Quit vs. Close
The mel close operation is a more drastic and global way for the
The first
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
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,
Like the
The following example shows the use of the
Dual Subscriptions
A task can have both readers and writers subscriptions.
We will now modify the example above by introducing an
Quit Attributes
The above implementation of
The same argument applies similarly for the <quit writers> command, any waiting
Astute readers of the
Previous Next Top
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
Thursday, March 24, 2016
Mel Location
Welcome
» NERWous C
» Mel
At Attribute
As introduced, the mel variable is an abstract element that stores values. To store a physical entity, the mel must have a physical location somewhere. In general, applications do not care about this somewhere. Then there are cases where the application must know where this location is. In NERWous C, this capability is supported by the
If the above program runs a distributed process over the web then the
To run in a distributed web environment, besides the configuration file, the code must be compiled with a NERWous C library for web transactions. To run the same code in another environment (such as a closely-coupled shared-memory supercomputer), the above code has to be recompiled with the appropriate library.
At Value
The value of the
When an id value is used, a stand-in cel entity is automatically created by the
DOWN Exception
What happens if the cel location is down? We can capture this condition with the
The
The
Examples
The following examples make use of the mel location
Previous Next Top
At Attribute
As introduced, the mel variable is an abstract element that stores values. To store a physical entity, the mel must have a physical location somewhere. In general, applications do not care about this somewhere. Then there are cases where the application must know where this location is. In NERWous C, this capability is supported by the
at attribute of the mel command:
extern <cel> NODE;
main () {
<mel at=NODE> 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 cel NODE is defined as an extern. Its definition will come from a configuration file that is associated with the above NERWous C program. This configuration file is specific to the runtime environment, and allows the same NERWous C program to be run on different physical platforms.
If the above program runs a distributed process over the web then the
NODE value will contain the address of a web page. The read and write operations to the mel translate to sending a request to the web page and waiting for the result. The code behind the web page will implement the mel characteristics such as mutual exclusion, priorities and timeouts. The real storage for the mel is also behind the web site, and can be a persistent memory, a file or even a record in a database. All this is transparent to the Producer / Consumer program.
To run in a distributed web environment, besides the configuration file, the code must be compiled with a NERWous C library for web transactions. To run the same code in another environment (such as a closely-coupled shared-memory supercomputer), the above code has to be recompiled with the appropriate library.
At Value
The value of the
at attribute is either a computer element (cel) variable or the identification value of the computer element. For example:
#define NODEURL "https://melstore.nerw/"
<cel>storeloc;
storeloc<id> = NODEURL;
<mel at=storeloc> int store1;
<mel at=NODEURL> int store2;
In the first use, the cel variable storeloc is initialized with the NODEURL id (which is a web address in this example), and used as the location of the mel store1. In the second use, the mel variable store2 specifies the NODEURL id directly, and thus resides in the same location as store1.
When an id value is used, a stand-in cel entity is automatically created by the
at attribute. This cel can be later accessed via the mel property location:
<mel at=NODEURL> int store2;
cel storeloc2 = store2<location>;
printf ("The id of storeloc2 (%s) is the same as (%s)", storeloc2<id>, NODE);
In the above example, the NODEURL id as a web address is built-in with the code via the #define macro. Unless the above program is written to run only over the web and at this specific web address, it is not recommended to hard code the cel id. In general, this value should be read in at run time from some configuration file. This will allow the same compiled code to be run on the same environment with different locations (e.g. same web environment but with different web addresses). If the runtime environment changes, such as going from a distributed web environment to a shared-memory supercomputing platform, using a different configuration file is required but not enough. The code has to be recompiled with the corresponding library, with a pre-compiler that translates all NERWous C constructs to instructions that are supported on the new platform.
DOWN Exception
What happens if the cel location is down? We can capture this condition with the
DOWN exception:
extern <cel> NODE;
main () {
<mel at=NODE> int store;
<!> Producer (store);
<!> Consumer (store);
}
void Producer (<mel> int store) {
int c;
char *reason;
try {
while ( c = Produce() )
<?>store = c;
reason = "No more product";
}
catch ( store<DOWN> ) {
printf ("The DOWN exception is about %s", store<why>);
reason = store<location> + " is down due to " + store<why>;
}
<close why=reason>store;
}
void Consumer (<mel> int store) {
try {
while ( 1 )
Consume(<?>store);
}
catch ( store<CLOSED> ) printf (%s, store<why>);
catch ( store<DOWN> ) printf (%s, store<why>);
}
The Producer keeps running until it cannot Produce anymore, or until it detects that the cel that hosts the mel store is down, preventing it to deposit the produced item. When the DOWN exception is raised, the Producer prints the reason of the exception as recorded in the why property of the mel store, gets out of the while loop, and then closes the mel with the proper closure reason.
The
Consumer task watches for two exceptions, the CLOSED exception and the DOWN exception. The former happens when the Producer invokes the close command, and the latter is due to the Consumer detecting that the hosting cell has been down. The why property due to the CLOSED exception is under the programmer's control as seen in the Producer code.
The
why property due to the DOWN exception comes from the underlying environment. For example, in a distributed web environment, the web site that serves as the cel may be down, or there is a networking issue that prevents access to the web site. The NERWous C library for the distributed web collects these and other errors and reports back to the application level via the why property. Not all libraries can do such collection. In that case the why property for the DOWN exception will be blank.
Examples
The following examples make use of the mel location
at attribute:
Previous Next Top
Tuesday, March 8, 2016
Mel Abortions
Welcome
» NERWous C
» Mel
Abort Operation
In the previous example of the Producer / Consumer, we have a
Abort Value
In the above code, we see that the
If the
Abort and Close
Due to its work being local, the
The
Abort Multi-Value
In the example on mel AND waits, we show a
In the above example, it is a compile-time error to do an
Previous Next Top
Abort Operation
In the previous example of the Producer / Consumer, we have a
OneTimeConsumer that gets out of the resumed waits rather abruptly, by terminating its task with a return statement. For a more refined exit strategy, the following code shows the use of the abort mel operation.
void OneTimeConsumer (<mel> int store) {
int retries = 5;
int myprio = 10;
try {
Consume(<? timeout priority=myprio>store);
printf ("I just consumed [%d]", store);
} catch ( store<TIMEDOUT> ) {
if ( --retries >= 0 ) {
myprio++;
<resume priority=myprio timeout+1>store;
}
<close>store; // close to inform the producer of no more consumption
<abort value=0>store;
}
return; // exit the task
}
When all the retries it can support have exhausted, OneTimeConsumer closes the mel store to inform any late-coming producers not to bother with this channel. It then invokes the mel operation abort with a special value to mark that there is no bona-fide product (here we choose 0). The code flow resumes at the <? timeout priority=myprio>store statement where the mel wait is now broken by abort. The special value of 0 is given to Consume so it can do whatever it needs to do with a non-product value. When Consume returns, the code flow to the printf statement, then out of the try / catch block, and finally to the return statement where the task OneTimeConsumer terminates.
Abort Value
In the above code, we see that the
abort operation contains a returned value. This value initializes the value property of the mel store. This value property is normally initialized by the mel wait statement through the retrieval of an item from the mel queue. The abort operation seeds this value locally without going over the network to the remote mel.
If the
abort returned value is not specified, the value property of the mel variable retains the value of the last successfully retrieved item from the mel queue. If no retrieval has been done successfully, the value property contains an undefined value. Therefore, although syntactically optional, the abort returned value is logically required so that the code can handle non-produced item deterministically.
Abort and Close
Due to its work being local, the
abort operation can be run after the close operation without raising the CLOSED exception like other mel operations.
The
close operation, if needed, must be put before the abort operation since abort makes the code flow jump back to the mel wait operation. Any statement after abort within the execution block will be ignored.
Abort Multi-Value
In the example on mel AND waits, we show a
Consumer that consumes two mel elements at the same time. Let's modify our OneTimeConsumer to do the same, and see how we can program the abort operation:
void OneTimeConsumer (<mel> int store1, <mel> int store2) {
int retries = 5;
int myprio = 10;
try {
ConsumeTwo(<? timeout priority=myprio>(store1 && store2));
printf ("I just consumed [%d] and [%d]", store1, store2);
} catch ( (store1 && store2)<TIMEDOUT> ) {
if ( --retries >= 0 ) {
myprio++;
<resume priority=myprio timeout+1>(store1 && store2);
}
<close>store1;
<close>store2;
<abort value=(0, 0)>(store1 && store2);
}
return; // exit the task
}
Since the mel wait is on both mel variables, the abort operation must be applied to both, with two return values for the ConsumeTwo function that requires two arguments.
In the above example, it is a compile-time error to do an
abort just on an individual mel (e.g. <abort value=0>store1), while the mel wait is on multiple mel variables.
Previous Next Top
Saturday, March 5, 2016
Mel Resumptions
Welcome
» NERWous C
» Mel
Resume Operation
Let's take a look at this
Let's enhance the code so that we also get a longer timeout on resumed waits:
Resume With Attribute
In the example above, if there is no forthcoming producer, the combination of
With Assignment vs. Mel Assignment
Let's compare the use of the
Another difference is that a mel assignment means a trip to the remote mel variable to deposit the
Resume With vs. Code Duplication
Instead of doing a
Resume In Exceptions
When a task waits for multiple mels, it can also use the
Once
On the first wait of
The
On the
The
Resume, Break and Continue
We started with VERSION 1 of the
With the
While waiting on the mel
The most likely scenario is that
The last exception is the catch-all exception,
Of course the C language also offers the
Resume In Exclusive Zones
See exclusive zones.
Previous Next Top
- Resume Operation
- Resume With Attribute
- Resume In Exceptions
- Resume, Break and Continue
- Resume In Exclusive Zones
Resume Operation
Let's take a look at this
Consumer task that only needs to Consume once:
/* VERSION 1 */
void Consumer (<mel> int store) {
int myprio = store<priority>;
try {
Consume(<? timeout priority=myprio>store);
} catch ( store<TIMEDOUT> ) {
++myprio;
}
}
When the default timeout expires, the TIMEDOUT exception is raised. The Consumer adds one to its mel wait priority to increase its chance to get a value on its next try. The problem is there is no loop in the logic to support that trial iteration. We could re-introduce a loop but that loop would be artificial. Instead we will use the resume mel operation:
/* VERSION 2 */
void Consumer (<mel> int store) {
int myprio = store<priority>;
try {
Consume(<? timeout priority=myprio>store);
} catch ( store<TIMEDOUT> ) {
++myprio;
<resume>;
}
}
The <resume> mel operation retries the latest <?> mel wait. In the above example, the resumption is at the mel wait for the Consume function. The newly resumed mel wait has the same default timeout value but with its priority increased by one by the exception handler.
Let's enhance the code so that we also get a longer timeout on resumed waits:
/* VERSION 3 */
void Consumer (<mel> int store) {
int myprio = store<priority>;
int mywait = store<timeout>;
try {
Consume(<? timeout=mywait priority=myprio>store);
} catch ( store<TIMEDOUT> ) {
++myprio;
mywait += 10;
<resume>;
}
}
The mel wait at the statement Consume starts with the default timeout and priority. After the first TIMEDOUT exception, the program retries the mel wait statement with a timeout increased by 10 msec and a priority increased by 1. After the second TIMEDOUT exception, the wait will have 10 msec more and a priority increased one more notch. Hopefully after awhile, the mel wait will be successful and a retrieved value can be given to Consume.
Resume With Attribute
In the example above, if there is no forthcoming producer, the combination of
TIMEDOUT exception and resume operation will cause the Consumer task to loop forever. Let's handle that condition:
/* VERSION 4 */
#define DUMMYVALUE 0
void Consumer (<mel> int store) {
int myprio = store<priority>;
int mywait = store<timeout>;
try {
Consume(<? timeout=mywait priority=myprio>store);
}
catch ( store<TIMEDOUT> ) {
++myprio;
mywait += 10;
<resume>;
}
catch ( store<CLOSED || NOWRITERS> ) {
<resume with=DUMMYVALUE>; // last resumption
}
return; // exit the task with consumption
}
If the producer is done and has closed the mel, the CLOSED exception is raised. If there is no more producers for the mel, the NOWRITERS exception is also raised. We catch either exception. We could flow out of the catch code and returning to close the Consumer task. Instead Consumer does one more retry.
Consumer wants to give a dummy value to the function Consume for its own closure (such as for Consume to print "no value found"). It accomplishes this by invoking the resume operation with the with attribute set to the dummy value. The Consumer task jumps back to the mel wait statement, and runs the Consume function with the dummy value as argument. Upon consumption, the Consumer task flows out of the try / catch setup and returns for the task closure.
With Assignment vs. Mel Assignment
Let's compare the use of the
resume with the with attribute with a mel assignment followed by a simple resume:
catch ( store<CLOSED || NOWRITERS> ) {
<?>store = DUMMYVALUE;
<resume>;
}
The mel assignment way above is fraught with gotcha's. It is possible that after the store mel variable gets filled with the DUMMYVALUE, and before the Consumer task gets resume'd back to the Consume statement, another task that is waiting on the store read queue will pick up that value, leaving Consumer with nothing for its Consume function.
Another difference is that a mel assignment means a trip to the remote mel variable to deposit the
DUMMYVALUE. The mel sequence property is also updated. On the other hand, a with assignment is local to the Consumer task. As far as the store mel variable is concerned, it never gets this DUMMYVALUE.
Resume With vs. Code Duplication
Instead of doing a
resume-with, we can also run the Consume function directly in the catch block:
catch ( store<CLOSED || NOWRITERS> ) {
Consume(DUMMYVALUE);
}
This is definitely more efficient, since there is no resumption jump-back. However, let's look at another version of Consumer:
/* VERSION 5 */
#define DUMMYVALUE 0
void Consumer (<mel> int store) {
int myprio = store<priority>;
int mywait = store<timeout>;
try {
Consume(<? timeout=mywait priority=myprio>store);
ReportConsumptionToHigherAuthority();
}
catch ( store<TIMEDOUT> ) {
++myprio;
mywait += 10;
<resume>;
}
catch ( store<CLOSED || NOWRITERS> ) {
<resume with=DUMMYVALUE>; // last resumption
}
return; // exit the task with consumption
}
With the resume-with method, the ReportConsumptionToHigherAuthority will also be executed for the DUMMYVALUE. This allows all the logic for the consumption to be in the try block, instead of being duplicated in a catch block for the special value of DUMMYVALUE:
try {
Consume(<? timeout=mywait priority=myprio>store);
ReportConsumptionToHigherAuthority();
}
catch ( store<CLOSED || NOWRITERS> ) {
Consume(DUMMYVALUE);
ReportConsumptionToHigherAuthority();
}
Of course if DUMMYVALUE needs to be treated differently, then its logic should be coded in the catch block, and resume-with should not be used:
catch ( store<CLOSED || NOWRITERS> ) {
Consume(DUMMYVALUE);
LetCelebrateOfJobWellDone();
}
Resume In Exceptions
When a task waits for multiple mels, it can also use the
resume operation. Let's look at this new Consumer task which consumes two mel variables, store1 and store2:
/* VERSION 6 */
int Consumer (<mel> int store1, <mel> int store2) {
try {
int retries;
string steps = "CONS_BOTH";
ConsumeBoth (<? timeout>(store1 && store2));
steps += "-CONS_ONE"; retries = null;
Consume (<? timeout>store1);
steps += "-CONS_TWO";
Consume (<? timeout>store2);
steps += "-CONS_ONE_AGAIN"; retries = 0;
Consume (<? timeout>store1);
return SUCCESS;
}
catch ( (store1 && store2)<TIMEDOUT> ) {
printf ("Can't get both values in time - Try again");
<resume>;
}
catch ( (store1 && store2)<CLOSED> ) {
return RESOURCE_FAILURE;
}
catch ( store1<TIMEDOUT> ) {
printf ("Can't get [store1] at steps [%s] in time - Try again", steps);
if ( retries == null ) <resume>;
else if ( ++retries < 5 ) <resume>;
else <resume with=DUMMYVALUE>;
}
catch (store2<TIMEDOUT> ) {
printf ("Can't get [store2] in time - Try again");
<resume>;
}
catch ( (store1 || store2)<CLOSED> ) {
<resume with=null>;
}
}
The ConsumeBoth expects two arguments, and waits for them at the mel AND wait. If this wait times out, the catch ( (store1 && store2)<TIMEDOUT> ) exception is invoked causing the "Can't get both values in time - Try Again" message to be shown. The resume statement will return the processing flow back to the mel AND wait for ConsumeBoth. This looping continues until both store1 and store2 are valued, or until both of them are closed. In the latter case, the code will continue at the catch ( (store1 && store2)<CLOSED> ) exception code, where the Consumer will exit with a RESOURCE_FAILURE.
Once
ConsumeBoth is satisfactorily run, the Consumer task waits for and Consume store1, then store2, and finally store1 again.
On the first wait of
store1 (i.e. at steps CONS_ONE), if the wait times out, the task will fall into the catch ( store1<TIMEDOUT> ) exception code block. For CONS_ONE, the local variable retries is always null, which instructs the Consumer to resume the wait. This timeout repeats until store1 is either valued or closed. For the first case, Consumer goes to step CONS_TWO for the consumption of store2. For the closed case, Consumer falls into the catch ( (store1 || store2)<CLOSED> ) exception code, where it resumes with a null value. The Consume function at the CONS_ONE mel wait is entered with this null value. It does whatever it does with this value, and then returns to the main flow, which then goes to step CONS_TWO.
The
Consumer task then waits on store2 until this mel variable is valued or closed. In either case, similarly to the analysis of store1 in step CONS_ONE above, the task will continue to step CONS_ONE_AGAIN.
On the
CONS_ONE_AGAIN wait on store1, the task has set the local variable retries to 0. This allows the task to do only 5 resume operations on timeout. On the 6th timeout, the Consumer will abort the wait by seeding the DUMMYVALUE to the Consume function so that the CONS_ONE_AGAIN step can finish.
The
Consumer task then returns SUCCESS.
Resume, Break and Continue
We started with VERSION 1 of the
Consumer which does a one-shot consumption. Let's modify Consumer to have it continuously Consume so that we can explore the difference between resume, continue and break:
/* VERSION 7 */
void Consumer (<mel> int store) {
while (1) {
printf ("Wait and consume ...");
int myprio = store<priority>;
try {
Consume(<? timeout priority=myprio>store);
}
catch ( store<TIMEDOUT> ) {
++myprio;
<resume>; /* resume at the wait statement */
}
catch ( store<FAILED> ) {
continue; /* iterate the while loop */
}
catch ( store<CLOSED> ) {
break; /* break out of the while loop */
}
catch ( store ) {
printf ("Unexpected exception [%s] ...", store));
}
printf ("One consumed\n");
}
printf ("All done!");
}
With the
while (1) loop, the Consumer task keeps Consume'ing until the mel store is closed by another process. When this happens, the catch ( store<CLOSED> ) exception handler will be entered. This handler ends with the standard C language break statement, allowing the program to break out of the while (1) loop. The "All done!" message is shown, and the task Consumer closes.
While waiting on the mel
store, the Consumer task may get hit by a FAILED exception. This exception may be caused by a permanent or temporary hiccup in accessing the remote mel location. Our Consumer task decides to retry by invoking the C language continue statement. This re-iterates the while (1) loop. The "Wait and consume ..." message is shown again.
The most likely scenario is that
Consumer will get hit by the TIMEOUT exception during its mel wait. The exception handler makes use of the NERWous C <resume> statement. Unlike the continue statement which iterates back to the while loop, the <resume> statement iterates back to the mel wait statement <? timeout priority=myprio>store. Any code inside the while loop before the mel wait statement will not be executed, including the printf for the "Wait and consume ..." message.
The last exception is the catch-all exception,
catch ( store ). After doing a printf of the unexpected exception, the code just flows through, the One consumed statement will be shown, and the program re-iterates to the while (1) loop. This differs from the continue statement which jumps back directly to the while (1) loop, bypassing the "Once consumed" message.
Of course the C language also offers the
goto statement to redirect the code flow. However we are not going there.
Resume In Exclusive Zones
See exclusive zones.
Previous Next Top
Subscribe to:
Posts (Atom)