Pages

Thursday, March 24, 2016

Mel Location

Welcome » NERWous C » Mel
  1. At Attribute
  2. At Values
  3. DOWN Exception
  4. Examples


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
  1. Abort Operation
  2. Abort Value
  3. Abort and Close
  4. Abort Multi-Value


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
  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

Friday, March 4, 2016

Mel Basic

Welcome » NERWous C » Mel
  1. A Serial Program
  2. A Concurrent Program
  3. Mel Elements
  4. Mel Variables
  5. Mel Creation
  6. Mel Passing
  7. Mel Scope
  8. Mel Life
  9. Mel Queues
  10. Mel Operations
  11. Mel Properties


A Serial Program

Let's start with a Producer / Consumer example written serially in standard C language:
main () {
     int c;
     while (c = Produce())
           Consume(c);
}
The main task works serially. It calls Produce to get a value to c, then calls Consume to consume it. The cycle repeats until there is no more products (with Produce returning 0).


A Concurrent Program

Let's make the above program run in concurrency by using NERWous C. Its constructs are highlighted below:
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     while ( <?>store = Produce() );
}
void Consumer (<mel> int store) {
     while ( int c = <?>store )
         Consume(c);
}
The main task creates a mel variable, store, as the communication channel between the Producer and Consumer. Then it creates a task to run the Producer code via the pel construct <!>, and another task to run the Consumer code. The main task then ends itself.

The Producer task keeps producing until Produce returns 0. Every time it has a product, it will see if the mel store is available for it to deposit the product. By default, the mel store has one slot. If a slower Consumer task has not consumed the previous product in that slot, the Producer will wait at the mel store via the <?> wait operator. Eventually the previous product will be Consume'd, the mel store will be emptied, and the CHAOS runtime will allow the Producer to access the mel store. Once it has successfully deposited its new product into the mel store, the Producer resumes its Produce chore. When it cannot Produce any more, it will deposit a 0 value to the mel store to inform the Consumer task. The Producer task then breaks out of the while loop and ends itself.

The Consumer task runs in parallel with the Producer task. The two synchronize via the mel store. If store is empty (due to a slower Producer) or is locked by the Producer while it deposits a new product, the Consumer task will wait at the <?> wait operator. Once it can access the mel store and has detected that the mel store has a value, it will empty the mel store and moves the value to the local variable c. If c is not 0, it will be Consumed. Otherwise, the Consumer task breaks out of the while loop and ends itself.

When all the tasks in the program (main, Producer and Consumer) have ended, the program itself exits.


Mel Elements

A mel element (or just mel) is the "shared data". The NERWous C mel element is introduced by the mel construct <mel>. It is the NERWous C implementation of the Memory Element of the NERW Concurrency Model. The mel element resides somewhere on the model nerw network, hosted in a model computer element (cel). We can imagine the cel as maintaining a pool of shared data, and requesting tasks asking for a piece of this shared data.

A mel is created by default with a 1-slot buffer. It can also be created with additional buffer to optimize throughput. When a mel is requested for reading or writing, the request can be prioritized to minimize latency.

Any task that requests access to the mel must first establish a connection to that element and subsequently maintain a session with it. This session is represented by the mel variable. A NERWous C program has multiple tasks accessing the same shared mel element. Each of these tasks maintain their own resident mel variable. Let's get to know more about mel variables next.


Mel Variables

A mel element is requested via the mel creation statement:
<mel> int store;
Once a mel element has been created, it is represented by a mel variable. The mel variable by itself does not contain the actual shared data. The shared data remains at the remote mel element. What the mel variable contains is the information on how to access the mel element to get hold of the shared data. This information is maintained in the CORE properties within the mel variable.

The other part of the mel variable is the information about the specific request that the task makes against the mel element. The task provides the IN properties of the mel variable to make a request. The task gets the result and status data back via the OUT properties. The mel element also piggy-backs with the OUT data some useful configuration information for the task to cache in the SET properties.

Each task maintains its own resident version of the mel variable. If there are three tasks that access the same mel element such as in the Producer/Consumer example above (i.e., the main task, the Producer task, and the Consumer task), then there are three separate mel variables with the same name (i.e. store). Each mel variable resides in the context of its specific task. They have the same values for the CORE properties, usually the same values for the SET properties, and most likely different values for the IN and OUT properties due to their different interactions with the mel element.


Mel Creation

The main task creates the mel variable, store, via the NERWous C statement:
<mel> int store;
While the above statement looks like a compile-time declaration, it is actually a run-time operation that invokes the underlying CHAOS runtime to secure the resources necessary for accessing the remote shared data. A mel creation statement can contain attributes to let the programmer specify how and where the mel element will be created.

Since it is a run-time operation, the mel creation can fail. In this case, the mel variable <error> property can be used for defensive coding:
main () {
    <mel> int store;
    if ( store<error> ) {
        printf ("Creation of [store] failed on [%s]", store<why>);
        exit 411;
    }
    <!> Producer (store);
    <!> Consumer (store);
}
The reason for a mel creation failure can be found in the <why> property.

Another way to check for failure is to catch a mel operation exception:
main () {
  try {
    <mel> int store;
  }
  catch ( store<...> ) {
    printf ("Creation of [store] failed on [%s]", store<why>);
    exit 411;
  }
    <!> Producer (store);
    <!> Consumer (store);
}


Mel Passing

Like any C language variable, a mel variable can be passed to functions as arguments. One thing special about mel variables is that the way they are passed depends on the destination they are passed to. If they are passed to another task, they are passed by value. If they are passed to a function within the same task, they are passed by reference.

Unlike a normal C variable where passing by reference is an option requiring the use of a pointer, there is no such thing as a pointer to a mel variable. NERWous C does not allow a programmer to choose between passing a mel variable by value or by reference. It always enforces its passing rule: if a mel variable is passed to another task, it is always passed by value; and if passed within a task, it is always passed by reference.

This example shows the passing of mel variables as arguments:
main () {
     <mel> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     if ( store<error> & NERW_ERROR_FAILED ) {
          printf ("Mel [store] invalid due to [%s]", store<why>);
          return;
     }
     while ( <?>store = Produce() );
}
void Consumer (<mel> int store) {
     if ( store<error> & NERW_ERROR_FAILED  ) {
          printf ("Mel [store] invalid due to [%s]", store<why>);
          return;
     }
     while ( 1 ) {
         DoConsume (store);
         printf ("Status in Consumer: %d", store<status>);
         if ( store<value> == 0 ) break;
     }
}
function DoConsume (<mel> int store) {
     Consume (<?>store);
     printf ("Status in DoConsume: %d", store<status>);
}
There are three different mel variables of the same name in the code above. The task main creates the first mel variable store to request the CHAOS runtime to set up the store mel element for sharing, and to maintain its own session with that shared data. It then passes that mel variable by value to the tasks Producer and Consumer. These tasks duplicate the property values of the parent mel variable into their own resident mel variables. As the processing goes on, some property values of the mel variable in Producer will diverge from those of the mel variable maintained by Consumer and the main task, while still sharing some common properties like the location of the mel element.

Within the Consumer task, the DoConsume serial function runs in the context of the task Consumer. It takes the mel variable store as argument by reference. Thus DoConsume is using the same mel variable as set up by Consumer, and whatever changes DoConsume makes on the mel variable store will be reflected back to the Consumer code. For example, DoConsume reads the value of the mel element and caches the <value> and >status< properties in the mel variable <store>. These properties are then used by the parent function Consumer appropriately.


Mel Scope

NERWous C mel variables follow the same scope rules of C variables. They are either global or local, as seen in the following example:
<mel> int GlobalStore;    /* global variable */
main () {
     <mel> int localStore;     /* local variable */
     <!> Producer (localStore);
     <!> Consumer ();
}
void Producer (<mel> int store) {
    <extern> GlobalStore;
    <?>GlobalStore = Produce();     /* OK - GlobalStore is global */
    <?>store = Produce();           /* OK - store is localStore passed by value */
    <?>localStore = Produce();      /* ERROR - localStore is not in scope */
}
void Consumer () {
    Consume (<?>GlobalStore);       /* ERROR - GlobalStore is not declared */
    Consume (<?>store);             /* ERRROR - store is not defined */
    Consume (<?>localStore);        /* ERROR - localStore is not in scope */
}
Any scope error in the above example is flagged during compile time. The NERWous C programmer has to fix all the scope related errors before the program can be compiled for execution.

The mel variable GlobalStore has a global scope. It is first created in the context of the main task. The Producer task re-creates it with the <extern> statement as its own resident mel variable. The NERWous C program now has two separate global mel variables, both pointing to the same GlobalStore mel element: one in the context of the main task and the other, in the context of the Producer task.

The Consumer task omits the <extern> operation before making use of the GlobalStore. This results in a compile-time error. We will learn more about the <extern> statement later: it is both a global scope facilitator during compile time and a global variable duplicator during run time.


Mel Life

The life of a mel element and the life of a mel variable are different. Let's consider the following example:
main () {
    <mel> int store;
    <!> Producer (store);
    <!> Consumer (store);
}
void Producer (<mel> int store) {
    while ( <?>store = Produce() );
    <close> store;
    printf ("Status of mel store [%d]\n", store<status>);  /* OK */
}
void Consumer (<mel> int store) {
  try {
    while ( 1 ) Consume(<?>store);
  } catch ( store<CLOSED> ) {
    store = null;
    printf ("Status of mel store [%d]\n", store<status>);  /* ERROR */
  }
}
The life of a mel element is from the time it is created until it is closed. In the above example, the mel element store comes into life at the mel creation statement as initiated by the main task
<mel> int store;
This statement requests the CHAOS runtime to allocate shared data for the int storage of the mel store, and to open the connections on the cel that hosts that mel element to all tasks that request access (main, Producer, Consumer).

When the mel element is closed, the CHAOS runtime recollects the allocated shared resource and closes the open connections. A mel element is closed implicitly when the NERWous C program exits, or explicitly when the <close> operation is invoked on the mel variable. Any task that has access to the mel element via its resident mel variable can invoke the <close> operation, like the Producer task above. Afterwards, any task that attempts to read or write to the closed mel element will encounter the CLOSED exception, as happens to the Consumer task above.

While there is only one remote mel element store, there are three mel variables store, resident in each of the task. Like all C variables, the life of the mel variables is dictated by their scope (local or global). For the mel variable store in the main task, it is between the mel creation statement and the end of the task. For the Producer task, its mel variable store is active from the task starts until the task ends. After Produce'ing a zero value, it writes that value to the remote mel element, gets off the while loop and closes the mel element. Even though CHAOS is removing the resources and accesses to the mel element store, the Producer still has full control of its resident mel variable. It can access the mel variable's properties at will from its own local context, such as the <status> property for the printf statement.

When the Consumer task detects that the remote mel store has been closed, it jumps into the exception handler, and explicitly deactivates its mel variable store by setting it to null. This frees any resource in the context of the Consumer task that has been allocated to the resident mel variable store. When Consumer tries to printf the property <status>, this property does not exist any more, and the Consumer task will end abnormally via a run-time error.
On a side note
Is the 0 value which is the last value Produce'd by Producer get Consume'd by Consumer? If Consumer gets hold to this value before the <close> statement is issued and processed, then yes, it does. If Consumer has been busy running the Consume on the previous-to-last value, the <close> statement from Producer has the chance to be fully processed by the CHAOS runtime. When Consumer goes back to the mel element store for the next value, it will be served by the <CLOSED> exception instead of getting the 0 value. If the logic of the program requires the 0 value to be Consume'd, then it should be the responsibility of the Consumer task to <close> the mel store.
In summary, the scope of a mel variable dictates its access during compile time, while the life of a mel variable dictates its access during run time.


Mel Queues

The three basic operations on a mel element is creation, reading and writing:
    <mel>int store;
    int c = <?>store;
    <?>store = c;
In the first statement above, the mel element represented by the mel variable store is created. As mentioned in the Mel Creation section, this declarative-looking statement is actually a run-time operation. The CHAOS runtime is instructed to collect all the necessary resources to allow tasks to access the shared data. The main resources are two queues: the reader's queue for read access, and the writer's queue for write access. NERWous C does not define where those queues are created, or even if these queues are physical. NERWous C programmers have to trust CHAOS to set up these shared queues properly, or to return a failure condition that allows them to check for creation errors.

In the second statement above, the task requests a READ access:
  1. The reader task comes to the mel element and stands in line at the default readers' queue.
  2. Whenever the task reaches the top of the default readers' queue, it waits for the queue's turn to read off the mel element. This is because there can be multiple queues with different reading priorities.
  3. The task then examines the mel element from the reading side. If the mel is buffered, reading is done on one side of the buffer and writing is done at the other side. By default, a mel element is unbuffered, or looking from a different angle, is buffered with one slot.
  4. If the mel element does not contain any value on the reading side of the mel buffer, it is said to be empty. The task keeps waiting at the top of the default readers' queue, until the mel element receives a value from a writer task.
  5. If the mel element contains a value on the reading side of the mel buffer, it is said to be filled. When this happens, the task removes the value from the mel element, and gets off the readers' queue with the retrieved value.
  6. The task then stores the retrieved value in the <value> property of the associated mel variable.
  7. The task resumes its program execution past the mel read statement.
In the third statement above, the calling task requests a WRITE access:
  1. The writer task comes to the mel element and stands in line at the default writers' queue.
  2. Whenever the task reaches the top of the default writers' queue, it waits for the queue's turn to write to the mel element. This is because there can be multiple queues with different writing priorities.
  3. The task then looks at the mel element from the writing side of the mel buffer. If the mel is not buffered, there is only one slot to write to.
  4. If there is no available slot in the mel element to write another value, the mel element is said to be full. The task keeps waiting at the top of the default writers' queue, until a reader task reads out the mel element, making a slot available for the writer task.
  5. If there is an available slot to write another value, the mel element is said to be vacant. The task deposits its value into the available slot, and gets off the writers' queue.
  6. The task resumes its program execution past the mel write statement.
A mel element has several distinct queues.

For reading:
Readers' QueuesSynopsis
Default queue Tasks with default priority wait in this queue for the mel element to be filled.
Priority queues Tasks with higher priorities wait in these queues instead of the default readers' queue.
Read-only queueTasks wait in this queue for read-only requests.

For writing:
Writers' QueuesSynopsis
Default queue Tasks with default priority wait in this queue for the mel element to be vacant.
Priority queues Tasks with higher priorities wait in these queues instead of the default writers' queue.
Write-Over queueTasks wait in this queue for writeoever requests.

The default readers' queue and writers' queue are created by default with the mel creation operation. The other queues are created on demand. All queue creations are done by the CHAOS runtime. Not all CHAOS implementations support all the queue creations; however all of them are required to support at least the creation of the default queues, either physically or virtually.


Mel Operations

A mel operation is a request that a task sends to the CHAOS runtime for some action on a mel element. In NERWous C, an operation is specified in lower case within the < > markers, and placed before a mel variable. An operation can contain attributes to customize the request. The attributes are specified between the < and > markers.

These are the mel operations and their attributes, using the mel variable store as a stand-in:
OperationAttributesSynopsis
<mel> int store Mel creation operation
buffer
=n
Create a buffered mel with n slots
at
=cel
Create the mel at the specific cel location
mode
=setting
Create the mel with this mode setting:
readwrite: this default mode allows the mel to be read and written to.
readonly: the mel access is always readonly.
<?>store Mel read operation
priority
=n
Access the mel for reading with priority n
timeout
=msec
Wait for the mel to be read for msec milliseconds before aborting
as
=var
Used to represent the mel variable selected in an exclusive reader OR zone
mode
=setting
Request read access with this mode setting:
reader: request a read access (default)
readonly: request read-only access
<?>store = Mel write operation
priority
=n
Access the mel for writing with priority n
timeout
=msec
Wait for the mel to be written for msec milliseconds before aborting
mode
=setting
Request write access with this mode setting:
writer: request write access (default)
writeover: request overwrite access
<close>storeMel closure request
<resume>Mel wait resumption
<quit>storeMel unsubscription request
<rebuffer>storeMel buffer resize request
<snapshot>storeMel properties refresh

Since a mel operation is a run time activity, it can fail. In NERWous C, such failure can be detected by the programmer checking the <error> property of the mel variable immediately after the mel operation, and investigating the <why> property for the long-text description. Another way is to request an exception to be raised on mel operation failures. The NERWous C language borrows the C++ try/catch construct to support exceptions. A mel exception name is specified within the < > markers, and placed after a mel variable. Unlike mel properties which are lower cased, mel exceptions are upper cased.


Mel Properties

A property of a mel variable is a local value that a task caches from its most recent interaction with a mel element. In NERWous C, a property is specified in lower case within the < > markers, and placed after a mel variable.

The value of a mel property is specific to a task. Two tasks that access the same mel element will have two different mel variables to represent their own access to the mel. These mel variables may have different values for the mel properties depending on the circumstance the tasks access the mel element. For example, the <value> property, which represents the value of a mel upon a successful mel read operation, will likely be different for each reader task since each task reads a different value (unless the producer task produces the same value over and over).

Accessing a mel variable by itself, such as c = <?>store or <?>sotre = c, is a mel operation, and thus requires the mel wait operator (<?>). On the other hand, a mel property is like any C language variable, it resides in the context of the task, and accessing one does not require mel waiting.

Let's take the mel variable store as the stand-in. These are its properties:

IN properties
are cached values of the attributes of the last mel operation that the task requests against the mel element:
PropertySynopsis
store<priority>Priority value used by the last mel operation
store<timeout>Timeout value used by the last mel operation
store<name>Scoped name of the mel variable set during creation.
store<agent>Entity that executes a requested mel operation

OUT properties
are the results received back from the last mel operation:
PropertySynopsis
store<value>Value returned by the mel read operation. This value can be a simple entity (like an int), an array or a structured entity
store<count>Number of items in an array value
store<sequence>Version identifier of the value returned from a mel read operation
store<status>Status of the mel element after a mel operation
store<condition>Status of the mel element before a mel operation
store<error>Error code of the last mel operation. A value of 0 means success and non-0 values mean failures.
store<why>Reason of a non-zero <error> value

SET properties
are cached values of the mel element's configuration information. Any change in the mel configuration will be propagated to a mel variable by piggy-backing to the last mel operation return:
PropertySynopsis
store<buffer>Number of buffer slots created for the mel element
store<location>Cel location of the mel element
store<readers>Number of reader tasks subscribing to the mel element
store<writers>Number of writer tasks subscribing the mel element

If the remote mel configuration information has been changed but the task has not made any mel operation request, the SET properties in its mel variable keep the old values and do not reflect the new changes. They are deemed stale.

CORE properties
are system values assigned by the CHAOS runtime to allow a task to reach out to a mel element. These values cannot be changed once a mel element has been created.
PropertySynopsis
store<id>Mel element numerical identification
store<url>Mel element string identifier

Unlike the other properties that may be different between mel variables, the CORE properties are the same for all mel variables that represent the session between the tasks and the mel element.

A mel variable is basically a collection of properties. The properties that are visible to a NERWous C programmer are listed above. A NERWous C implementation can have additional custom properties that are particular to that implementation and hidden from the application-level programming.


Previous Next Top

Pel in NERWous C

Welcome » NERWous C

The processing element (or pel) abstracts the execution concept of the NERW Concurrency Model. To achieve concurrency, a program must be broken up into execution units that can run in parallel. These execution units require the following:
  1. Creation: the execution units are created on a computer element that can execute the code.
  2. Initialization: contextual data is passed from the creating execution unit to the created execution units so that the latter can perform on behalf of the former.
  3. Synchronization: the created execution units work together by accessing shared resources.
  4. Termination: the created execution units pass their final results back to the creating execution unit and terminate.
  5. Exception: if execution fails, the execution unit reports back the exception.

The following sections describe how NERWous C supports the pel element:
  1. Pel Basic:
    1. Pel Construct
    2. Pel Code Blocks
    3. Pel Life Cycle
    4. Pel Variables
    5. Synchronous Task Creation
    6. Pel Operations
    7. Pel Properties
    8. Pel Exceptions
  2. Local and Global Variables:
    1. Local Variables
    2. Global Variables
    3. Import Attribute
    4. Import-File Attribute
    5. Array Variables
    6. Mel Variable Properties
  3. Pel Closure:
    1. Ways To Close
    2. Terminate Statement
    3. Pel TERMED Exception
    4. Pel Closed Status
    5. Pel CLOSED Exception
    6. Pel Closure Detection
    7. Kill Statement
    8. Pel Abortion
    9. Exit Statement
  4. Pel Returns:
    1. Return Statement
    2. Return For Inline Code
    3. Release Statement
    4. Multi-Value Release
    5. Multi-Value Return
  5. Pel Loops:
    1. Pel A Loop
    2. Loop Of Pels
    3. Synchronous Loop Of Pels
    4. Pel A Loop Of Pels
  6. Pel Location:



Previous Next Top

Mel in NERWous C

Welcome » NERWous C

The memory element (or mel) abstracts the shared information concept of the NERW Concurrency Model. When an execution task runs, it produces or consumes data (or does both). In a concurrent environment, this data is shared with other execution tasks.

The following sections describe how NERWous C handles the sharing of data:
  1. Mel Basic:
    1. A Serial Program
    2. A Concurrent Program
    3. Mel Elements
    4. Mel Variables
    5. Mel Creation
    6. Mel Passing
    7. Mel Scope
    8. Mel Life
    9. Mel Queues
    10. Mel Operations
    11. Mel Properties
  2. Mel Properties:
    1. Mel Properties Cache
    2. Mel Properties Categories
    3. Mel Properties Constants
    4. Mel ID Property
    5. Mel URL Property
    6. Mel Value Property
    7. Mel Value Auto Operators
    8. Mel Status Property
    9. Mel Condition Property
    10. Mel Name Property
  3. Mel Operations:
    1. Mel Operations List
    2. Mel Creation Operation
    3. Mel Passing Operation
    4. Mel Operations Error Codes
    5. Mel Operations Error Descriptions
    6. Mel Operations Exceptions
    7. Mel Snapshot Operation
  4. Mel Global Variables:
    1. Global Variable Scope
    2. Global Variable Usage
    3. Global Variable Creation
    4. Global Variable Extern Statement
    5. Global Variable Closure
  5. Mel Buffers:
    1. Mel with a Buffer
    2. Mel Buffer States
    3. Mel Buffer Access Modes
    4. Mel Buffer Property
    5. Mel Rebuffer Operation
  6. Mel Closures:
    1. Closing A Mel
    2. Multiple Mel Closures
    3. Closing At Task End
  7. Mel Timeouts:
    1. Mel Waits with Timeouts
    2. Run-Time Timeouts
    3. Mel Default Timeout
    4. Mel Timeout Settings
    5. Mel Timeout Special Values
    6. Mel Timeout Property
  8. Mel Reads:
    1. Mel Read Attributes
    2. Mel Reader Mode
    3. Mel Read Waits
    4. Mel Properties On Reads
    5. Agent Mel Property
    6. Mel Asynchronous Reads
  9. Mel Combined Reads:
    1. Mel Multiple Reads
    2. Mel OR Reads
    3. OR Read Exceptions
    4. Asynchronous Mel OR Reads
    5. Mel AND Reads
    6. Cumulative AND Reads
    7. Simultaneous AND Reads
    8. Asynchronous Mel AND Reads
  10. Mel Writes:
    1. Mel Writer Mode
    2. Mel Write Waits
    3. Mel OR Writes
    4. Mel AND Writes
    5. Mel LIST Writes
    6. Mel Writeover Mode
  11. Mel Priorities:
    1. Mel Access Priority
    2. Mel Priority Queues
    3. Mel Default Priority
    4. Mel Priority Settings
    5. Mel Priority Property
  12. Mel Resumptions:
    1. Resume Operation
    2. Resume With Attribute
    3. Resume In Exceptions
    4. Resume, Break and Continue
    5. Resume In Exclusive Zones
  13. Mel Subscriptions:
    1. Writers Subscriptions
    2. Quit vs. Close
    3. Readers Subscriptions
    4. Dual Subscriptions
    5. Quit Attributes
  14. Mel Readonly Access:
    1. Readonly Statement
    2. Readonly Abbreviation
    3. Readonly Queue
    4. Sequence Property
    5. Readonly On Read
    6. Readonly Mels
    7. Readonly Access To Exclusive Zones
  15. Mel Reader Zones:
    1. Reader Zones
    2. Checkout Operation
    3. Eponymous Variables
    4. Reader Zone Attributes
    5. Reader Zone Resumptions
  16. Compound Reader Zones:
    1. Reader OR Zones
    2. Reader LIST Zones
    3. Reader AND Zones
  17. Mel Writer Zones:
    1. Writer Zones
    2. Checkout Operation
    3. Writer OR Zones
    4. Writer LIST Zones
    5. Writer AND Zones
    6. Writer Zone Attributes
  18. Mel Mixed Zones:
    1. Mixed LIST Zones
    2. Checkout Operations
    3. Mixed AND Zones
    4. Mixed OR Zones
  19. Mel Arrays:
    1. Mel Arrays
    2. Mel Array Value Property
    3. Mel Array Closures
    4. Mel Array Status Property
    5. Mel Array Exceptions
    6. Mel Array Boundary Exceptions
    7. Mel Array Count Property
    8. Mel Array Boundary Behaviors
    9. Mel Array Consecutive Constructs
    10. Mel Array OR Wait Examples
    11. Mel Array AND Wait Examples
    12. Mel Array LIST Wait Examples
  20. Structured Mels:
    1. Mel Structures
    2. Mel Structure Buffers
    3. Mel Structures With Multiple Items
    4. Mel Structure Value Property
    5. Mel Structure Direct Itemized Access
    6. Mel Structures In Exclusive Zones
    7. Mel Structures Inside Local Structures
  21. Mel Locations:
    1. At Attribute
    2. At Values
    3. DOWN Exception



Previous Next Top

Thursday, March 3, 2016

Mel Buffers

Welcome » NERWous C » Mel
  1. Mel with a Buffer
    1. Buffer Allocation Error
    2. Buffer Size Variable
    3. Buffer Keyword Omission
    4. Mel Buffer vs. Value Property
  2. Mel Buffer States
  3. Mel Buffer Access Modes
    1. Mel Buffer Read Access
    2. Mel Buffer Write Access
  4. Mel Buffer Property
  5. Mel Rebuffer Operation
    1. Buffer Attribute


Mel with a Buffer

A mel element is used as a communication channel between multiple tasks. By default it is set up by the CHAOS runtime with one slot. When that slot is occupied, no writer task can deposit a new value, causing it to wait. In the example below, we can potentially improve the concurrency between the Producer and Consumer tasks, by buffering that communication channel with an increased number of slots. This allows a periodically faster Producer to have extra room to deposit its products and move on, instead of being blocked by a slower Consumer:
main () {
     <mel buffer=100> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     while ( <?>store = Produce() );
}
void Consumer (<mel> int store) {
     int c;
     while ( c = <?>store )
         Consume(c);
}
By using the attribute buffer with a value of 100, we ask the CHAOS runtime to set up a new mel element named store that contains that many slots. Unlike the request to create a mel element, which is a success-or-failure request, the add-on buffer request is a best-effort request. The CHAOS runtime may fulfill the full request by allocating all 100 requested slots; or it may satisfy the request partially by allocating more than one but less than 100 slots; or it totally ignores the request and just creates the mel element with the default one-slot buffer. The latter cases can happen if there are not enough resources to satisfy the request, or if the underlying runtime environment does not support the mel buffering feature at all. The best-effort approach allows the same NERWous C program to be able to run in different platforms and take advantage of better features when they are available.

When CHAOS has carried out the mel statement and the code flow is returned to the NERWous C program, the latter knows how many slots CHAOS has successfully allocated by checking the <buffer> property of the just created mel variable. If this number is not satisfactory, the program can invoke the <rebuffer> operation for another attempt to get to a newly desired buffer:
<mel buffer=100> int store;   /* ask for 100 */
if ( store<buffer> < 50 ) {  /* cannot get even 50 */
   <rebuffer buffer=50>store>  /* try to get 50 */
}
/* now accept whatever CHAOS can give */

Buffer Allocation Error

Since a mel creation statement is a run-time statement, it can fail. However no error should be attributed to a mel buffer request, due to its best-effort nature. If the CHAOS runtime cannot find the necessary resources, it will accommodate as much as it can. The fallback is to assign just one slot, which is the default behavior. If CHAOS cannot even assign that one slot, this is a failure of the whole mel creation request, and not just of the add-on buffer request.

As mentioned in the mel creation operation, a mel creation statement failure can be handled via the <error> property or via an exception. These are some examples with the buffer attribute:
/* Handling via <error> property: */
<mel buffer=100> int store1;
if ( store1<error> == NERW_ERROR_CLOSED )
   printf ("Failure creating [store1] due to %s", store1<why>);

/* Handling via an exception: */
try { < mel 100> int store2; }
catch ( store2<CLOSED> ) {
    printf ("Failure creating [store2] due to %s", store2<why>);
}
NERWous C leaves it to the underlying runtime environment to report any issue with buffering in addition to the mel creation error message in the <why> property.

Buffer Size Variable

Since the mel buffer setup happens during run time, it is possible to specify the size of a mel buffer using variables and constants:
     int mysize = 100;
     <mel buffer=mysize> int store1;

     const DEFAULTSIZE = 100;
     <mel buffer=DEFAULTSIZE> int store2;

Buffer Keyword Omission

The buffer keyword can be omitted if it is the only specified attribute of type integer:
     <mel 100> int store;

     int mysize = 100;
     <mel mysize> int store1;

     const DEFAULTSIZE = 100;
     <mel DEFAULTSIZE readonly> int store2;
The last statement stands for:
<mel buffer=DEFAULTSIZE mode=readonly> int store2;
Since the value for the buffer attribute is an integer, and the value for the mode attribute is a character keyword, they do not conflict each other, and the attribute names can be dropped for a more concise statement.

Mel Buffer vs. Value Property

In the previous Producer/Consumer example, we have three mel variables of the same name, one for each task (main, Producer and Consumer), to represent its own access to the newly created (and hopefully buffered) mel element.

The mel buffer belongs to the remote mel element, and not to the resident mel variables. When a reader task, like Consumer, reads from the mel element, it will read from that mel element's buffer and deposits the read value into the <value> property of the mel variable. The mel buffer may have one slot (by default) or many slots (via the buffer attribute or <rebuffer> operation), but there is always one and only one "slot" in the <value> property that contains the last read value.


Mel Buffer States

The mel buffer is a First-In-First-Out (FIFO) circular queue. A writer task (such as Producer in our example) puts a value at the back of the buffer, called the writer's end. A reader task (such as Consumer) gets a value at the front of the buffer, called the reader's end.

If all the slots of a mel buffer are filled, the mel element is said to be full, causing a producer that needs to deposit a new value, to wait at a writers' queue, until a slot is emptied by a consumer task. When there are one or more slots available for new deposits, the mel element is said to be vacant.

If none of the slots of a mel buffer contain a value, the mel element is said to be empty, causing a consumer that needs to read a value, to wait at a readers' queue, until a slot is filled by a producer. When there are one or more slots with value, the mel element is said to be filled.

All the four conditions of the mel buffer are reflected back to the mel status and mel condition properties of a resident mel variable after the pertinent task has made a mel operation.


Mel Buffer Access Modes

A mel variable being a shared entity, its access is controlled by the ? wait operator so that only one task has exclusive access at any given time.

Mel Buffer Read Access

To access the reader's end of the buffer, these statements are equivalent:
c = <? mode=reader>store;
c = <? reader>store;
c = <?>store;
The first statement is fully descriptive. It specifies the mel buffer access mode as reader to mean the access to be from the reader's end. The second statement skips the optional keyword mode. The succinct third statement is the most used since it takes into account that the mode reader is the default mode and does not have to be specified. (The other reading mode is readonly.)

In a later chapter, we will explore all the details about a general mel read. For now, the three equivalent statements towards the buffered mel element store can be described as follows:
  1. The task reaches out to the mel element store.
  2. The task gets info the default mel read queue of store.
  3. The task waits until it reaches the top of that queue.
  4. If the mel element is empty, the task will wait in the queue until the mel element is filled.
  5. If or when the mel element is filled, the task removes the top value at the reader's end of the mel buffer.
  6. The task gets off the queue, initializes the <value> property of its mel variable with the value removed from the mel buffer, and returns to the program flow.
  7. The value of the <value> property is copied to the local variable c.
It is good to re-emphasize that reading from a mel element means a removal. Once an item is read out of the mel buffer by a reader task, it is not available any more for a subsequent reader task. On the other hand, reading out of the mel variable means a copy of the <value> property. The <value> property is like any regular C variable. We can read it and assign it to any other C variables, like the C variable c above, as many times as we want.

Mel Buffer Write Access

To access the mel variable from the writer's end of the buffer, these equivalent statements are used:
<? mode=writer>store = c;
<? writer>store = c;
<?>store = c;
The first statement is fully descriptive. The second statement skips the optional keyword mode. The third statement is the abbreviation that is used the most, since the default mode for writing is writer. (The other writing mode is writeover.)

In a later chapter, we will explore all the details about a mel write. For now, a mel write from the above three statements towards the buffered mel element storecan be described as follows:
  1. The task reaches out to the mel element store.
  2. The task gets into the default mel write queue of store.
  3. The task waits until it reaches the top of that queue.
  4. If the mel element is full, the task will wait in the queue until the mel element is vacant.
  5. If or when the mel element is vacant, the task deposits the value of the local variable c to the writer's end of the mel buffer.
  6. The task gets off the queue and returns to the program flow.
A mel write operation does not affect the <value> property of the resident mel variable of the task. This property is only affected by the buffer read access.


Mel Buffer Property

The mel variables resident in a task have a property that relate to the mel element's buffer feature. It is the buffer property. It is initialized after a mel operation that a task carries towards the mel element, and contains the reported size of the mel buffer.

The buffer can be used to check what the CHAOS runtime has been able to allocate for a mel creation request:
<mel buffer=10000>int store;
printf ("CHAOS gives me [%d] instead", store<buffer>);
The buffer property is part of SET properties of a mel variable. It is updated via piggy-backing with the result of any mel operation the task conducts towards the mel element.

Usually the buffer property is constant with the same value, since once created, the mel element keeps the number of slots that CHAOS has originally allocated to it. However if a task has successfully issued a rebuffer operation, the mel element now has a new buffer size, and the buffer property of mel variables resident in all tasks will contain a stale value. A task can have its mel variable's buffer property updated by invoking a mel operation towards the remote mel element.


Mel Rebuffer Operation

NERWous C allows a concurrent platform to offer running tasks the capability to request the CHAOS runtime to change the size of mel buffers. This request is done via the <rebuffer> operation. Any task can issue <rebuffer>. Since the mel element is shared, a <rebuffer> request from one task, if and when granted by the CHAOS runtime, will make the buffer property of all corresponding mel variables in all the tasks, stale. A mel operation by a task will update its own mel variable's buffer property through piggy-backing.

Like the mel buffer request at creation time, a subsequent <rebuffer> operation is also a best-effort request to the CHAOS runtime. This means that CHAOS may chose to do a full resize of the mel buffer, a partial resize, or no resize at all. On concurrent platforms that do not support mel rebuffering, the last option is the only action that can take place.

A <rebuffer> operation is asynchronous. A task once it has successfully registered the <rebuffer> request to the CHAOS runtime, can get back to its processing flow, without knowing if CHAOS has worked on the request or not. For example, if the rebuffing request is to increase the size of the buffer, it may take CHAOS awhile to find the necessary resources; on the other hand, if the rebuffing request is to decrease the size, CHAOS may have to wait for filled slots to be emptied by readers before those could be taken away. It is better for a requesting task not to wait for these unknowns, and check back on the <buffer> property of the mel variable using the <snapshot> or any other mel operation, as seen in the following example:
main () {
     <mel buffer=100> int store;
     <!> Producer (store);
     <!> Consumer (store);
}
void Producer (<mel> int store) {
     printf ("Original buffer size on entry: [%d]\n", store<buffer>);
     int c;
     while ( c = Produce() ) {
        try { <? timeout>store = c; }
        catch ( store<TIMEOUT> ) {
           int cursize = store<buffer>;
           int newsize = cursize + 10;
           <rebuffer buffer=newsize> store;
           printf ("Previous size [%d], requested size [%d], reported size [%d],
                   cursize, newsize, store<buffer>);
           <resume>;
        }
     }
     <?>store = 0;
     printf ("Final buffer size on exit: [%d]\n", store<buffer>);
}
void Consumer (<mel> int store) {
     int c;
     while ( c = <?>store )
         Consume(c);
}
The Producer task prints the original buffer size on entry, then works on depositing new products to the mel element store. If the Consumer task is slow to pick up those products, Producer asks the CHAOS runtime to increase the size of the mel buffer. When Producer produces a 0 value, it gets off the production loop and prints the final size of the mel buffer. The final size will be the same as the original size if CHAOS has not or cannot honor the rebuffering requests.

The Producer becomes aware of the Consumer slowness when all the existing slots in the mel element still remain filled and it has to wait for a vacant slot. The Producer limits its wait by using the timeout exception. In the TIMEOUT handler, Producer issues a <rebuffer> request. Once this operation returns, Producer checks the <buffer> property to see if CHAOS has carried out the request. For concurrent platforms that have dynamic resource allocation, CHAOS may have satisfied the request before letting the <rebuffer> operation return to the requesting task. Otherwise, if the request has been registered but not worked on yet, the new <buffer> value will be the same as the value before the request (as saved in the cursize local variable).

The Producer uses the <resume> operation to try to deposit the product again. It it were to use the C language continue statement, the logic would flow back to the beginning of the while loop which Produces another product, causing the product that goes into the TIMEOUT exception handler to be skipped. The <resume> statement, which we will explore in details in a later chapter, allows Producer to get back to the last mel wait statement to try the product deposit again.

When the Producer task ends, its <rebuffer> request, if not already serviced, may still be alive. Once submitted, the request becomes independent from the requesting task. A rebuffing request will be cleared only if:
  1. it is serviced
  2. it is automatically rejected on non-supporting platforms
  3. there is a more recent <rebuffer> request
  4. the whole NERWous C program terminates
The ending of the requesting task does not force the pending rebuffer request to clear.

Buffer Attribute

The <rebuffer> operation has only one attribute, and it is mandatory. The buffer attribute denotes the new number of slots that the task requests the CHAOS runtime to change at the remote mel element buffer. Since it is the only attribute, the buffer keyword is usually omitted. The following code snippet lists several examples of the rebuffer operation. For the sake of discussion, the inline comments assume that CHAOS will honor the rebuffering request immediately whenever possible.
<mel 100> int store;    /* Create a mel buffer with 100 slots

<rebuffer buffer=200> store;     /* buffer size goes up to 200 slots */
<rebuffer 300> store;    /* buffer size goes up to 300 slots */

/* Decrease back to 50 slots.
** CHAOS will do the decrease when the buffer has at least 300 - 50 = 250 slots vacant.
*/
int smaller = 50;
<rebuffer buffer=smaller> store;    /* buffer size eventually goes down to 50 */
<rebuffer smaller> store;    /* buffer does not change since it is the current size */

/* Decrease to 0 or negative number of slots. */
int verysmall = 0;
<rebuffer verysmall> store;  /* buffer size eventually goes down to 1 */

/* Increment/decrement the buffer size from current size */
<rebuffer buffer+50> store;  /* buffer size is now 1 + 50 = 51 slots */
<rebuffer buffer+5> store;   /* buffer size is now 51 + 5 = 56 slots */
<rebuffer buffer-10> store;  /* buffer eventually goes down to 46 slots */
Let's expand on some of the inline comments.

Minimum Buffer Size

There must be at least one slot in the mel buffer. If the rebuffing request goes to 0 or lower, the CHAOS runtime will reset the mel buffer to the one slot default value.

Buffer= vs. Buffer+

A statement like <rebuffer buffer=200> store; means a request for the mel buffer to contain between 1 and 200 slots.

A statement like <rebuffer buffer+50> store; means to add from 0 to 50 more slots to the current mel buffer. This is roughly equivalent to the code below:
<snapshot> store;
int cursize = store<buffer>;
<rebuffer buffer=cursize+50>store;
The word roughly is used since it is possible that between the snapshot statement and the rebuffer statement, another task has successfully issued a <rebuffer> operation, causing cursize to contain a stale value.

Previous Next Top