


                                                       Chapter 25
                                            THE SIMPLE RENDEZVOUS


COMMUNICATION BETWEEN TASKS
_________________________________________________________________

In the last chapter, we ran a few programs with tasking, but they
all used simple tasking with no form of synchronization.  In a real
situation using tasking, some form of communication between tasks
will be required so we will study the simple rendezvous in this
chapter.

Examine the program named HOTDOG.ADA which will  ================
illustrate the simple rendezvous.  This program     HOTDOG.ADA
consists of two tasks, the one in lines 10       ================
through 25 and the main program itself.


THE entry STATEMENT
_________________________________________________________________

The task specification is a little more complicated here than it
was in any of the example programs in the last chapter because we
have an entry point declared in the task.  The reserved word entry
begins the entry declaration and is followed by the entry name,
which is Make_A_Hot_Dog in this case.  The entry statement defines
the interface to the outside of a task in much the same way as the
procedure header defines the external interface to a package in the
package specification.  Unlike a package, no types, variables, or
constants are allowed to be declared in the task specification, but
there is no limit to the number of entry points allowed.  An entry
can have formal parameters declared as part of the entry name, (we
will have an example in the next example program), but it cannot
have a return parameter similar to a function.  One or more of the
formal parameters is permitted to have a parameter of mode out or
in out however, so data can be passed both ways while a
synchronization is effected.  The in mode is also permitted.  We
will have more to say about this topic later in this chapter.


THE accept STATEMENT
_________________________________________________________________

The task body is a very simple sequence of statements in which a
message is output to the monitor, a loop is executed four times,
and another message is output.  The thing that is new here is the
accept statement within the loop.  The accept statement begins with
the reserved word accept and has the following general form;

     accept <entry-name> do
            <executable statements>
     end <entry-name>;

                                                        Page 25-1

                               Chapter 25 - The Simple Rendezvous


and any legal Ada statements can be contained within it.  When
execution of the task reaches the accept statement, the task "goes
to sleep" until some other task makes a call to this particular
accept statement.  The term "goes to sleep" means that the task
does not simply sit there and execute a do nothing loop while it
is waiting, effectively wasting the resources of the system.
Instead, it is actually doing nothing until it is awakened by an
entry call.  It is also possible to have an accept statement with
no statements contained within it.  It will have the following
form;

     accept <entry-name>;

This statement will only be used for task synchronization since
there is no data passed to the entered task.


THE ENTRY CALL
_________________________________________________________________

The main part of the program is another loop with four iterations
followed by a statement to display a line of text on the monitor.
The only thing unusual about the loop is the statement in line 29
which is an entry call to the entry named Make_A_Hot_Dog in the
task named Gourmet.  Keep in mind that these are two tasks that are
operating in parallel and we will carefully explain what is
happening.
The entry call is executed at line 29 which wakes up the sleeping
task at line 18 and the task named Gourmet continues from where it
went to sleep.  Notice that the calling program is not controlling
the execution of Gourmet, but Gourmet itself is in control now that
it has been allowed to continue operation.  The entry call is not
like a procedure call where the sequence of operation continues in
the procedure, but is instead only a synchronization call that
allows Gourmet to continue what it was doing prior to being put to
sleep.


WHAT IS THE CALLING PROGRAM DOING NOW?
_________________________________________________________________

During the time that the called task is executing statements within
its accept block, the calling task is effectively put to sleep, and
must wait until the called task completes its accept block.  When
Gourmet reaches line 22, both tasks are allowed to operate in
parallel again until one or both reach their point of rendezvous
again.  If the main program reaches its entry call before Gourmet
is ready to accept the call, then the main program will wait until
Gourmet is ready.  The accept statement, and the corresponding
entry call, are therefore used to synchronize the two tasks.

If you were to move the end of Make_A_Hot_Dog to the line
immediately after the accept statement, including a null of course,

                                                        Page 25-2

                               Chapter 25 - The Simple Rendezvous

the output statements would then be running in parallel.  The
delays have been selected in such a way that after making this
change, the hot dog would be eaten before it was made.  This is one
of the programming exercises at the end of this chapter.


A COUPLE OF FINE POINTS MUST BE MADE HERE
_________________________________________________________________

Although the entry call looks very much like a procedure call, it
is different because it is not legal to use a use clause for a
task.  It is required therefore that every entry call must use the
extended naming convention, or the "dotted" notation.  Renaming can
be used to reduce the size of the names used and will be
illustrated in the next program.

You will notice that the two tasks were composed of exactly four
calls and four executions of the accept statement.  This was done
on purpose in order to simplify the problem of task termination at
this point in our study.  The study of task termination will be
covered in detail in the next chapter.  Until then, you should see
what happens when the two loops are different.  Change the loop in
Gourmet to 5 iterations and see what happens if it waits to accept
a call that never comes, then make the loop in the main program
bigger to see what happens when there is nothing to accept its
call.  We will study tasking exceptions in a later chapter of this
tutorial.  The execution of a return statement (not illustrated
here), within an accept statement corresponds to reaching the final
end and effectively terminates the rendezvous.


SERVERS AND USERS
_________________________________________________________________

Tasks are categorized into two rather loosely defined groups,
server tasks and user tasks.  A server task is one that accepts
calls from other tasks, and a user task is one that makes calls to
one or more server tasks.  In the present example program, Gourmet
is a server task, and the main program is a user task.  Some tasks
both accept calls and make calls, and they are referred to as
intermediate tasks.  This terminology, or some other fairly self
defining terminology may be used in the literature about Ada, so
you should be aware that such classifications may exist.

Be sure to compile and execute this program making the changes
suggested earlier and observe the results.


MULTIPLE ENTRY POINTS
_________________________________________________________________

Examine the program named HOTDOGS.ADA for a few added tasking
topics beginning with multiple accept statements.  You will notice
that there are three accept statements in the task body

                                                        Page 25-3

                               Chapter 25 - The Simple Rendezvous

corresponding to a single entry point declared    ===============
in the task specification.  There is no limit to    HOTDOGS.ADA
the number of accept statements and they are      ===============
executed when the logic causes execution to
arrive at each one in turn.  Remember that the
task is executed in the logical order as defined in the sequence
of statements, except that each time the logic causes execution to
arrive at an accept statement, the task will "go to sleep" until
an entry call is made to the waiting accept statement.  Moreover,
the logic does not care where the entry call comes from, nor does
it know where it came from, it only cares that the entry call is
made.  Thus it is possible for several different tasks to be
calling this entry point and allowing the task to progress through
its logic.  Because there is only one other task in this program,
we know exactly where the entry call is being generated.

Careful study of the logic will reveal that the accept statement
in line 21 must be called, followed by four calls to line 29, and
another call to line 39.  After six calls to this entry point, the
task will reach the end of its execution and will be completed.
You will notice that we make exactly six calls to this entry point
by the task which is the main program, so everything will come out
right.


RENAMING A TASK ENTRY
_________________________________________________________________

The alternate name, which is declared in line 14, is used in the
main program task to illustrate the use of the renaming facility.
Note that the dotted notation is not required in this case.  Once
again, you can change either the number of calls or the number of
accepts to see the exception error or a deadlock condition.



ENTRY PARAMETERS
_________________________________________________________________

You should have noticed by now that each of the accept statements
has a formal parameter associated with it in the same manner that
a subprogram can have.  In fact, it is permissible to have as many
parameters as you desire to transfer data either from the calling
task to the called task or back the other way.  All three modes of
parameter passing are legal for use and the in mode will be used
if none is specified as is done here.  There is one difference in
a task however, you are not permitted to have a return parameter
like a function has, but you can return a value by using an out or
an in out parameter.

Since you can pass parameters both ways, the task appears to be
executed just like a procedure, but there is a very definite
difference as mentioned earlier.  A procedure is actually executed
by the calling program, but the task is simply let free to run on

                                                        Page 25-4

                               Chapter 25 - The Simple Rendezvous

its own in parallel with the calling program.  The task is not a
subservient program but is running its own logic as it is coded to
do.

Be sure to compile and execute this program after you understand
what it is supposed to do.


MULTIPLE CALLING TASKS
_________________________________________________________________

Examine the program named MANYDOGS.ADA for an    ================
example of two tasks calling a third task's        MANYDOGS.ADA
entry point.  The task named Gourmet has a       ================
single entry point, but this time there are two
formal parameters declared in the task
specification and the same two declared in the accept statement.
These must agree of course.  The entry point named Make_A_Hot_Dog
in line 19 contains some text to display and a delay of 0.8 second,
because it takes a little time to make a hot dog.  You will notice
that sometimes mustard is added, and sometimes it is not, depending
on who is eating the result.  We will say more about this later.

We have two additional tasks in lines 35 through 56 named Bill and
John, each of which executes in parallel, and each of which
requests a number of hot dogs and consumes them in zero time, since
there is no delay prior to their next request.  You will also
notice that once again the numbers come out right because Bill
requests three hot dogs and John asks for two while the task that
supplies them makes exactly five.  We will discuss better methods
of task termination later.  For the time being, simply accept the
utopian situation depicted here.


ENTRIES STACK UP AT THE ENTRY POINT
_________________________________________________________________

Considering all that we have said so far about tasking, you should
understand that all three of these tasks begin execution at the
same time and Bill and John both request a hot dog immediately.
Since there is only one entry point, only one can be served at a
time, and the Ada definition does not specify which will be
serviced first.  It does specify that both will ultimately be
served, so there is an implicit queue at the entry point where
requests can be stored until they can be serviced in turn.  All
requests are serviced on a First In First Out (FIFO) basis with no
concern for priority of tasks (to be defined later).  This queue
is a "hidden" queue as far as you, the programmer, are concerned
because you have no access to it.  You cannot therefore, sort
through the entries and redefine the order of execution of the
various entry requests.  The attribute named COUNT is available
which will return the number of requests pending on any entry
queue.  Its use will be illustrated in the program named FAMILY.ADA
in chapter 27 of this tutorial.

                                                        Page 25-5

                               Chapter 25 - The Simple Rendezvous


After we study some of the advanced tasking topics, you will have
the ability to define several queues with different priorities and
set up your own priorities as needed.



THE ORDER OF OUTPUT IS SORT OF ODD
_________________________________________________________________

Back to the program at hand.  Examining the result of execution
will reveal that for some unknown reason, the task named John made
the first request and the task named Gourmet made a hot dog with
mustard first, because John's task was the one requesting a hot dog
with mustard.  However, before John was allowed to continue
execution, the Gourmet task continued and serviced the next call
in its entry queue for Make_A_Hot_Dog, and made a hot dog without
mustard for Bill.  At this point Gourmet had completed all of its
entry calls and allowed one of the other tasks to run.  The task
named John was then allowed to continue execution.  John ate his
hot dog and requested another.  Once again, by some undefined
method, the task named Gourmet was allowed to run and make a second
hot dog for John, with mustard, when Bill still hasn't been allowed
to eat his first one.  This continues until all conditions have
been satisfied, which means that five hot dogs have been made, and
all five have been consumed.  Both consuming tasks declare their
lack of hunger, and the task named Gourmet declares that it is out
of hot dogs.  The program has finally run to completion.



WHAT ABOUT THE FUNNY ORDER OF RESULTS?
_________________________________________________________________

Even though the results seemed to come out in a funny order, they
did follow all the rules we set down for them to follow.  Remember
that as an experienced programmer, you are accustomed to seeing
everything come out in a very well defined precise order, because
you have spent your programming career writing sequential programs.
If you look at this output from the point of view of each task,
you will see that the output from each task is perfectly
sequential, as defined by the logic of the task.  Additionally, you
will see that the order of execution has been preserved as defined
by the various rendezvous, because nobody eats a hot dog before it
is made, and there are no hot dogs made too early.  The
synchronization of the tasks has been done exactly as we requested.

Spend enough time studying the logic here to completely understand
what is happening, then compile and execute this program to see if
your compiler does anything in a different order.





                                                        Page 25-6

                               Chapter 25 - The Simple Rendezvous

THE select STATEMENT
_________________________________________________________________

Examine the program named RETAIL1.ADA for our     ===============
first example program using a select statement.     RETAIL1.ADA
The select statement is used to allow a task to   ===============
select between two or more alternative entry
points.  In effect, a task can be waiting at two
or more entry points for an entry call to be made, and can act on
the first occurring entry call.  The structure of the select
statement is given by;

     select
        accept ...;          -- Complete entry point logic
     or
        accept ...;          -- Complete entry point logic
     or
        accept ...;          -- Complete entry point logic
     end select;

and is illustrated in lines 23 through 33 of the present example
program.  In this case there are two select alternatives, but there
is no limit to the number of selections.  Each additional branch
is delimited by the reserved word or.  When program control of the
task arrives at the select statement in line 23, either entry call
can be accepted and acted upon immediately.



THE OVERALL PROGRAM
_________________________________________________________________

Common sense tells us that we cannot deliver a hot dog until we
stock the shelf with a hot dog, so the program has been written to
reflect this.  The task requires an entry call to
Stock_With_A_Hot_Dog before it begins the loop with the select in
it to assure that at least one hot dog will be available.  After
that, it doesn't care what the order of entry calls is because the
select statement in the loop will allow them to occur in any order.
This is a very simplistic approach to setting up a precedence
requirement in an Ada task, but it is too simple to really be
effective which we shall see when we examine some of the problems
that can occur.

In the first place, if the main program, or task, fails to call
Stock_With_A_Hot_Dog first, the system will simply lock up with the
calling program demanding a delivered hot dog and steadfastly
refusing to continue until it does, and the called task refusing
to deliver one until it has been stocked with one in line 16.  The
system is in deadlock with both tasks refusing to do anything.  You
can simulate this condition by reversing the two calls in lines 39
and 40.  Your compiler will probably give a message indicating
deadlock has occurred and terminate operation of the program.
Another problem has to do with the inflexibility of this program,

                                                        Page 25-7

                               Chapter 25 - The Simple Rendezvous

since we have once again counted the number of calls required to
complete the two tasks and programmed compatible numbers in the two
tasks.

A further problem involves the fact that, after one hot dog has
been stocked, there is nothing to prevent us from taking delivery
of hundreds of hot dogs without adding any more to the shelves.

When you think you understand this program, compile and execute it,
then we will go on to the next program where we will solve two of
the three problems mentioned in connection with the present
program.


SELECT STATEMENTS WITH GUARDS
_________________________________________________________________

Examine the program named RETAIL2.ADA for an      ===============
example of a select statement with guards.  The     RETAIL2.ADA
guards are used to guard the entry points of the  ===============
select statement to prevent the kinds of silly
things that happened in the last program.  The
task body Retail_Hot_Dogs has been modified in this program to
include guards in lines 29 and 37 for the select statement in lines
28 through 42.  A guard is simply a BOOLEAN condition that must be
satisfied before that particular entry point can be accepted and
its logic executed.  The general form of the select statement with
guards is given as;

     select
        when <BOOLEAN condition> =>
           accept ...;               -- Complete entry point logic
     or
        when <BOOLEAN condition> =>
           accept ...;               -- Complete entry point logic
     or
        when <BOOLEAN condition> =>
           accept ...;               -- Complete entry point logic
     end select;

and there is no limit to the number of permissible selections, each
being separated by the reserved word or.  In fact, one or more of
the selections can have no guard, in which case it is similar to
having a guard which always evaluates to TRUE.  When the select
statement is encountered, each of the guards is evaluated for TRUE
or FALSE, and those conditions that evaluate to TRUE are allowed
to enter into the active wait state for an entry, while those that
have guards evaluating to FALSE are not.  Those with guards
evaluating to FALSE are treated as if they didn't exist for this
pass through the loop.  Once the guards are evaluated upon entering
the select statement, they are not reevaluated until the next time
the select statement is encountered, but remain static.  If all
guards evaluate to FALSE for a given select statement, the
exception Program_Error is raised.

                                                        Page 25-8

                               Chapter 25 - The Simple Rendezvous


LIMITING THE NUMBER OF HOT DOGS ON THE SHELF
_________________________________________________________________

In this program, when the select statement is entered in line 28,
the guard at line 29 is evaluated and if the number of hot dogs on
the shelf is less than 8, then the accept statement in line 30 is
enabled and we are permitted to stock the shelf with one more hot
dog.  If the number of hot dogs is greater than zero, as the guard
at line 37 tests for us, then the accept statement in line 38 is
enabled and we are allowed to deliver a hot dog.  Even though we
may be allowed to either stock the shelf with a hot dog, or deliver
a hot dog, we must wait until some other task requests us to do so
before we actually do one of the operations.  It should be clear
to you that in this particular case we will always be permitted to
do at least one of the operations, and in many cases both will be
permitted.  If none of the guards evaluate to TRUE, then none of
the selections can be taken, and the program is therefore
effectively deadlocked and the exception named Tasking_Error will
be raised.  You, the programmer, can trap this exception in much
the same way that you can trap any other exception and handle it
in your own manner, but the rules are a little different for
tasking exceptions than for exceptions raised during sequential
operation.  We will cover tasking exceptions in detail later.

It should be pointed out that, even if a guard evaluates to FALSE,
entries can be added to the entry queue and serviced during
subsequent executions of the select statement when the guard may
become TRUE.  Because of this method of defining the entry queue,
no calls to the entry are lost, and the operation is predictable.

This program contains four tasks, counting the main program, with
one named Five_Dogs stocking the shelf very quickly with five hot
dogs, because of the short delay, and another removing five hot
dogs a little slower.  The main program stocks and retrieves four
hot dogs rather slowly due to the relatively long time delay built
into the loop.


WATCH THE GUARDS DO THEIR JOB
_________________________________________________________________

When you run this program you will see very little action with the
guards because of the selection of the guard limits.  The five hot
dogs are put on the shelf very quickly, but the upper limit of 8
is never reached, and there are always hot dogs on the shelf to
supply the limited demands.  In fact, as listed in the result of
execution, there are never more than 5 on the shelf, and always
more than zero.  You should compile and execute the program to see
if your compiler does the same thing as the one used for this
execution.

Change line 29 so that the limit is 3, and recompile and execute
the resulting program.  In this case, you will very clearly see

                                                        Page 25-9

                               Chapter 25 - The Simple Rendezvous

that the first guard prevents more than three hot dogs from being
placed on the shelf.  In effect it builds up the entry queue for
the Stock_With_A_Hot_Dog entry point and requires the suppliers to
wait for shelf space.

Reverse the delays in lines 49 and 57, as your next exercise, so
that the hot dogs are consumed much faster than they are stocked
so that the guard on the entry point named Deliver_A_Hot_Dog will
be needed to protect the delivery of too many hot dogs.  In this
case, the queue to this entry point will build up a list of
requests to be satisfied as hot dogs are delivered.


THIS PROGRAM IS MUCH BETTER
_________________________________________________________________

This program solved two of the three problems listed concerning the
last program but we still must use the method of counting the
required entry calls and providing the proper number of entries.
As promised before, this problem will be remedied soon.  Be sure
you compile and execute this program three times, once unmodified,
and twice with the suggested changes, then study the output to
assure yourself that you understand it completely.



PROGRAMMING EXERCISES
_________________________________________________________________

1.   Move the end of the accept statement in HOTDOG.ADA to the line
     immediately after the accept statement itself to see that it
     is possible to eat the hot dog before it is made because the
     tasks are both running at the same time.

2.   Add another task to RETAIL2.ADA that executes a loop 10 times
     with a 0.3 second delay that outputs the current number of hot
     dogs on the shelf.

3.   Using the package Calendar, output the elapsed time each time
     the new procedure defined in exercise 2 outputs the number of
     hot dogs on the shelf.











                                                       Page 25-10