Pages

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

No comments:

Post a Comment