Pages

Saturday, March 30, 2013

NOTHING Mel Exception

Welcome » NERWous C » Exceptions

The NOTHING mel exception is the ying-yang of the NOROOM exception. While NOROOM checks if the mel buffer is full, the NOTHING exception is raised when the mel buffer is empty. Like NOROOM, the NOTHING is raised immediately by the application itself when it accesses the mel variable via the wait expression ?.

The NOTHING exception has the same relationship with the NOIN exception as between NOROOM and NOOUT exceptions. While NOTHING and NOIN both check that the mel buffer does not have any product, the NOIN exception is raised by the run-time environment after a configurable amount of time to instill confidence that no Producers will fill the buffer again. As said previously, the NOTHING is raised by the application immediately upon detection of the empty condition.

Let now rewrite the Producer / Consumer example to have two Producers and one Consumer:
main() {
    pel Producer() p1;
    pel Producer() p2;
    pel Consumer (p1, p2) c;
}
(int mel val) Producer () {
    int product;
    for (int i=0; i<10; ++i)
       ?val = produce();
}
void Consumer (pel Producer p1, pel Producer p2) {
    while ( 1 )
        try {
           consume (?p1!val);
        }
        catch (p1!val!NOTHING) {
           consume (?p2!val);
        }
     }
}
The Consumer will first try to get the product from the Producer p1. If this Producer does not have a product ready yet, the NOTHING exception is raised on its mel return port, and Consumer instead of waiting for p1, now attempts to get a product out of the other Producer, p2.

In the above example, if p2 also does not have a product, the Consumer is stuck waiting for it, even if by that time the first Producer, p1 has a product out. To allow the Consumer to keep checking both Producers until it gets a product from any of them, we can rewrite the Consumer:
void Consumer (pel Producer p1, pel Producer p2) {
    while ( 1 )
        consume ( (?p1!val NOTHING ?p2!val) );
 }
One flaw on the above code is that when both Producers have terminated, the Consumer keeps checking ad infinitum. To break out of such loop, the Consumer must also check for the NOIN exception:
void Consumer (pel Producer p1, pel Producer p2) {
    while ( 1 ) {
      try {
        consume ( (?p1!val NOTHING ?p2!val)<somebody_give_me> );
      } catch (somebody_give_me!NOIN) {
          printf ("Both Producers have terminated. I go home too.");
          break;    // out of while loop
      }
    }
 }
Here, the Consumer checks ?p1!val first. If this mel port has NOTHING, it checks ?p2!val. If this mel port also has NOTHING, it goes back to check ?p1!val. This continuous checking is programmatically tagged with the tag somebody_give_me. This tagged checking continues until either Producers has a product for the Consumer, or a NOIN condition has been raised on both Producer mel ports.

We can pass an array of many Producers to the Consumer,too:
void Consumer (pel Producer p[]) {
    while ( 1 ) {
      try {
        consume ( (?p!val NOTHING)<somebody_give_me> );
      } 
      catch (somebody_give_me!NOIN) {
          printf ("All Producers have terminated. I go home too.");
          break;    // out of while loop
      }
    }
}


If in the above example, the first Producer in the array is somehow special, we can specifically watch for its termination in addition to all Producer terminations:
void Consumer (pel Producer p[]) {
    while ( 1 ) {
      try {
        consume ( (?p!val NOTHING)<somebody_give_me> );
      } 
      catch (?p[0]!val!NOIN) {
          printf ("The leader Producer has terminated. I follow the leader and go home.");
          break;    // out of while loop
      }
      catch (somebody_give_me!NOIN) {
          printf ("All Producers have terminated. I go home too.");
          break;    // out of while loop
      }
    }
}

NOROOM Mel Exception

Welcome » NERWous C

In the previous Producer/Consumer example, we have two Producers vying for the same mel. If they come to the mel at the same time, the run-time environment will allow one Producer to enter the mel, while the other waits at the ?. If the Consumer is slow to empty the mel, the mel can get full, and the Producers have to wait for room to be made available again on the mel. Instead of waiting, the Producers have the option to check for this no-room condition through the mel exception NOROOM.

Let's modify the previous Producer/Consumer example by introducing a second Consumer:
main() {
    pel Consumer() c1;
    pel Consumer() c2;
    pel Producer1(c1, c2);
    pel Producer2(c1);
}
void Producer1 (pel Consumer c1, pel Consumer c2) {
    int product;
    for (int i=0; i<10; ++i) {
       product = produce();
       try {
           ?c1!val = product;
       }
       catch (c1!val!NOROOM) {
           ?c2!val = product;
       }
    }
}
void Producer2 (pel Consumer c) {
    for (int i=0; i<70; ++i)
       ?c!val = produce();
}
void Consumer (mel int val) {
    try {
       while ( 1 )
           consume (?val);
     }
     catch (val!NOIN) {
       printf ("No more producers to me");
     }
}
The first Producer makes use of both Consumers. The second Producer uses only the first Consumer. The first Consumer's input mel val can be slow to drain because of its serving of two Producers. If things buffer up and this mel has no room, the first Producer detects this condition via the NOROOM mel exception and switch to the second Consumer. On the other hand, the second Producer sticks with its Consumer and opts for waiting at the ?c!val until the mel buffer is freed up for it to deposit the product.

Unlike the temporaneous mel exception NOOUT, the NOROOM exception is immediate when the Producer checks the mel variable at ?c!val. On the other hand, the NOOUT exception depends on the run-time environment to monitor all consumers of the mel variable and raise that exception if after a configurable amount of time it has not detected any consumption.

With the existing NOROOM exception, there is no need for Producer1 to add the NOOUT checking to its mel communication between itself and Consumer1. However we can enhance the 2nd Producer so that it will not wait forever if its Consumer1 partner has terminated, and there is no consumer for its product:
void Producer2 (pel Consumer c) {
    for (int i=0; i<70; ++i) {
      try {
        ?c!val = produce();
      }
      catch (?c!val!NOOUT) {
        print ("My consumer has gone away. I go home too.");
        break;    // out of for loop
      }
   }
}
It is worth repeating that NOROOM is different from NOOUT. If Producer2 were using NOROOM, it would exit at the first detection of the buffer-full condition at the mel variable ?c!val. With the use of NOOUT, Producer2 will wait for a configurable amount of time for the Consumer to catch up on its consumption and clear out the mel buffer val

In NERWous C, there is a better way to write Producer1 so that it will keep trying between Consumer1 and Consumer2 if their mel ports are in the NOROOM mode (i.e. full), until either one port can accept the product or until the NOOUT conditions have been detected at both ports (i.e. both Consumers have terminated):
void Producer1 (pel Consumer c1, pel Consumer c2) {
    int product;
    for (int i=0; i<10; ++i) {
       product = produce();
       try {
           (?c1!val NOROOM ?c2!val)<somebody_take_me> = product;
       }
       catch (?c1!val!NOOUT) {
           printf ("Consumer1 has terminated. I continue with Consumer2 only.");
           nobreak;   // continue waiting with c2 only
       }
       catch (?c2!val!NOOUT) {
           printf ("Consumer2 has terminated; I continue with Consumer1 only.");
           nobreak;  // continue waiting with c1 only
       }
       catch (somebody_take_me!NOOUT) {
           printf ("Both consumers have terminated. I go home too.");
           break;     // out of for loop
       }
    }
}
Here, Producer1 checks ?c1!val first. If this mel has NOROOM, it checks ?c2!val. If this mel also has NOROOM, it goes back to check ?c1!val. This continuous checking is programmatically tagged with the tag somebody_take_me. This tagged checking continues until either Consumer mel takes in the product, or a NOOUT condition has been raised on both Consumer mels.

We can pass an array of many Consumers to Producer1, too:
void Producer1 (pel Consumer c[]) {
    int product;
    for (int i=0; i<10; ++i) {
       product = produce();
       try {
           (?c[]!val NOROOM)<somebody_take_me> = product;
       }
       catch (somebody_take_me!NOOUT) {
           printf ("All consumers have terminated. I go home too.");
           break;     // out of for loop
       }
    }
}
In the above example, the Producer1 will try all consumers in the array c. If the first consumer is full (i.e. NOROOM), then Producer1 will try the next consumer in the array. If none of the consumers in the array are available, it will loop back and check the first one again. This busy check will continue forever until one consumer can take the product (and Producer1 can go on and produce another product until 10 are produced), or the exception NOOUT is raised when the running system detects that all the consumers in the array have terminated. If some consumers have terminated and some consumers are still active, then the NOOUT exception is not raised.

Saturday, June 9, 2012

NOW Directive

In addition to the buffer size and wait-time directives, the mel variable supports a no-wait access directive, called the NOW directive. Let's change our Producer/Consumer example to use the NOW directives:
main() {
    pel Producer() p;
    pel Consumer(p);
}
(mel item val<10>) Producer () {
   try {
        while ( !timeToPunchOut() )
           ?val<NOW> = produce();
    }
    catch (val!NOREADER) {
        printf ("Consumer has done consuming. I go home too");
    }
}
void Consumer (Producer p) {
    item c;
    try {
      while ( 1 ) {
          c = ?p!val<NOW>;
          if ( c == NULL ) doOtherStuff();
          else consume(c);
    }
    catch (p!TERMINATE){
      printf ("Producer has done producing. I go home too");
    }
    catch (p!val!NOWRITER) {
      printf ("Producer has disappeared. I get out too");
    }
}

Saturday, June 2, 2012

Mel Directives

In the previous Producer/Consumer example, we mention that the mel variable acts like a buffer between the Producer and Consumer. NERWous C defines two directives for the mel variable, buffer size and wait time. Let's look at this new Producer/Consumer code:
main() {
    pel Producer() p;
    pel Consumer(p);
}
(mel item val<10>) Producer () {
   try {
        while ( !timeToPunchOut() )
           ?val<20msec> = produce();
    }
    catch (val!NOREADER) {
        printf ("Consumer has done consuming. I go home too");
    }
}
void Consumer (Producer p) {
    try {
      while ( 1 )
          consume(?p!val<50min>);
    }
    catch (p!TERMINATE){
      printf ("Producer has done producing. I go home too");
    }
    catch (p!val!NOWRITER) {
      printf ("Producer has disappeared. I get out too");
    }
}
In the above code, the Producer suggests to the run-time system to allocate 10 "buffer slots" for the mel variable val. This suggestion is similar to the register declaration in the C language. The run-time system may accept or ignore the buffer size directive.

Both the Producer and Consumer also indicate how long they will wait before raising the mel exceptions NOREADER or NOWRITER. Again, these values are suggestions. The run-time system may or may not support the per call wait time directive.

Sunday, May 20, 2012

Wait Exception (NOMORE / NOOUT / NOIN)

In the previous Producer/Consumer example, the Producer indicates that it is done producing by generating a NULL item as the last item. Many Producers are not that nice. When they are done producing, they just terminate. What happens to the Consumer then? Will it wait forever? Let's look at this new code:
main() {
    pel Producer() p;
    pel Consumer(p);
}
(mel item val) Producer () {
    while ( !timeToPunchOut() )
      ?val = produce();
}
void Consumer (Producer p) {
    try {
      while ( 1 )
          consume(?p!val);
    }
    catch (p!NOMORE){
      printf ("Producer has done producing. I go home too");
    }
}
Here, the Consumer keeps consuming until its wait for the mel val of the pel p fails due to the NOMORE pel exception. This exception is raised by the Producer p as it terminates (normally or abnormally). This allows the Consumer to clean itself up (here by printing a message) and terminate too. When both Producer and Consumer have terminated, the whole program terminates.

Now, let us look at the reverse problem, where the Consumer terminates before consuming all the produced items. The Producer keeps producing and deposits into its mel variable val, which acts like a buffer between the Producer and Consumer. If the mel val is full and cannot accept any more items, the Producer then waits at the ? for the Consumer to clear the mel val. If the Consumer has terminated (normally or abnormally), the Producer will wait forever. To break out of such potentially infinite wait, the Producer can check for the NOOUT mel exception:
(mel item val) Producer () {
    try {
        while ( !timeToPunchOut() )
           ?val = produce();
    }
    catch (val!NOOUT) {
        printf ("Consumer has done consuming. I go home too");
    }
}
The NOOUT mel exception is raised when there is no reader subscribing to the mel and the mel buffer stays full for some time (this "some time" is configurable).

There is a corresponding NOIN mel exception for the Consumer to check in case there are no Producer subscribing to the mel to deposit new values. Let's rewrite the example so that we now have one Consumer but two Producers:
main() {
    mel int Val;
    pel Producer1();
    pel Producer2();
    pel Consumer();
}
void Producer1 () {
    for (int i=0; i<10; ++i)
       ?Val = produce();
}
void Producer2 () {
    for (int i=0; i<70; ++i)
       ?Val = produce();
}
void Consumer () {
    try {
       while ( 1 )
           consume (?Val);
     }
     catch (Val!NOIN) {
      printf ("No more producers");
     }
}
The mel Val is now used by both Producers and the single Consumer. Producer1 produces 10 times then terminates. Producer2 does 70 times and terminates. After both of them have terminated, the Consumer will wait forever at the ?Val. Here the Consumer protects itself from a forever wait by checking the NOIN mel exception. This exception is raised by the run-time environment when it detects that there are no pel processes that register to produce for the mel variable Val for some time (this "some time" is configurable).

Thursday, May 10, 2012

Basic Pel and Mel

Welcome » NERWous C

A Serial Producer / Consumer

This is a Producer / Consumer example written 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 3-Task NERW Producer / Consumer

Let's apply the NERW model to make the above code concurrent:
main () {
     <mel> int store;
     <pel> Producer (store);
     <pel> Consumer (store);
}
void Producer (<mel> int store) {
     int c;
     while ( c = Produce() )
         <?>store = c;

     <?>store = 0;
}
void Consumer (<mel> int store) {
     int c;
     while ( c = <?>store )
         Consume(c);
}
The task main creates a mel variable, store, as the communication channel between the Producer and Consumer. It then creates a task to run the Producer code via the pel construct, and immediately creates another task to run the Consumer code. The task main then waits for all the tasks it forks to be terminated.

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 definition, the mel variable has one slot (we will see how to define a multi-slotted mel variable later). If a slower Consumer task has not consumed the previous product, the Producer will wait at the mel via the <?> construct. Once the deposit to the mel is successful, the Producer resumes its Produce chore. When it cannot Produce any more, it will deposit a 0 value to the mel to alert the Consumer task. The Producer task then terminates.

The Consumer task runs in parallel with the Producer task. The two synchronize at the mel variable 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 <?> construct. Once it sees a 0-valued product, the Consumer task terminates.

With all tasks it forks terminated, the main task terminates itself. The application then exits.


A 2-Task NERW Producer / Consumer

Instead of pelling a task to run Consumer, the task main can run it by itself:
main () {
     <mel> int store;
     <pel> Producer (store);
     Consumer (store);
}
This will save some task resources.

Monday, May 7, 2012

NERWous C

Welcome

NERWous C is an embodiment of the NERW Concurrency Model using the C language as the base language. NERWous stands for "NERW on utopian systems" since NERWous C currently exists only as a thought exercise.

The purpose of NERWous C is to explore a programming syntax that exposes the capabilities of the NERW Concurrency Model. It answers the question -- how to "easily" write a program based on this reference model. Writing a concurrent program is hard, making it easy is many times harder.

There is an abundance of ways to write a concurrent program. Each concurrent language or extension has its own syntax; you would need more than your fingers and toes to count them. Let NERWous C add another one, which is different from the others. NERWous C's approach is not object oriented like many contemporary concurrent languages; instead, it is procedural and mimics the HyperText Markup Language (HTML):
  1. It uses angle brackets (< and >) to encapsulate NERW-related facilities, preserving as much as possible the existing logic of a program. A simple serial C program could be made concurrent by sprinkling here and there various angle-bracketed NERWous C constructs.
     
  2. It offers attributes in most angle-bracketed NERWous C constructs. Many of these attributes are supported in a relaxed manner, like in the HTML language. If the underlying CHAOS runtime supports them, they will be executed in the optimal manner that the programmer intends; otherwise, their default behavior will allow the program to continue in a strapped and stripped state.
In this chapter:


Previous Next Top