Welcome
» NERWous C
» Mel
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
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:
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
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 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:
Buffer Keyword Omission
The
Mel Buffer vs. Value Property
In the previous Producer/Consumer example, we have three mel variables of the same name, one for each task (
The mel buffer belongs to the remote mel element, and not to the resident mel variables. When a reader task, like
Mel Buffer States
The mel buffer is a First-In-First-Out (FIFO) circular queue. A writer task (such as
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
Mel Buffer Read Access
To access the reader's end of the buffer, these statements are equivalent:
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
Mel Buffer Write Access
To access the mel variable from the writer's end of the buffer, these equivalent statements are used:
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
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
Usually the
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
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:
The
The
When the
Buffer Attribute
The
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
A statement like
Previous Next Top
- Mel with a Buffer
- Mel Buffer States
- Mel Buffer Access Modes
- Mel Buffer Property
- Mel Rebuffer Operation
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:
- The task reaches out to the mel element
store
. - The task gets info the default mel read queue of
store
. - The task waits until it reaches the top of that queue.
- If the mel element is empty, the task will wait in the queue until the mel element is filled.
- If or when the mel element is filled, the task removes the top value at the reader's end of the mel buffer.
- 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.
- The value of the <value> property is copied to the local variable
c
.
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
store
can be described as follows:
- The task reaches out to the mel element
store
. - The task gets into the default mel write queue of
store
. - The task waits until it reaches the top of that queue.
- If the mel element is full, the task will wait in the queue until the mel element is vacant.
- 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. - The task gets off the queue and returns to the program flow.
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 Produce
s 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:
- it is serviced
- it is automatically rejected on non-supporting platforms
- there is a more recent <rebuffer> request
- the whole NERWous C program terminates
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