Pages

Saturday, March 5, 2016

Mel Resumptions

Welcome » NERWous C » Mel
  1. Resume Operation
  2. Resume With Attribute
  3. Resume In Exceptions
  4. Resume, Break and Continue
  5. 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

No comments:

Post a Comment