Welcome
» NERWous C
» Mel
Mel Multiple Reads
Let's create a greedy
The
For a more compact code albeit less efficient, we can check the mel property
A
Mel OR Reads
The Version A of
The default method for a mel OR read is the parallel method. This method checks all the listed mel elements at the same time, and pick the first one that is read ready.
This is its behind-the-scene process for the sample statement below:
NERWous C supports another method called the OR serial read method. Here, the first listed mel is checked first. If it is not read ready, then the next listed mel is checked. Each mel is checked serially in the listed order until one is found read ready, or none is. If none of the mels are ready on the first pass, the task will do the OR parallel read and wait for the first mel that is read ready.
To re-emphasize, the serial checking is applicable only on the first pass. After that, the serial method behaves like the parallel method.
OR Read Methods Summary
Before moving on, let's summarize about the methods available for use in a mel OR read. The selected method is specified via the method attribute:
Since the values for the
OR Read Exceptions
Exceptions against mel OR reads are handled following these rules:
We will get to know more about these rules by looking at the TIMEDOUT and CLOSED exceptions during mel OR reads.
OR Read TIMEDOUT Exceptions
Common Timeout
When a task requests a mel OR read, it can include a timeout value, such as in:
Timeout Extension
Let's modify the
Timeout Combination Handler
In the above example,
What happens if the
OR Read CLOSED Exceptions
Let's revisit our full Producer/Consumer example:
Unlike the TIMEDOUT exception case where one mel wait timing out means that all the mel waits have also timed out, the CLOSED exception happens individually for each mel wait. One
When CHAOS detects that a mel has closed, it looks for a matching CLOSED exception to process. In our example here, there is none since the specified AND combination handler requires both mels to be closed. In this case, CHAOS will apply the Skipping rule for mel OR reads. It removes the task from the waiting queue of the closed mel, but keeps the task waiting for the other active mel element(s). Since a mel OR read only needs one mel to satisfy the mel READ conditions, the task can stay with the mel OR read operation, instead of being aborted.
In our example, let's assume that
Specific Closeouts
With the Skipping rule,
After processing the single exception handler, the
In both single handler cases, the Aborting rule will cause
Combined Closeouts
An alternative to develop single CLOSED exception handlers for each mel element in the OR list, is to write an OR combination CLOSED handler. Let's modify
as attribute and the <name> property. When either
On the first mel that has closed, the OR combination CLOSED handler is matched, and the handler wraps up with a <resume> operation to continue the wait on the other still operative mel element. If later on, this mel element is closed too, the AND combination CLOSED handler is then matched to
Asynchronous Mel OR Reads
Like the single mel asynchronous reads, the combination mel OR reads can also be invoked asynchronously. Let's look at this code snippet:
In the code snippet above, the task invokes the mel OR read asynchronously, and while CHAOS does the mel OR wait for its stead, the task does a
Instead of taking back the mel OR wait too soon, the task may be able to conduct several rounds of
When CHAOS has done with the waiting in one mel, it changes the <error> property of the corresponding mel variable from NERW_ERROR_PENDING to NERW_ERROR_NONE if the read is successful, or to one of the NERW_ERROR_FAILED code to mark failure.
In the above snippet, the
Always assuming success is not robust code. Let's throw in some error checking in the code snippet:
Mel AND Reads
While the mel OR reads are for getting a value from one of the listed mel elements, the mel AND reads are for getting values from all of them. The AND mel read supports the following methods to process the listed mel elements:
To illustrate the mel AND read, let's first replace the
We now make
The version above can be further shortened:
Cumulative AND Reads
Let's look at the behind-the-scene process of the cumulative method of a mel AND read with a timeout specified, such as:
Cumulative AND Reads Timeouts
Like all mel reads, timeouts are optional for mel AND reads. The task has to request it by specifying the
Cumulative AND Reads Closeouts
Another cause of failure is that one mel is closed. Since any mel in the AND list can be closed at any time, the recommended handler is the OR combination, and the get-off strategy is a
Let's consider some other scenarios:
Simultaneous AND Reads
The simultaneous method is a very special case of mel AND reads. It is used when there is a potential for circular deadlocks. To resolve such possibilities, it adds another layer between the mel readers' queues and the mel variables of the requesting tasks.
Let's look at the following example:
If the simultaneous method is not specified, the mel AND reads default to the cumulative method, and this situation can happen:
NERWous C does not define an implementation for the simultaneous method for mel AND reads. The computer literature contains a great number of discussions on resolving circular deadlocks due to competition of shared resources. These discussions usually relate to the Dining Philosophers problem. Depending on the physical and runtime environment, a particular algorithm may be a better fit than another; thus, the implementation is left to the designers of an actual NERWous C compilation or translation tool set.
In the above example, suppose that the
Simultaneous AND Reads Timeouts
The above example has the mel AND reads without timeouts. Let's now introduce this facility for the
There are similarities between timeouts for cumulative and simultaneous mel AND reads:
Simultaneous AND Read Closeouts
While the simultaneous AND wait is going on, one of the waited-on mel elements may get closed by some other task. Like for the closeouts for cumulative AND waits, the recommended handler for the <CLOSED> exception is the OR combination:
While there are many similarities between a cumulative and simultaneous mel AND read, there are many differences between them too.
Asynchronous Mel AND Reads
We have seen that the asynchronous read mode is applicable to mel OR reads. It is also applicable to mel AND reads, for both the cumulative and simultaneous methods. Let's take a look at this
As in the asynchronous read of single mels, the
The default AND read method is the cumulative method. If we want the
Error Handling For Asynchronous Mel AND Reads
Let's modify
The
For a successful read, the local <value> properties contain the read values, and
For the timeout failure, if the method used for mel AND read were the simultaneous method, its all-or-nothing would have both mel variables timed out. Since the cumulative method is in use here, one mel variable may already be valued (with its <error> property set to NERW_ERROR_NONE), and the other mel variable timed out. The task
Once a timeout has been detected,
Exception Handling For Asynchronous Mel AND Reads
An alternative to using the <error> property for checking for mel AND read results is to work with exceptions. Let's modify the
The second way that theConsumeTwo statement which has the same
In the catch-all
Previous Next Top
- Mel Multiple Reads
- Mel OR Reads
- OR Read Exceptions
- Asynchronous Mel OR Reads
- Mel AND Reads
- Cumulative AND Reads
- Simultaneous AND Reads
- Asynchronous Mel AND Reads
Mel Multiple Reads
Let's create a greedy
Consumer
that Consume
s from two Producer
tasks at the same time:
main () {
<mel> int store1;
<mel> int store2;
<!> Producer (store1);
<!> Producer (store2);
<!> Consumer (store1, store2);
}
void Producer (<mel> int store) {
while ( <?>store = Produce() );
<close>store;
}
void Consumer (<mel> int store1, <mel> int store2) {
bool store1valid = true;
bool store2valid = true;
while ( 1 ) {
if ( store1valid ) {
try { Consume(<? timeout>store1); }
catch ( store1<TIMEDOUT> ) {}
catch ( store1<CLOSED> ) { store1valid = false; }
}
if ( store2valid ) {
try { Consume(<? timeout>store2); }
catch ( store2<TIMEDOUT> ) {}
catch ( store2<CLOSED> ) { store2valid = false; }
}
if (!store1valid && !store2valid) break; // no more producers
}
}
The Consumer
uses a default timeout in both its mel reads. If the first Producer
is too slow, the timeout exception fires and allows the Consumer
to break out from the mel wait. It then tries the other Producer
.
The
Consumer
gets a CLOSED
exception if the corresponding Producer
has closed the mel element. The Consumer
remembers this CLOSED
status in a local variable. On future iterations, the Consumer
skips the closed mel element and works with the other Producer
only. When the Consumer
has detected that both mel elements have been closed, it will break
out of the infinite loop and ends itself.
For a more compact code albeit less efficient, we can check the mel property
CLOSED
instead of using a local variable:
/* VERSION A */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try { Consume(<? timeout>store1); }
catch ( store1<TIMEDOUT> ) {}
catch ( store1<CLOSED> ) {}
try { Consume(<? timeout>store2); }
catch ( store2<TIMEDOUT> ) {}
catch ( store2<CLOSED> ) {}
if (store1<CLOSED> && store2<CLOSED>) break;
}
}
The cleaner code is easier to follow. The price paid is a performance reduction. Even though a mel element has been closed, the Consumer
will continue to check it at each iteration and immediately gets the CLOSED
exception that it has to handle again. All this repetitive computation takes more time than simply checking a local variable that "remembers" the closed status.
A
CLOSED
or TIMEDOUT
exception to a mel wait is cleared when the task jumps out of the corresponding handler. For example, if Consumer
has detected that store1
has closed, the store1<CLOSED>
exception is raised during that iteration. The corresponding CLOSED handler does not have any visible code, but it does serve two purposes: it prevents the exception to become escalated, and it clears the exception. On the next iteration of the loop, when Consumer
invokes Consume(<? timeout>store1)
again, the store1<CLOSED>
exception is raised again when the mel wait detects that the mel element has indeed been closed. This back-and-forth processing continues until the Consumer
task detects that both mel elements have been closed. Only at that time, it break
s out of the while
loop, and ends.
Mel OR Reads
The Version A of
Consumer
is very deterministic: it Consume
s store1
then store2
. If a mel element is empty, it depends on the TIMEDOUT
exception to stop the wait on that mel element and switch to the other mel wait. In Version B below, the reading from two (or more) elements can be written more compactly using the mel OR read feature.
/* VERSION B */
void Consumer (<mel> int store1, <mel> int store2) {
int c;
while ( 1 ) {
try {
c = <? timeout>(store1 || store2);
Consume(c);
}
catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
An even more compact version of Consumer
removes the intermediary local variable c
:
/* VERSION C */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try { Consume(<? timeout>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
Parallel OR Read
The default method for a mel OR read is the parallel method. This method checks all the listed mel elements at the same time, and pick the first one that is read ready.
This is its behind-the-scene process for the sample statement below:
<? timeout>(store1 || store2)
:
- The
Consumer
task puts itself to bothstore1
andstore2
default readers' queues with the defaulttimeout
value.
- If the mel READ conditions are met at, for example
store1
, theConsumer
retrieves the mel value from that mel buffer, gets off thestore1
queue, aborts its wait on thestore2
mel by getting off thestore2
queue, exits the mel OR wait statement, andConsume
s thestore1
retrieved mel value. Similar processing is done if the mel READ conditions are met first atstore2
.
- If the mel READ conditions are not met at neither
store1
norstore2
, theConsumer
will wait on both queues. When the mel READ conditions become true for either one of the mel variables, theConsumer
task retrieves the mel value from that mel buffer, gets off both queues, exits the mel OR wait statement, andConsume
s the retrieved mel value.
- If it happens that the mel READ conditions become true at both mel elements at the same time, the CHAOS runtime will ensure that
Consumer
will retrieve value from one or the other mel element, and not both.
- If the mel READ conditions are not met at neither
store1
norstore2
by thetimeout
period, theConsumer
task will get off both queues, and raises the<TIMEOUT>
exception. This will be explored in a later section.
NERWous C supports another method called the OR serial read method. Here, the first listed mel is checked first. If it is not read ready, then the next listed mel is checked. Each mel is checked serially in the listed order until one is found read ready, or none is. If none of the mels are ready on the first pass, the task will do the OR parallel read and wait for the first mel that is read ready.
To re-emphasize, the serial checking is applicable only on the first pass. After that, the serial method behaves like the parallel method.
OR Read Methods Summary
Before moving on, let's summarize about the methods available for use in a mel OR read. The selected method is specified via the method attribute:
/* VERSION D */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try { Consume(<? timeout method=serial>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
This table summarizes the values of the method
attribute:
Mel OR Read Methods | |
---|---|
Method | Description |
parallel | This is the default method if the method attribute is not specified. It gives all the listed mel elements a fair chance to be picked. |
serial | To be in use, this method must be explicitly specified with the method attribute. This method establishes a hierarchy so that the first listed mel is the primary source, and the subsequent mels are backup sources. It is possible that none of the subsequent mels will be picked if the first listed mel is always read ready on the first pass. |
custom | A NERWous implementation may support a custom method specific to a particular underlying platform. If a custom method is not recognized, NERWous C will fall back to the default method. |
Since the values for the
method
behavior are reserved, their appearance in a mel OR read statement is sufficient without the use of the keyword method
. We can simplify Version D as follows:
/* VERSION E */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try { Consume(<? timeout serial>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) { printf ("Wait too long"); }
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
The examples for the mel OR reads make use of exception handling. This is now time to explore them in details.
OR Read Exceptions
Exceptions against mel OR reads are handled following these rules:
- Skipping rule: if an exception happens to a mel element, and there is no specific handler for that mel exception, the CHAOS runtime will remove the task from waiting on that mel element. The mel OR read continues with the mel elements that have not had the exception. In other words, the program flow of the task still remains in the mel OR read operation.
- Aborting rule: if there is a specific handler for that mel exception, the mel OR read is aborted. The task gets off all the waiting mel queues, including those that belong to mel elements that do not have exceptions; and the task then processes the identified handler. In other words, the program flow of the task moves out of the mel OR read operation into the exception handler.
- Escalation rule: if an exception happens to the last remaining mel element in a mel OR read, and there is no handler for that exception, the mel OR read is aborted with an unhandled exception. This exception escalates up the program flow until there is a handler. If no handler is found, the escalation will cause the task to abort (i.e. end abnormally).
- Precedence rule: when an exception can be processed by multiple handlers, the more restrictive handler gets the work. Let's use the TIMEDOUT exception with two mel elements,
store1
andstore2
, as an example:
- If there is one, a matching AND combination exception handler, such as:
will be picked first. This is the most restrictive handler since all the mels in the AND list must have that exception.catch ( (store1 && store2)<TIMEDOUT> ) { ... }
- The next handlers are single mel handlers, such as:
If more than one single mel handlers match, CHAOS will arbitrarily pick one to proceed with the abortion rule.catch ( store1<TIMEDOUT> ) { ...} catch ( store2<TIMEDOUT> ) { ... }
- The last handlers are the OR combination exception handlers, such as:
These are the least restrictive handlers since they match any of the listed mels, here eithercatch ( (store1 || store2)<TIMEDOUT> ) { ... }
store1
orstore2
.
- If there is one, a matching AND combination exception handler, such as:
We will get to know more about these rules by looking at the TIMEDOUT and CLOSED exceptions during mel OR reads.
OR Read TIMEDOUT Exceptions
Common Timeout
When a task requests a mel OR read, it can include a timeout value, such as in:
Consume(< timeout>(store1 || store2))
Consume (< timeout=1000>(store1 || store2))
Only one single timeout can be specified, and this same timeout value applies to all the mel waits in the OR list. W hen the time is up, all the mel waits will trigger the TIMEDOUT exception. If there are many matching single mel exceptions, CHAOS will arbitrarily pick one handler to process, as stated by the Precedence rule.
Timeout Extension
Let's modify the
Consumer
example again:
/* VERSION F */
void Consumer (<mel> int store1, <mel> int store2) {
int mypatience = 10; /* msec */
while ( 1 ) {
try { Consume(<? timeout=mypatience>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) {
printf ("Increase my patience");
mypatience += 5;
}
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
On the first round, Consumer
is willing to wait for 10
msec for either store1
or store2
to be valued. If the timeout occurs before this can happen, Consumer
jumps into the OR combination TIMEDOUT handler. It displays a printf
message, and increases the amount it is willing to wait for another 5
msec. When it gets off the handler, Consumer
iterates the while
loop to retry the mel OR read, now with the expanded timeout value.
Timeout Combination Handler
In the above example,
Consumer
uses an OR combination handler to catch the TIMEDOUT exception of a mel OR read. It could also use an AND combination handler, as seen in the following example:
/* VERSION G */
void Consumer (<mel> int store1, <mel> int store2) {
int mypatience = 10; /* msec */
while ( 1 ) {
try { Consume(<? timeout=mypatience>(store1 || store2)); }
catch ( (store1 && store2)<TIMEDOUT> ) {
printf ("Increase my patience");
mypatience += 5;
}
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
An AND combination handler is entered only if all the listed mel elements have caught the exception. Due to the timeout being common, all mel waits time out at the same time, satisfying the AND condition. Thus, for the TIMEDOUT exception, the OR catch and the AND catch are equivalent. This may not be the case for other types of exceptions, such as the CLOSED exception we will discuss in the next section.
What happens if the
Consumer
task has both the OR combination and the AND combination TIMEDOUT handlers, like this version:
/* VERSION H */
void Consumer (<mel> int store1, <mel> int store2) {
int mypatience = 10; /* msec */
while ( 1 ) {
try { Consume(<? timeout=mypatience>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) {
printf ("Increase my patience a lot ");
mypatience += 500;
}
catch ( (store1 && store2)<TIMEDOUT> )
printf ("Increase my patience a little");
mypatience += 5;
}
catch ( (store1 && store2)<CLOSED> ) { break; }
}
}
Based on the Precedence rule, the AND combination handler takes precedence over the OR combination handler since it is more restrictive by nature. Thus, the timeout value is increased by 5
instead of 500
in the above example.
OR Read CLOSED Exceptions
Let's revisit our full Producer/Consumer example:
main () {
<mel> int store1;
<mel> int store2;
<!> Producer (store1);
<!> Producer (store2);
<!> Consumer (store1, store2);
}
void Producer (<mel> int store) {
while ( <?>store = Produce() );
<close>store;
}
/* VERSION I */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
printf ("Start new iteration");
try { Consume(<? timeout>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) {
printf ("Wait too long"};
<resume>
}
catch ( (store1 && store2)<CLOSED> ) {
break;
}
}
}
In the above example, we have two separate Producer
tasks, one Produce
'ing for store1
, and the other for store2
. We have one single Consumer
task that Consume
s either from store1
or store2
, whatever is available at the moment. This Consumer
has only one exception handler for the CLOSED exception:
catch ( (store1 && store2)<CLOSED> )
Independent Closeouts
Unlike the TIMEDOUT exception case where one mel wait timing out means that all the mel waits have also timed out, the CLOSED exception happens individually for each mel wait. One
Producer
task may have closed its associated "store" mel element, but the other Producer
may still merrily Produce
'ing for the other "store".
When CHAOS detects that a mel has closed, it looks for a matching CLOSED exception to process. In our example here, there is none since the specified AND combination handler requires both mels to be closed. In this case, CHAOS will apply the Skipping rule for mel OR reads. It removes the task from the waiting queue of the closed mel, but keeps the task waiting for the other active mel element(s). Since a mel OR read only needs one mel to satisfy the mel READ conditions, the task can stay with the mel OR read operation, instead of being aborted.
In our example, let's assume that
store1
has been closed. The programmed AND combination handler is not matched since it requires that both store1
and store2
to be closed, and store2
is still operative. Eventually, store2
is valued and the Consumer
task will Consume
this value, and iterates the while
loop. The mel OR read is again attempted. This time, CHAOS notices that store1
has been closed, so it will not put the Consumer
task into store1
waiting queue. In this and subsequent iterations, the mel OR read on store1
and store2
is practically a single mel read on store2
.
Specific Closeouts
With the Skipping rule,
Consumer
may not care if a mel in the OR list has closed. What if it does? Let's modify Consumer
again:
/* VERSION J */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
printf ("Start new iteration");
try { Consume(<? timeout>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) {
printf ("Wait too long"};
<resume>;
}
catch ( store1<CLOSED> ) {
printf ("[store1] has closed");
<resume>;
}
catch ( store2<CLOSED> ) {
printf ("[store2] has closed");
continue;
}
catch ( (store1 && store2)<CLOSED> ) {
break;
}
}
}
The new version of Consumer
wants to do a printf
of any mel in the OR list that has closed. It achieves this by having two single mel exception handlers, one for store1<CLOSED>
and the other for store2<CLOSED>
.
After processing the single exception handler, the
Consumer
task still wants to wait for a value from the other operative mel element so that it can eventually Consume
that mel's value. The store1<CLOSED>
handler uses the NERWous C <resume> operation to directly retry the mel OR wait operation, bypassing the while
loop iteration. The store2<CLOSED>
handler uses the C language continue
statement which iterates the while
loop, to allow it to printf
another "Start new iteration"
message.
In both single handler cases, the Aborting rule will cause
Consumer
to also abort the waiting on the other mel that has not closed, and the program flow goes back to the Consumer
task. If there is no matching handler, then the Skipping rule takes precedence. The program flow stays with the CHAOS runtime, which simply skips the wait on any closed mels. Only when the last mel is closed, then CHAOS releases the program flow back to the task. Unless the task has a matching handler (such as an AND combination CLOSED handler), the exception will escalate per Escalation rule.
Combined Closeouts
An alternative to develop single CLOSED exception handlers for each mel element in the OR list, is to write an OR combination CLOSED handler. Let's modify
Consumer
again:
/* VERSION K */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
printf ("Start new iteration");
try { Consume(<? timeout>(store1 || store2)); }
catch ( (store1 || store2)<TIMEDOUT> ) {
printf ("Wait too long"};
<resume>;
}
catch ( (store1 || store2)<CLOSED as=store> ) {
printf ("[%s] has closed", store<name>);
<resume>;
}
catch ( (store1 && store2)<CLOSED> ) {
break;
}
}
}
The OR combination CLOSED handler makes use of the store1
or store2
is closed, this handler is matched, and the stand-in store
variable will be mapped to the mel variable of the closed mel. This allows the handler to use the <name> property to show the name of the closed mel.
On the first mel that has closed, the OR combination CLOSED handler is matched, and the handler wraps up with a <resume> operation to continue the wait on the other still operative mel element. If later on, this mel element is closed too, the AND combination CLOSED handler is then matched to
break
out of the infinite while
loop.
Asynchronous Mel OR Reads
Like the single mel asynchronous reads, the combination mel OR reads can also be invoked asynchronously. Let's look at this code snippet:
<? async parallel as=store>(store1 || store2);
doSomething ();
Consume (<?>store);
The first thing to notice is that the mode
and method
keywords have been omitted, since their values can uniquely identify them. The full mel OR read statement is as follows:
<? mode=async method=parallel as=store>(store1 || store2);
When a mel OR read is invoked asynchronously, the as
attribute with a stand-in value (here it is store
) is required. This allows the task to use the stand-in mel variable to check for the outcome of outsourcing the mel OR wait to the CHAOS runtime.
In the code snippet above, the task invokes the mel OR read asynchronously, and while CHAOS does the mel OR wait for its stead, the task does a
doSomething
chore. Once the chore is done, it issues the read statement <?>store
against the stand-in mel to check for the result of the mel OR read. If CHAOS has successfully done the read, the task will get the value from the local store<value>
property. Because this is an OR read, this value can come from either the mel element store1
or store2
. If CHAOS cannot do the read yet, then the task takes over the mel OR read of store1
and store2
. Instead of re-issuing the full mel OR read list, the task uses the stand-in store
mel variable.
Instead of taking back the mel OR wait too soon, the task may be able to conduct several rounds of
doSomething
by polling the wait:
<? async as=store>(store1 || store2);
while ( store<error> == NERW_ERROR_PENDING ) {
doSomething ();
}
Consume (store<value>);
printf ("CHAOS gets the value from [%s]", store<name>);
In the new code snippet, the task after issuing the asynchronous mel OR read, keeps polling for the success of the read by checking the <error> property of the stand-in mel variable. On entry of a mel OR read, the <error> property is set to NERW_ERROR_PENDING.
Side note: Without the stand-in variable, the polling check is more verbose:If CHAOS is still working on the request, the task can do itsThe verbose statement is also error-prone, with an OR (||) clause wrongly used instead of the correct AND (&&).while ( (store1<error> == NERW_ERROR_PENDING ) && (store2<error> == NERW_ERROR_PENDING) )
doSomething
chore. It checks back at the end of the chore, and keeps doing doSomething
for each while
loop iteration.
When CHAOS has done with the waiting in one mel, it changes the <error> property of the corresponding mel variable from NERW_ERROR_PENDING to NERW_ERROR_NONE if the read is successful, or to one of the NERW_ERROR_FAILED code to mark failure.
In the above snippet, the
Consumer
task assumes the asynchronous mel OR read is always successful; thus it expects a read value in the stand-in mel variable's <value> property. It can also know from which mel variable the read value comes from by checking the <name> property -- it is set to the string value "store1"
or "store2"
.
Always assuming success is not robust code. Let's throw in some error checking in the code snippet:
<? async as=store>(store1 || store2);
while ( store<error> != NERW_ERROR_NONE ) {
doSomething ();
if ( store<error> & NERW_ERROR_FAILED ) break;
}
if ( store<error> == NERW_ERROR_NONE ) {
Consume (store<value>);
printf ("CHAOS gets the value from [%s]", store<name>);
}
else {
printf ("Mel OR Read has failed on [%s] due to [%s]",
store<error>, store<why>);
}
We can also use the try/catch
exception method:
<? async as=store>(store1 || store2);
try {
while ( store<error> != NERW_ERROR_NONE ) {
doSomething ();
}
Consume (store<value>);
printf ("CHAOS gets the value from [%s]", store<name>);
}
catch ( store<...> ) {
printf ("Mel OR Read has failed on [%s] due to [%s]",
store<error>, store<why>);
}
If there is an error during the asynchronous mel OR read, the exception is triggered when the task checks on the store<error>
. It is not triggered during the doSomething
chore.
Mel AND Reads
While the mel OR reads are for getting a value from one of the listed mel elements, the mel AND reads are for getting values from all of them. The AND mel read supports the following methods to process the listed mel elements:
Mel AND Read Methods | |
---|---|
Method Value | Description |
Cumul | With the cumulative method, the task checks all the mel elements in the AND list, and accumulates the read values as they become available. This method is efficient, and is the default method. |
Simul | With the simultaneous method, the task checks all the mel elements in the AND list, wait for all of them to be available, then reads all of them at once. This method is more complex but does prevent deadlocks. |
custom | A NERWous implementation may support a custom method specific to a particular underlying platform. If a custom method is not recognized, NERWous C will fall back to the default method. |
To illustrate the mel AND read, let's first replace the
Consume
function which takes one argument, with the ConsumeTwo
function which takes two arguments:
main () {
<mel> int store1;
<mel> int store2;
<!> Producer (store1);
<!> Producer (store2);
<!> Consumer (store1, store2);
}
void Producer (<mel> int store) {
while ( <?>store = Produce() );
<close>store;
}
/* VERSION 1 */
void Consumer (<mel> int store1, <mel> int store2) {
int c1, c2;
while ( 1 ) {
try {
c1 = <? timeout>store1;
c2 = <? timeout>store2;
ConsumeTwo (c1, c2);
}
catch ( store1<TIMEDOUT> ) {
printf ("Can't get store1 in time");
<resume>; /* retry store1 */
}
catch ( store2<TIMEDOUT> ) {
printf ("Got store1 value of [%d]\n", store1<value>);
printf ("Can't get store2 in time");
<resume>; /* retry store2 */
}
catch ( (store1 || store2)<CLOSED> ) {
break; /* stop on first closure */
}
}
}
The Consumer
task first waits for store1
. Once it gets the value from store1
, it waits for store2
. Once it gets both values, it will invoke the serial function ConsumeTwo
with the two values as input arguments.
We now make
Consumer
more compact by using the mel AND read construct:
/* VERSION 2 */
void Consumer (<mel> int store1, <mel> int store2) {
int c1, c2;
while ( 1 ) {
try {
/* The mel AND wait returns two values to initialize c1 and c2 */
(c1, c2) = <? timeout as=store>(store1 && store2);
ConsumeTwo (c1, c2);
}
catch ( store<TIMEDOUT> ) {
printf ("Can't get %s in time", store<name>);
<resume>; /* retry store1 or store2 or both */
}
catch ( store<CLOSED> ) {
printf ("%s has closed", store<name>);
break; /* stop on first closure */
}
}
}
Comparing the two versions of Consumer
, the AND mel read version offers the benefit that all listed mels are waited in parallel, instead of serially one after the other, thus potentially cutting down on the overall wait time.
Side note: A computer linguist will note that the C language does not support the return of multiple values. A C function returns at most one value. Let's say that NERWous C has taken the liberty to expand the language with the support of multiple returns from a function. The multiple returns are enclosed within the ( ) markers. When checking for true and false, if all the returned values are true then the multiple return is true. If any returned value is false, then the multiple return is false.Notice the use of the stand-in mel variable,
store
. This allows the condition for a handler exception to be written more succinctly. For example, instead of catch (store1 || store2)<TIMEDOUT>
, we have catch (store<TIMEDOUT>)
. The identity of the mel that causes the exception can be found via the stand-in mel variable properties, <name> and <id>.
The version above can be further shortened:
/* VERSION 3 */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try {
ConsumeTwo (<? timeout as=store>(store1 && store2));
}
catch ( store<TIMEDOUT> ) {
printf ("Can't get %s in time", store<name>);
<resume>; /* retry store1 or store2 or both */
}
catch ( store<CLOSED> ) {
printf ("%s has closed", store<name>);
break; /* stop on first closure */
}
}
}
Without an explicit method, the AND mel wait uses the default cumulative method. The following statements are equivalent:
<?>(store1 && store2);
<? cumul>(store1 && store2);
<? method=cumul>(store1 && store2);
If the simultaneous method is desired, it has to be explicitly specified. The following statements are equivalent:
<? simul>(store1 && store2);
<? method=simul>(store1 && store2);
Since cumul
and simul
are unique keywords, NERWous C can associate them with the attribute method
, which can then be dropped.
Cumulative AND Reads
Let's look at the behind-the-scene process of the cumulative method of a mel AND read with a timeout specified, such as:
<? timeout>(store1 && store2);
- The task puts itself to the default readers' queues of all the listed mels, with the specified timeout value. All the corresponding mel variables have their <error> property reset to NERW_ERROR_PENDING, per On
Read Entry setting.
- If the mel READ conditions are met for one of the mel elements, CHAOS will do the read operation on that element. The top value of the mel buffer is removed and deposited into the <value> property of the corresponding mel variable. Its <error> property is changed to
NERW_ERROR_NONE
to denote that the read has been successful. The task takes itself off the wait queue of that mel element.
- The task collects available read values as they happen, and continues to wait at the other mels' queues until those mels also meet the mel READ conditions.
- Once the task gets a value of all the mels in the AND list, it gets off the AND mel read, and resumes the program's code flow. All the corresponding mel variables have their <value> property updated and their <error> property set to
NERW_ERROR_NONE
to mark all successful reads.
- The AND mel read can also ends with a failure, with the task not receiving all the values from the requested mels in the AND list. The first failure from any mel will cause the AND mel read to fail. For a cumulative method, when the task gets off an AND mel read with a failure, the mel variables of the listed mels have the following properties settings:
- Successful reads
- Mels that have been successfully read have their corresponding mel variables with <value> property updated and <error> property changed to
NERW_ERROR_NONE
. - Failed reads
- The mel that causes the failure has its corresponding mel variable with <value> property unchanged from entry, and <error> property set to the error code of the failure.
- Un-filled reads
- Mels that are still waiting to be read have their corresponding mel variables with <value> property unchanged from entry, and <error> property remaining at
NERW_ERROR_PENDING
.
Cumulative AND Reads Timeouts
Like all mel reads, timeouts are optional for mel AND reads. The task has to request it by specifying the
timeout
attribute in the mel AND read, such as:
<? timeout=10>(store1 && store2);
These are some notes about timeouts used with cumulative AND reads:
- A single timeout is applied to all the mel waits.
- Any mels that have been read before the timeout occurs, will be removed from the timeout consideration. Their corresponding mel variables have the <value> property updated and the <error> property changed to
NERW_ERROR_NONE
.
- When the timeout occurs, all remaining mel waits will time out, due to the single common timeout attribute. Their corresponding mel variables have the <value> property unchanged and the <error> property set to
NERW_ERROR_TIMEDOUT
.
- When a task gets off an AND mel read due to a timeout exception, the <error> property of the mel variables are either
NERW_ERROR_NONE
if a read has been done, orNERW_ERROR_TIMEDOUT
if a read has not happened yet. There will be no mel variable with the<error>
setting ofNERW_ERROR_PENDING
.
- When a timeout occurs, the task gets off all the remaining mel waits, and CHAOS raises a TIMEDOUT exception against the task. If the mel AND read has a matching handler, the programming flow will jump to the handler code. Otherwise, the exception is escalated to a higher exception handler of the task. If there is none, or none matching, the task is aborted and a pel exception is generated.
- CHAOS raises a TIMEDOUT exception to all the remaining mel waits. However since a program does not know which mels have been read and which ones not, the recommended TIMEDOUT handler for a cumulative mel AND read is the OR combination handler, such as:
If a stand-in mel variable is specified as an attribute to mel AND wait, the OR combination handler can be abbreviated to:catch ( (store1 || store2)<TIMEDOUT> ) { }
catch ( store<TIMEDOUT> ) { }
- If the logic of the program demands, single TIMEDOUT handlers can be added. For example:
The Precedence rule also applies to cumulative AND reads. When a timeout occurs, CHAOS needs to pick a handler to process that exception. If there are multiple matching handlers, CHAOS will pick the more restrictive one. Thus, if there are both matching single handlers and an OR combination handler, CHAOS will pick the single handler. If there are multiple single handlers that match, CHAOS will arbitrarily pick one.catch ( store1<TIMEDOUT> ) { printf ("Store 1 has timed out"); alertTheProducerOfStore1ToSpeedUp (); <resume>; }
- It is not recommended to have an AND combination handler for cumulative AND reads for TIMEDOUT exceptions, such as:
That handler does not work if eithercatch ( (store1 && store2)<TIMEDOUT> ) { printf ("All the mel waits time out at the same time"); <resume>; }
store1
orstore2
has been successfully read already. It can only work if neither has been read. However, in this case, the OR handler is still better since it also works with the "either" case.
Consumer
task to see how it gets off a cumulative AND read TIMEDOUT exception handler:
/* VERSION 4 */
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try {
ConsumeTwo (<? timeout as=store>(store1 && store2));
}
catch ( store1<TIMEDOUT> ) {
printf ("[Store1] has timed out");
informProducerToSpeedUp (1);
<resume>; /* retry store1 or both */
}
catch ( store<TIMEDOUT> ) {
printf ("Can't get %s in time", store<name>);
<resume>; /* retry store1 or store2 or both */
}
catch ( store<CLOSED> ) {
printf ("[%s] has closed", store<name>);
break; /* stop on first closure */
}
}
}
Let's run through some scenarios.
store1
times out- The
Consumer
task gets off all the queues it is still waiting on, and jumps into the single exception handlercatch ( store1<TIMEDOUT> )
. The reason for this handler is that the task can ask theProducer
ofstore1
to speed up. The handler then invokes the <resume> operation.
The <resume> operation does a fresh cumulative mel AND read, without iterating through thewhile
loop. Forstore1
that times out, theConsumer
task puts itself back into its waiting queue. Forstore2
, it is a little more complicated. Ifstore2
has been successfully read, thenConsumer
does not wait for it anymore. Otherwise,Consumer
also puts itself to the bottom start ofstore2
waiting queue.
store2
times out- The
Consumer
task gets off all the queues it is still waiting on, and jumps into the OR combination exception handler represented by the stand-in mel variablestore
:
After printing thecatch ( store<TIMEDOUT> )
store2
information, the handler invokes the <resume> operation.
The <resume> operation redoes the cumulative mel AND read. Forstore2
that times out, theConsumer
task puts itself back into the waiting queue. Forstore1
,Consumer
redoes the mel wait only ifstore1
has not been successfully read.
continue
to iterate the while
loop, then the mel AND read would also be invoked again, but in this new invocation, the Consumer
task would be automatically put into both mels waiting queues. If during the previous invocation (which has timed out), a value had been successfully read, this value would be irrevocably lost. On the other hand, the <resume> operation intends to make good on the existing cumulative mel AND read. Accumulated values are retained, and only un-filled values will be waited again.
Cumulative AND Reads Closeouts
Another cause of failure is that one mel is closed. Since any mel in the AND list can be closed at any time, the recommended handler is the OR combination, and the get-off strategy is a
break
. For example:
catch ( (store1 || store2)<CLOSED>) ) {
break; /* stop on first closure */
}
In the above example, the Consumer
task also wants to know which mel element has timed out. This is resolved by using the stand-in mel variable store
:
catch ( store<CLOSED> ) {
printf ("[%s] has closed", store<name>);
break; /* stop on first closure */
}
Let's consider some other scenarios:
- Single CLOSED exception handler
- Per Precedence rule, if a single handler exists and matches, it will be processed over the recommended OR combination handler:
The above handler only matches thecatch ( store1<CLOSED> ) { printf ("[store1] has closed"); break; }
store1
closeout. In our example, ifstore2
is closed, it will be handled by the OR combination handler.
- AND combination CLOSED exception handler
- This handler:
is more problematic than it is usually worth. Normally this handler will not get matched since both mels must be closed at the same time, which rarely happens if ever, because mel closures are usually independent from one another, and not synchronized.catch ( (store1 && store2)<CLOSED> )
One contrived scenario that can cause the AND combination CLOSED handler to ever get matched, is when there is a single handler that gets off with a <resume> operation. The AND mel wait is resumed, CHAOS detects that one mel has closed, the single handler is re-matched, the program flow jumps back into that single handler again, which again does a <resume> back into the mel AND read; this livelock spin prevents the task to do other work, and continues until the rest of the mels get closed by other task or tasks. Only at that time, due to the precedence rule, the AND combination CLOSED handler, which is more restrictive than any single handler, will get picked.
- No resumption on closeouts
- An AND mel read is successful if all the listed mels are successfully read. If one fails due to closure, the AND mel read will definitely fail. Like all exceptions, a CLOSED exception is cleared on exit of the serving handler. However while a timeout period is restarted on re-entry of a mel read, a mel closure condition remains and is detected immediately again, causing the CLOSED handler to be re-entered, resulting in a livelock spin.
If the AND mel read is inside an infinite loop, it is also not recommended to use thecontinue
get-off strategy. Let's look at this example:
The/* VERSION 5 */ void Consumer (<mel> int store1, <mel> int store2) { while ( 1 ) { try { ConsumeTwo (<?>(store1 && store2)); } catch ( (store1 || store2)<CLOSED>) ) { continue; } } }
continue
statement will loop back thewhile
loop, and a new mel AND read is attempted again. CHAOS will again see that one of the mels (or both) has closed, and transfer the program flow of the task to the CLOSED exception handler again, which again loops back to thewhile
loop. Again, we have a spinning livelock.
The only viable get-off strategy in case of a mel closure during an AND mel read is to get out of the read, and do not attempt to retry it via resumption or iteration.
- Orphan reads on closeouts
- A mel AND read has many mels listed. Some of these mels may have been successfully read before one of the remaining mels gets closed, which usually triggers the task to get off the mel AND read. What happens then to the values that have been read?
This is up to the task what to do with these values. Let's explore the scenarios:- Mels with successful reads:
- Mels that have been successfully read have their corresponding mel variables with <value> property updated and <error> property changed to
NERW_ERROR_NONE
. - Mels that have been successfully read have their corresponding mel variables with <value> property updated and <error> property changed to
- Closed mels:
- Mels that are closed, have their corresponding mel variables with <value> property unchanged and <error> property set NERW_ERROR_CLOSED.
- Mels still in waiting:
- Mels that are still waiting to be read have their corresponding mel variables with <value> property unchanged and <error> property remaining at
NERW_ERROR_PENDING
. - Mels that are still waiting to be read have their corresponding mel variables with <value> property unchanged and <error> property remaining at
The following example shows aConsumer
that does not want to lose any read values:
If the OR combination CLOSED handler is entered because of either/* VERSION 6 */ void Consumer (<mel> int store1, <mel> int store2) { while ( 1 ) { try { ConsumeTwo (<? as=store>(store1 && store2)); } catch ( store<CLOSED>) ) { if ( store1<error> == NERW_ERROR_NONE ) Consume (store1<value>); if ( store2<error> == NERW_ERROR_NONE ) Consume (store2<value>); break; } } }
store1
orstore2
closing, it checks if either mel variables have been successfully read, and if so, it willConsume
the read value, before breaking off thewhile
loop. - Mels with successful reads:
Simultaneous AND Reads
The simultaneous method is a very special case of mel AND reads. It is used when there is a potential for circular deadlocks. To resolve such possibilities, it adds another layer between the mel readers' queues and the mel variables of the requesting tasks.
Let's look at the following example:
<mel> int store;
main () {
<mel> int leftkey = 1;
<mel> int rightkey = 1;
<!>Producer ();
<!>ConsumerRH (rightkey, leftkey);
<!>ConsumerLH (leftkey, rightkey);
}
void Producer () {
while ( <?>store = Produce() );
<close>store;
}
void ConsumerRH (<mel> rightkey, <mel>leftkey) {
while ( 1 ) {
try {
<? method=simul>(rightkey && leftkey);
ConsumeRH (<?>store ) {
<?>rightkey = 1;
<?>leftkey = 1;
}
}
catch ( store<CLOSED> ) {
break;
}
}
}
void ConsumerLH (<mel>leftkey, <mel> rightkey) {
while ( 1 ) {
try {
<? simul>(leftkey && rightkey);
ConsumeLH (<?>store ) {
<?>leftkey = 1;
<?>rightkey = 1;
}
}
catch ( store<CLOSED> ) {
break;
}
}
}
Both "Consumer" tasks issue a mel AND read for the "right and left keys". The ConsumerRH
task uses the keyword method
to introduce the simultaneous method. As said previously, this keyword can be omitted since its values are well-known. The ConsumeLH
task takes advantage of this permission, and just uses the value simul
.
If the simultaneous method is not specified, the mel AND reads default to the cumulative method, and this situation can happen:
- The main task initializes the
leftkey
andrigthkey
- The
ConsumerRH
task grasps therightkey
value, causing therightkey
mel to become empty. - The
ConsumerLH
task grasps theleftkey
value, causing theleftkey
mel to become empty. - When
ConsumerRH
tries to grasp theleftkey
, it findsleftkey
to be empty. - When
ConsumerLH
tries to grasp therightkey
, it findsrightkey
to be empty. - The mel AND reads at
ConsumerRH
andConsumerLH
are stuck in a circular deadlock./li>
NERWous C does not define an implementation for the simultaneous method for mel AND reads. The computer literature contains a great number of discussions on resolving circular deadlocks due to competition of shared resources. These discussions usually relate to the Dining Philosophers problem. Depending on the physical and runtime environment, a particular algorithm may be a better fit than another; thus, the implementation is left to the designers of an actual NERWous C compilation or translation tool set.
In the above example, suppose that the
ConsumerRH
task already gets hold to the rightkey
mel value, and is waiting for the leftkey
value. If this value is given to the task ConsumerLH
then a deadlock will result, with each task holding a key and waiting for the other key, and no ex-machina producer task to generate new values for those keys. An implementation of the simultaneous method either prevents the deadlock to happen in the first place (e.g., by giving the leftkey
to ConsumerRH
to have one less mel AND wait cases pending), or resolves the deadlock after it happens (e.g., by yanking the rightkey
from ConsumerRH
and giving it to ConsumerLH
).
Simultaneous AND Reads Timeouts
The above example has the mel AND reads without timeouts. Let's now introduce this facility for the
ConsumerLH
task:
void ConsumerLH (<mel>leftkey, <mel> rightkey) {
while ( 1 ) {
try {
<? simul timeout as=key>(leftkey && rightkey);
ConsumeLH (<?>store ) {
<?>leftkey = 1;
<?>rightkey = 1;
}
}
catch ( key<TIMEDOUT> ) {
printf ("Got a timeout after [%d]\n", key<timeout> );
<resume timeout+10>;
}
catch ( store<CLOSED> ) {
break;
}
}
}
With this change, the task ConsumerLH
wants to have a checkpoint TIMEDOUT exception after the default timeout is reached. The exception entry key<TIMEDOUT>
uses the stand-in mel variable key
, and is the short-cut for (rightkey || leftkey)<TIMEDOUT>
. After the checkpoint printf
, the ConsumerLH
task increases the current timeout period by 10 msec, and <resume>s the mel AND wait at the starting positions in both mel queues.
There are similarities between timeouts for cumulative and simultaneous mel AND reads:
- Timeouts are optional.
If no timeout is specified, a simultaneous AND read will wait forever until all the listed mel elements are successfully read, or until one mel read fails due to other failures other than timeouts.
- The single timeout is applicable to all.
All mel waits use the same timeout value specified in the simultaneous mel AND read. This is true on the original timeout and also on updated timeouts. For example, theConsumerLH
task, in its TIMEDOUT exception handler, increases the amount of timeout by 10 msec for a subsequent resumption:
This new timeout value will be applicable to all mels in the simultaneous mel AND wait.<resume timeout+10>;
- All timeouts happen at the same time.
The simultaneous mel AND read does not collect mel values one at a time as they meet the mel READ conditions. The implemented AND read underlying algorithm may collect the mel values that way, but it only gives these values to a waiting task if all the mel values have been assembled. In other words, it is all or nothing. (If there are tasks that compete for circular shared resources, the task that receives all the waited mel values is the one that the algorithm decides that will prevent a deadlock to occur, or the one that resolves an occurring deadlock.)
Since a task cannot see a mel value until it sees all of them, a timeout that occurs in one mel wait, is applicable to all mels in the AND list. In the example above,ConsumerRH
catches the exception with an OR combination handler:
which is abbreviated to this statement due to the availability of the stand-in mel variablecatch ( (rightkey || leftkey)<TIMEDOUT> )
key
:
Thecatch ( key<TIMEDOUT> )
ConsumerLH
can also use the more restrictive AND combination handler:
Due to the all-or-nothing nature of the simultaneous method, both the OR or AND combination handlers will properly catch the TIMEDOUT exception.catch ( (rightkey && leftkey)<TIMEDOUT> )
- Available values still time out.
A peculiarity of the simultaneous mel AND read is that a mel that satisfies the mel READ conditions of a task, can still cause that task to time out. This is because the mel value is read by the implemented AND read algorithm, and this algorithm has decided to not give that value to the task due the potential of a deadlock. The algorithm may be waiting for other mel values to be available and give all of them to the task in one shot.
If a timed-out task redoes the simultaneous mel AND read, either via a resumption or an iteration, it may have the chance to get the mel value that is not granted to it the first time. If the value is still available, the algorithm may now find the conditions good enough for the task to receive that value and the rest.
- Resumption is for all mel queues.
With the cumulative mel AND read, a <resume> operation from a TIMEDOUT exception handler puts the task back into the queues of mel elements that the task has not accumulated the values yet. For the simultaneous mel AND read, its all-or-none nature has the task being put back into all the queues of requested mels.
When the task is put back into a queue, it begins the wait at the bottom of the queue (i.e. the starting position). It has to move up to the top position of the queue, to satisfy one of the conditions of the mel READ conditions, before it can be considered by the implemented AND read algorithm. If there are other tasks in the queue waiting on other mel reads, the task will have to wait for its due time.
- Available mel values may not be read
With the cumulative mel AND read, once a value shows up on the mel buffer, it will likely be read by one the requesting tasks that has met the mel READ conditions. For the simultaneous mel AND read, the implemented AND read algorithm will take over the reading. It may read out the value into some internal holding area. This will allow other tasks in the mel queue to get to subsequent values in the mel buffer. If the algorithm does not support a holding area, it may leave the task at the top of the queue to hold up the mel buffer value. In this case, any queued tasks have to wait for the algorithm to resolve the no-deadlock AND wait problem before they can access the mel buffer.
simul
, things get more complicated at the runtime level. The implemented AND read algorithm may require additional resources, and in general will cause mel waits to be lengthened. The simultaneous mel AND read should not be used unless warranted.
Simultaneous AND Read Closeouts
While the simultaneous AND wait is going on, one of the waited-on mel elements may get closed by some other task. Like for the closeouts for cumulative AND waits, the recommended handler for the <CLOSED> exception is the OR combination:
catch ( (rightkey || leftkey)<CLOSED> )
To prevent livelocks, the handler should not get off with a resumption or iteration attempt, since any subsequent attempt to do a simultaneous mel AND read that includes a mel that has been closed, will result in another <CLOSED> exception.
While there are many similarities between a cumulative and simultaneous mel AND read, there are many differences between them too.
- No accumulated values
-
A task doing a cumulative mel AND wait, may be able to read out some values for its mel variables from the mel request list before it gets a <CLOSED> exception handler. If it does a simultaneous mel AND read, the all-or-nothing nature of the read insures that the task will have none of its mel variables valued. Even if there are values to be had, the implemented AND read algorithm still holds them back.
- Lost accumulated values
-
The implemented AND read algorithm holds back the generated mel values until it can give to a task all the values it requests in the mel AND list. If a mel gets closed, the task will abort the mel AND read with the <CLOSED> exception, getting none of the accumulated values. What happens to these accumulated values then? They are simply available to other tasks that also do simultaneous AND read. If the implemented AND read algorithm cannot find any task to give the accumulated values, they will be discarded at the end of the NERWous C program.
When the <CLOSED> exception happens to a task doing a cumulative mel AND read, it gets granted the accumulated mel values, and in the <CLOSED> exception handler, it can decide to discard these values or make some remedial use. With the accumulated values being held by the implemented simultaneous AND read algorithm, there is no such option.
Asynchronous Mel AND Reads
We have seen that the asynchronous read mode is applicable to mel OR reads. It is also applicable to mel AND reads, for both the cumulative and simultaneous methods. Let's take a look at this
Consumer
example:
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
<? mode=async>(store1 && store2);
doSomething ();
ConsumeTwo (<?>store1, <?>store2);
}
}
The Consumer
above needs the values of the mel elements store1
and store2
for its ConsumeTwo
chore. Instead of immediately waiting for them, it outsources the AND mel wait to the CHAOS runtime by issuing an early asynchronous mel AND read request:
<? mode=async>(store1 && store2);
While CHAOS stands in line in the mel queues, Consumer
uses that time to doSomething
. Once done, it issues the mel read requests on the mel elements. Although the mel store1
is processed first in this statement
ConsumeTwo (<?>store1, <?>store2);
the mel AND read will not complete until all the listed mel values are available. In other words, due to the default cumulative method used, CHAOS can accumulate the value of store1
, but it will not give it to the Consumer
task until the value of store2
is also accumulated.
As in the asynchronous read of single mels, the
<?>store1
and <?>store2
requests mean that the Consumer
task will use the <value> properties of the local mel variables if CHAOS has successfully carried out the mel AND wait; otherwise Consumer
will relieve CHAOS and take over the wait.
Side note: As a reminder, the asynchronous mel AND wait is most useful if there are many readers vying for the same mel elements. This allows a task to put itself in the queue as early as possible to "stake its position". If the task is the only consumer of the mel resources, there is no need for a "pay someone to stand in line for me" scheme. In our Producer/Consumer example, since Consumer is the only reader task,Consumer
can actually be written as simply as follows:We are making things more complicated than necessary here for the sake of discussion.void Consumer (<mel> int store1, <mel> int store2) { while ( 1 ) { doSomething (); ConsumeTwo (<?>store1, <?>store2); } }
The default AND read method is the cumulative method. If we want the
Consumer
task to use the simultaneous method, we have to specify it:
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
<? mode=async method=simul>(store1 && store2);
doSomething ();
ConsumeTwo (<?>store1, <?>store2);
}
}
We can omit the attribute keywords, mode
and method
:
<? async simul>(store1 && store2);
Error Handling For Asynchronous Mel AND Reads
Let's modify
Consumer
to add some error handling:
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
<? async timeout as=store>(store1 && store2);
while ( store<error> == NERW_ERROR_PENDING )
doSomething ();
switch ( store<error> ) {
case NERW_ERROR_NONE: /* success */
ConsumeTwo (store1<value>, store2<value>);
continue; /* while(1) loop */
case NERW_ERROR_TIMEDOUT:
printf ("[%s] has timed out - TAKE OVER READ\n", store<name>);
ConsumeTwo (<?>(store1 && store2)); /* take over the wait */
continue; /* while(1) loop */
case NERW_ERROR_CLOSED:
printf ("[%s] has closed - STOP READ\n", store<name>);
/* fall through the default case */
default:
printf ("[%s] fails on [%d] due to [%s] - STOP READ\n",
store<name>, store<error>, store<why> );
if ( store1<error> == NERW_ERROR_NONE )
Consume (store1<value>);
if ( store2<error> == NERW_ERROR_NONE )
Consume (store2<value>);
break; /* out of while(1) loop */
}
}
}
The above Consumer
task first requests an asynchronous mel AND read with timeout. While the CHAOS runtime does the waiting, the task runs the doSomething
chore. It keeps doing the doSomething
until it detects that CHAOS has ended the mel AND read. The ending may be a success or failure. If it is a success, the task runs ConsumeTwo
to process the read values of the mel stores, and iterates the while
loop to repeat the same process. If the asynchronous mel AND read times out, the Consumer
task, not having anything better to do, takes over the mel AND read from CHAOS. Once it gets the mel values, Consumer
runs ConsumerTwo
, then iterates the while
loop to repeat the process. For other kinds of failures, the Consumer
task gets off the while
loop. But before doing that, since the mel AND read is a cumulative one, one of the two mel reads may have been successful. Instead of forfeiting this mel value, Consumer
salvages it with the single-parameter Consume
function.
The
Consumer
task achieves the above by using the <error> mel variable. First, it sets the store
mel variable as the stand-in for the mel AND read of store1
and store2
. The invoked asynchronous mel AND read sets the <error> property of both mel variables to NERW_ERROR_PENDING
. While the <error> variables have this value, CHAOS is still running the mel AND read on behalf of Consumer
. This allows Consumer
to run doSomething
. The check in the while
loop:
store<error> == NERW_ERROR_PENDING
uses the stand-in variable store
as a short-cut way for:
store1<error> == NERW_ERROR_PENDING || store2<error> == NERW_ERROR_PENDING
Since the mel AND read is invoked with the default cumulative method, the variables can have their <error> values changed at separate times. When both values have been changed, the Consumer
task knows that CHAOS is done with the mel AND read. To know if this read is successful or not, Consumer
again makes use of the <error> property. The construct:
switch ( store<error> ) {
case NERW_ERROR_NONE: ...
case NERW_ERROR_TIMEDOUT: ...
case NERW_ERROR_CLOSED: ...
default: ...
}
is a short-cut for:
int _err1 = store1<error>, _err2 = store2<error>;
if ( _err1 == NERW_ERROR_NONE && _err2 == NERW_ERROR_NONE ) { ... }
else if ( _err1 == NERW_ERROR_TIMEDOUT || _err2 == NERW_ERROR_TIMEDOUT ) { ... }
else if ( _err1 == NERW_ERROR_CLOSED || _err2 == NERW_ERROR_CLOSED ) { ... }
else { ... }
For a successful read, both <error> values must be NERW_ERROR_NONE. For a failed read, only one <error> variable needs to have a failed error code. Using the stand-in store
mel variable ensures that the AND (&&) and OR (||) operators are applied correctly, besides resulting in a more concise code.
For a successful read, the local <value> properties contain the read values, and
Consumer
uses them directly for the ConsumeTwo
chore. For generality, Consumer
can also use the mel AND wait construct:
ConsumeTwo (<?>(store1 && store2));
The above wait construct first checks the local mel <value> properties. If fresh values are found there, the wait construct will return them immediately, instead of going out to the remote mels and doing mel waits. In the case above, we are sure to find fresh values, so it is more efficient to use the <value> properties directly.
For the timeout failure, if the method used for mel AND read were the simultaneous method, its all-or-nothing would have both mel variables timed out. Since the cumulative method is in use here, one mel variable may already be valued (with its <error> property set to NERW_ERROR_NONE), and the other mel variable timed out. The task
Consumer
knows which mel variable whose timeout causes the NERW_TIMEDOUT_ERROR case, by checking the <name> property of the stand-in variable store
.
Once a timeout has been detected,
Consumer
chooses to take over the wait from CHAOS. It issues a mel AND wait, and will be willing to wait until both of the requested mels are valued. Note that if one mel variable is already valued (by the CHAOS runtime from the asynchronous mel AND read), the new mel AND read will wait only on the other mel.
Side note: An astute programmer will notice that there is no error checking for this second mel AND read. This statement:For the CLOSED and other error conditions, theis a concise way to write:ConsumeTwo (<?>(store1 && store2));
What happens if the <error> properties are not NERW_ERROR_NONE? If this is the case, the mel AND read has failed, the<?>(store1 && store2); if ( store1<error> == NERW_ERROR_NONE && store2<error> == NERW_ERROR_NONE ) ConsumeTwo (store1<value>, store2<value>);
ConsumeTwo
function will not be invoked. TheConsumer
task gets off thecase NERW_ERROR_TIMEDOUT
with acontinue
to iterate the loop. The asynchronous mel AND read is requested again; and this time, any mel deficiency discovered from the previous iteration, will be handled by this iteration'sswitch/case
statements.
Consumer
task breaks off the while
loop. Before doing that, the task tries to salvage any values that have been successfully read during the asynchronous attempt. Those values are in the <value> properties of the mel variables whose <error> properties have been changed from NERW_ERROR_PENDING to NERW_ERROR_NONE.
Side note: This salvage work would not make sense if the AND read method were simultaneous. With its all-or-none nature, there would not be any successful partial reads on failure.
Exception Handling For Asynchronous Mel AND Reads
An alternative to using the <error> property for checking for mel AND read results is to work with exceptions. Let's modify the
Consumer
code again:
void Consumer (<mel> int store1, <mel> int store2) {
while ( 1 ) {
try {
while ( <? async simul timeout as=store>(store1 && store2) )
doSomething ();
ConsumeTwo (<? tag=TAKEOVER as=store>(store1 && store2));
}
catch ( store<TIMEDOUT> ) {
printf ("[%s] has timed out - TAKEOVER READ\n", store<name>);
<resume tag=TAKEOVER>; /* take over the wait */
}
catch ( store<...> ) {
if ( store<error> == NERW_ERROR_CLOSED )
printf ("[%s] has closed - STOP READ\n", store<name>);
else
printf ("[%s] fails on [%d] due to [%s ] - STOP READ\n",
store<name>, store<error>, store<why>);
break; /* stop on first failure */
}
}
This time, we use the simultaneous method with the keyword simul
. The statement:
while ( <? async simul timeout as=store>(store1 && store2) ) doSomething;
is a concise way to write:
<? async simul timeout as=store>(store1 && store2);
while ( store<error> == NERW_ERROR_PENDING ) doSomething;
The statement
ConsumeTwo (<? tag=TAKEOVER as=store>(store1 && store2));
can be entered two ways. The first way is when the asynchronous mel AND read is successful, and the arguments that ConsumeTwo
takes in are in fact store1<value>
and store2<value>
. There is no waiting involved even though the mel wait ?
is present.
The second way that the
ConsumeTwo
statement is entered is via the <resume tag=TAKEOVER>
statement invoked inside the store<TIMEDOUT>
exception handler. The Consumer
task now wants to take over the mel AND read from the CHAOS runtime. By default, the <resume> operation picks up on the last mel operation, which is the asynchronous mel read statement:
<? async simul timeout as=store>(store1 && store2)
which generates the timeout exception. By specifying the tag
attribute in <resume>, the Consumer
task requests the mel AND wait to be resumed at the tag
attribute. (Without using tag
, the <resume> operation would redo the asynchronous mel AND wait.)
In the catch-all
store<...>
handler, Consumer
makes the wise decision to break out of the infinite while
loop since future iterations will likely fail on the AND mel read. Before the breakout, Consumer
can use the stand-in store
variable to display information about the failure. It is not in the code above, but if Consumer
wants to do something special with store1
, it can via the if
statement:
if ( store<name> == "store1" ) { ... }
Previous Next Top