DataMuseum.dk

Presents historical artifacts from the history of:

Rational R1000/400 Tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about Rational R1000/400 Tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: C T

⟦1b00f5a47⟧ TextFile

    Length: 58670 (0xe52e)
    Types: TextFile
    Names: »CHAPTER_9«

Derivation

└─⟦180fe333a⟧ Bits:30000405 8mm tape, Rational 1000, SW CATALOG, 10_20_0
└─⟦180fe333a⟧ Bits:30000537 8mm tape, Rational 1000, SW Catalog 10_20_0
    └─⟦5cb1d1d7f⟧ »DATA« 
        └─⟦3b1ee7bd8⟧ 
            └─⟦this⟧ 

TextFile

                                 9. Tasks



The execution of a program that does not contain a task is defined in terms
of a sequential execution of its actions, according to the rules  described
in  other  chapters  of this manual.  These actions can be considered to be
executed by a single logical processor.

Tasks are entities whose executions proceed in parallel  in  the  following
sense.   Each  task can be considered to be executed by a logical processor
of  its  own.   Different  tasks  (different  logical  processors)  proceed
independently, except at points where they synchronize.

Some  tasks have entries.  An entry of a task can be called by other tasks.
A task accepts a call  of  one  of  its  entries  by  executing  an  accept
statement for the entry.  Synchronization is achieved by rendezvous between
a  task  issuing an entry call and a task accepting the call.  Some entries
have parameters;  entry calls and accept statements for  such  entries  are
the principal means of communicating values between tasks.

The  properties of each task are defined by a corresponding task unit which
consists of a task specification and a task body.  Task units  are  one  of
the  four  forms  of  program  unit of which programs can be composed.  The
other forms are subprograms, packages and generic units.  The properties of
task units,  tasks,  and  entries,  and  the  statements  that  affect  the
interaction   between   tasks  (that  is,  entry  call  statements,  accept
statements, delay statements, select statements, and abort statements)  are
described in this chapter.

Note:

Parallel   tasks  (parallel  logical  processors)  may  be  implemented  on
multicomputers, multiprocessors, or with interleaved execution on a  single
physical  processor.   On  the  other  hand, whenever an implementation can
detect that the same effect can be guaranteed if parts of the actions of  a
given  task  are  executed  by  different  physical  processors  acting  in
parallel, it may choose to execute them in  this  way;   in  such  a  case,
several physical processors implement a single logical processor.

References:   abort  statement  9.10, accept statement 9.5, delay statement
9.6, entry 9.5, entry call statement  9.5,  generic  unit  12,  package  7,
parameter  in  an  entry  call  9.5, program unit 6, rendezvous 9.5, select
statement 9.7, subprogram 6, task body 9.1, task specification 9.1




9.1  Task Specifications and Task Bodies



                                   9 - 1








A task unit consists of a task specification  and  a  task  body.   A  task
specification  that  starts  with  the reserved words  task type declares a
task type.  The value of an object of a task type designates a task  having
the  entries,  if  any, that are declared in the task specification;  these
entries are also called entries of this object.  The execution of the  task
is defined by the corresponding task body.



















































                                   9 - 2









A  task specification without the reserved word type defines a single task.
A task declaration with this form of specification  is  equivalent  to  the
declaration   of  an  anonymous  task  type  immediately  followed  by  the
declaration of an object of the task type, and  the  task  unit  identifier
names the object.  In the remainder of this chapter, explanations are given
in  terms  of  task  type declarations;  the corresponding explanations for
single task declarations follow from the stated equivalence.

    task_declaration ::= task_specification;

    task_specification ::=
       task [type] identifier [is
          {entry_declaration}
          {representation_clause}
       end [task_simple_name]]

    task_body ::=
        task body task_simple_name is
           [declarative_part]
        begin
            sequence_of_statements
       [exception
            exception_handler
           {exception_handler}]
        end [task_simple_name];

The simple name at the start of a task  body  must  repeat  the  task  unit
identifier.   Similarly  if  a  simple  name appears at the end of the task
specification or body, it must repeat the task unit identifier.   Within  a
task  body,  the  name  of  the corresponding task unit can also be used to
refer to the task object that designates the task currently  executing  the
body;   furthermore,  the  use  of  this name as a type mark is not allowed
within the task unit itself.

For the  elaboration  of  a  task  specification,  entry  declarations  and
representation  clauses,  if  any, are elaborated in the order given.  Such
representation clauses only apply to  the  entries  declared  in  the  task
specification (see 13.5).

The  elaboration  of a task body has no other effect than to establish that
the body can from then on be used for the execution of tasks designated  by
objects of the corresponding task type.

The  execution of a task body is invoked by the activation of a task object
of the corresponding type (see 9.3).  The optional  exception  handlers  at
the end of a task body handle exceptions raised during the execution of the
sequence of statements of the task body (see 11.4).

Examples of specifications of task types:

    task type RESOURCE is
       entry SEIZE;
       entry RELEASE;
    end RESOURCE;


                                   9 - 3








    task type KEYBOARD_DRIVER is
       entry READ (C : out CHARACTER);
       entry WRITE(C : in  CHARACTER);
    end KEYBOARD_DRIVER;





















































                                   9 - 4








Examples of specifications of single tasks:

    task PRODUCER_CONSUMER is
       entry READ (V : out ITEM);
       entry WRITE(E : in  ITEM);
    end;

    task CONTROLLER is
       entry REQUEST(LEVEL)(D : ITEM);  --  a family of entries
    end CONTROLLER;

    task USER;  --  has no entries

Example of task specification and corresponding body:

    task PROTECTED_ARRAY is
       --  INDEX and ITEM are global types
       entry READ (N : in INDEX; V : out ITEM);
       entry WRITE(N : in INDEX; E : in  ITEM);
    end;

    task body PROTECTED_ARRAY is
       TABLE : array(INDEX) of ITEM := (INDEX => NULL_ITEM);
    begin
       loop
          select
             accept READ (N : in INDEX; V : out ITEM) do
                V := TABLE(N);
             end READ;
          or
             accept WRITE(N : in INDEX; E : in  ITEM) do
                TABLE(N) := E;
             end WRITE;
          end select;
       end loop;
    end PROTECTED_ARRAY;

Note:

A task specification specifies the interface of tasks of the task type with
other  tasks  of  the  same  or  of different types, and also with the main
program.

References:  declaration 3.1, declarative part 3.9, elaboration 3.9,  entry
9.5,  entry  declaration  9.5, exception handler 11.2, identifier 2.3, main
program 10.1, object 3.2, object declaration 3.2.1,  representation  clause
13.1,  reserved word 2.9, sequence of statements 5.1, simple name 4.1, type
3.3, type declaration 3.3.1





9.2  Task Types and Task Objects



                                   9 - 5








A task type is a limited type (see 7.4.4).  Hence  neither  assignment  nor
the  predefined  comparison  for  equality  and  inequality are defined for
objects of task types;  moreover, the mode out is not allowed for a  formal
parameter whose type is a task type.





















































                                   9 - 6









A  task object is an object whose type is a task type.  The value of a task
object designates a task that has the entries  of  the  corresponding  task
type,  and whose execution is specified by the corresponding task body.  If
a task object is the object, or a subcomponent of the object,  declared  by
an  object declaration, then the value of the task object is defined by the
elaboration of the object declaration.  If a task object is the object,  or
a  subcomponent  of  the object, created by the evaluation of an allocator,
then the value of the task object is  defined  by  the  evaluation  of  the
allocator.   For  all  parameter modes, if an actual parameter designates a
task, the associated formal parameter designates the same task;   the  same
holds  for  a  subcomponent  of  an  actual parameter and the corresponding
subcomponent of the associated formal parameter;  finally, the  same  holds
for generic parameters.

Examples:

    CONTROL  : RESOURCE;
    TELETYPE : KEYBOARD_DRIVER;
    POOL     : array(1 .. 10) of KEYBOARD_DRIVER;
    --  see also examples of declarations of single tasks in 9.1

Example of access type designating task objects:

    type KEYBOARD is access KEYBOARD_DRIVER;

    TERMINAL : KEYBOARD := new KEYBOARD_DRIVER;

Notes:

Since  a  task type is a limited type, it can appear as the definition of a
limited private type in a private part, and as a generic  actual  parameter
associated  with  a  formal parameter whose type is a limited type.  On the
other hand, the type of a generic formal parameter of mode in must not be a
limited type and hence cannot be a task type.

Task objects behave as constants (a task object always designates the  same
task)  since  their  values are implicitly defined either at declaration or
allocation, or by a parameter  association,  and  since  no  assignment  is
available.   However  the  reserved  word  constant  is  not allowed in the
declaration  of  a  task  object  since  this  would  require  an  explicit
initialization.   A  task object that is a formal parameter of mode in is a
constant (as is any formal parameter of this mode).

If an application needs to store and exchange task identities, it can do so
by defining an access type designating the corresponding task  objects  and
by  using  access  values  for identification purposes (see above example).
Assignment is available for such an access type as for any access type.

Subtype declarations are allowed for task types as  for  other  types,  but
there are no constraints applicable to task types.

References:   access  type  3.8,  actual  parameter  6.4.1,  allocator 4.8,
assignment 5.2, component declaration 3.7,  composite  type  3.3,  constant
3.2.1,  constant  declaration  3.2.1,  constraint  3.3,  designate 3.8 9.1,


                                   9 - 7








elaboration 3.9, entry 9.5, equality operator 4.5.2, formal parameter  6.2,
formal   parameter   mode  6.2,  generic  actual  parameter  12.3,  generic
association 12.3, generic formal parameter 12.1, generic  formal  parameter
mode  12.1.1,  generic  unit  12, inequality operator 4.5.2, initialization
3.2.1, limited type 7.4.4, object 3.2, object declaration 3.2.1,  parameter
association  6.4,  private  part  7.2, private type 7.4, reserved word 2.9,
subcomponent 3.3, subprogram 6, subtype declaration 3.3.2, task  body  9.1,
type 3.3

















































                                   9 - 8








9.3  Task Execution - Task Activation


A  task body defines the execution of any task that is designated by a task
object of the corresponding task type.  The initial part of this  execution
is  called  the  activation  of  the  task  object,  and  also  that of the
designated task;  it consists of the elaboration of the  declarative  part,
if  any, of the task body.  The execution of different tasks, in particular
their activation, proceeds in parallel.

If an object declaration that declares a  task  object  occurs  immediately
within  a  declarative  part, then the activation of the task object starts
after the elaboration of the declarative part (that is, after  passing  the
reserved  word  begin following the declarative part);  similarly if such a
declaration  occurs  immediately  within  a  package   specification,   the
activation  starts  after  the  elaboration  of the declarative part of the
package body.  The same holds for the activation of a task object that is a
subcomponent of an object declared immediately within a declarative part or
package specification.  The first statement following the declarative  part
is  executed only after conclusion of the activation of these task objects.

Should an exception be raised by the activation of one of these tasks, that
task becomes a completed task (see 9.4);   other  tasks  are  not  directly
affected.   Should  one  of  these  tasks  thus become completed during its
activation, the exception TASKING_ERROR is raised upon  conclusion  of  the
activation  of  all  of  these  tasks  (whether  successfully or not);  the
exception is raised at  a  place  that  is  immediately  before  the  first
statement  following  the  declarative part (immediately after the reserved
word begin).  Should several of these tasks thus  become  completed  during
their activation, the exception TASKING_ERROR is raised only once.

Should  an  exception be raised by the elaboration of a declarative part or
package  specification,  then  any  task  that  is  created  (directly   or
indirectly)  by  this  elaboration  and  that  is not yet activated becomes
terminated and is therefore  never  activated  (see  section  9.4  for  the
definition of a terminated task).

For  the  above  rules,  in  any  package  body  without statements, a null
statement is assumed.  For any package without a package body, an  implicit
package  body  containing a single null statement is assumed.  If a package
without a package body is declared immediately within some program unit  or
block  statement,  the  implicit  package  body  occurs  at  the end of the
declarative part of the program unit or  block  statement;   if  there  are
several  such  packages,  the  order  of  the  implicit  package  bodies is
undefined.

A task object that is the object, or a subcomponent of the object,  created
by  the  evaluation  of  an allocator is activated by this evaluation.  The
activation starts after any initialization for the object  created  by  the
allocator;   if  several subcomponents are task objects, they are activated
in parallel.  The access value designating such an object  is  returned  by
the allocator only after the conclusion of these activations.

Should an exception be raised by the activation of one of these tasks, that
task  becomes  a  completed  task;   other tasks are not directly affected.


                                   9 - 9








Should one of these tasks thus become completed during its activation,  the
exception  TASKING_ERROR is raised upon conclusion of the activation of all
of these tasks (whether successfully or not);  the exception is  raised  at
the  place where the allocator is evaluated.  Should several of these tasks
thus become completed during their activation, the exception  TASKING_ERROR
is raised only once.

Should  an  exception be raised by the initialization of the object created
by an allocator (hence before  the  start  of  any  activation),  any  task
designated  by  a  subcomponent  of  this  object becomes terminated and is
therefore never activated.














































                                  9 - 10









Example:

    procedure P is
       A, B : RESOURCE;  --  elaborate the task objects A, B
       C    : RESOURCE;  --  elaborate the task object C
    begin
       --  the tasks A, B, C are activated in parallel before the first statement
       ...
    end;

Notes:

An entry of a task can be called before the task has  been  activated.   If
several  tasks  are  activated  in  parallel, the execution of any of these
tasks need not await the end of the activation  of the other tasks.  A task
may become completed during its activation either because of  an  exception
or because it is aborted (see 9.10).

References:   allocator  4.8,  completed  task  9.4,  declarative part 3.9,
elaboration 3.9, entry 9.5,  exception  11,  handling  an  exception  11.4,
package body 7.1, parallel execution 9, statement 5, subcomponent 3.3, task
body   9.1,   task  object  9.2,  task  termination  9.4,  task  type  9.1,
tasking_error exception 11.1




9.4  Task Dependence - Termination of Tasks


Each task depends on at least one master.  A master is a construct that  is
either  a  task,  a currently executing block statement or subprogram, or a
library package (a package declared within another program unit  is  not  a
master).   The  dependence  on  a  master  is  a  direct  dependence in the
following two cases:

(a)  The task designated by  a  task  object  that  is  the  object,  or  a
     subcomponent  of the object, created by the evaluation of an allocator
     depends on the master that elaborates the  corresponding  access  type
     definition.

(b)  The task designated by any other task object  depends  on  the  master
     whose execution creates the task object.

Furthermore,  if a task depends on a given master that is a block statement
executed by another master, then  the  task  depends  also  on  this  other
master,  in  an  indirect  manner;  the same holds if the given master is a
subprogram called by another master, and if the given master is a task that
depends (directly or indirectly) on another master.  Dependences exist  for
objects  of  a  private  type  whose full declaration is in terms of a task
type.

A task is said to have completed its execution when  it  has  finished  the
execution  of  the  sequence  of statements that appears after the reserved


                                  9 - 11








word  begin in the corresponding body.  Similarly a block or  a  subprogram
is  said to have completed its execution when it has finished the execution
of the corresponding sequence of statements.  For a  block  statement,  the
execution  is also said to be completed when it reaches an exit, return, or
goto statement transferring control out of the block.  For a procedure, the
execution is  also  said  to  be  completed  when  a  corresponding  return
statement  is  reached.   For  a function, the execution is also said to be
completed after the  evaluation  of  the  result  expression  of  a  return
statement.  Finally the execution of a task, block statement, or subprogram
is  completed if an exception is raised by the execution of its sequence of
statements and there is no corresponding handler, or, if there is one, when
it has finished the execution of the corresponding handler.













































                                  9 - 12









If a task has no dependent task, its termination takes place  when  it  has
completed  its  execution.   After  its  termination,  a task is said to be
terminated.  If a task has dependent tasks,  its  termination  takes  place
when  the  execution  of  the task is completed and all dependent tasks are
terminated.  A block  statement  or  subprogram  body  whose  execution  is
completed is not left until all of its dependent tasks are terminated.

Termination  of  a  task otherwise takes place if and only if its execution
has reached an open  terminate  alternative  in  a  select  statement  (see
9.7.1), and the following conditions are satisfied:

  -  The task depends on some master whose execution  is  completed  (hence
     not a library package).

  -  Each task that depends on the  master  considered  is  either  already
     terminated  or similarly waiting on an open terminate alternative of a
     select statement.

When both conditions are satisfied, the task considered becomes terminated,
together with all tasks that depend on the master considered.

Example:

    declare
       type GLOBAL is access RESOURCE;          --  see 9.1
       A, B : RESOURCE;
       G    : GLOBAL;
    begin
       --  activation of A and B
       declare
          type LOCAL is access RESOURCE;
          X : GLOBAL := new RESOURCE;  --  activation of X.all
          L : LOCAL  := new RESOURCE;  --  activation of L.all
          C : RESOURCE;
       begin
          --  activation of C
          G := X;  --  both G and X designate the same task object
          ...
       end;  --  await termination of C and L.all (but not X.all)
       ...
    end;  --  await termination of A, B, and G.all

Notes:

The rules given for termination imply that all tasks that depend  (directly
or  indirectly)  on a given master and that are not already terminated, can
be terminated (collectively) if and only if each of them is waiting  on  an
open  terminate  alternative of a select statement and the execution of the
given master is completed.

The usual rules apply to the main program.   Consequently,  termination  of
the  main  program  awaits  termination  of  any dependent task even if the
corresponding task type is declared in a library  package.   On  the  other
hand,  termination  of the main program does not await termination of tasks


                                  9 - 13








that depend on library packages;  the language does not define whether such
tasks are required to terminate.

For an access type derived from  another  access  type,  the  corresponding
access  type  definition  is that of the parent type;  the dependence is on
the master that elaborates the ultimate parent access type definition.



















































                                  9 - 14









A renaming declaration defines a new name for an existing entity and  hence
creates no further dependence.

References:    access   type  3.8,  allocator  4.8,  block  statement  5.6,
declaration 3.1, designate 3.8 9.1, exception 11, exception  handler  11.2,
exit  statement  5.7,  function 6.5, goto statement 5.9, library unit 10.1,
main program 10.1, object 3.2, open alternative 9.7.1, package  7,  program
unit  6,  renaming  declaration  8.5,  return statement 5.8, selective wait
9.7.1,  sequence  of  statements  5.1,  statement  5,   subcomponent   3.3,
subprogram  body  6.3, subprogram call 6.4, task body 9.1, task object 9.2,
terminate alternative 9.7.1




9.5  Entries, Entry Calls, and Accept Statements


Entry calls and accept statements are the primary means of  synchronization
of  tasks, and of communicating values between tasks.  An entry declaration
is similar to a subprogram declaration  and  is  only  allowed  in  a  task
specification.   The  actions  to  be performed when an entry is called are
specified by corresponding accept statements.

    entry_declaration ::=
       entry identifier [(discrete_range)] [formal_part];

    entry_call_statement ::= entry_name [actual_parameter_part];

    accept_statement ::=
       accept entry_simple_name [(entry_index)] [formal_part] [do
          sequence_of_statements
       end [entry_simple_name]];

    entry_index ::= expression

An entry declaration that includes a discrete range (see 3.6.1) declares  a
family  of distinct entries having the same formal part (if any);  that is,
one such entry for each value of the discrete range.  The term single entry
is used in the definition of any rule that applies to any entry other  than
one  of  a family.  The task designated by an object of a task type has (or
owns) the entries declared in the specification of the task type.

Within the body of a task, each of its single entries or entry families can
be named by the corresponding simple name.  The  name  of  an  entry  of  a
family takes the form of an indexed component, the family simple name being
followed  by  the index in parentheses;  the type of this index must be the
same as that of the  discrete  range  in  the  corresponding  entry  family
declaration.   Outside  the  body of a task an entry name has the form of a
selected component,  whose  prefix  denotes  the  task  object,  and  whose
selector is the simple name of one of its single entries or entry families.

A  single  entry overloads a subprogram, an enumeration literal, or another
single entry if they have the same identifier.  Overloading is not  defined


                                  9 - 15








for  entry  families.  A single entry or an entry of an entry family can be
renamed as a procedure as explained in section 8.5.

The parameter modes defined for parameters of the formal part of  an  entry
declaration  are the same as for a subprogram declaration and have the same
meaning (see 6.2).  The syntax of an entry call  statement  is  similar  to
that   of   a  procedure  call  statement,  and  the  rules  for  parameter
associations are the same as for subprogram calls (see 6.4.1 and 6.4.2).

















































                                  9 - 16









An accept statement specifies the actions to be performed at a  call  of  a
named entry (it can be an entry of a family).  The formal part of an accept
statement  must  conform to the formal part given in the declaration of the
single entry or entry family named by the  accept  statement  (see  section
6.3.1  for  the conformance rules).  If a simple name appears at the end of
an accept statement, it must repeat that given at the start.

An accept statement for an entry of a given task is only allowed within the
corresponding task body;  excluding within the body  of  any  program  unit
that  is,  itself,  inner  to  the task body;  and excluding within another
accept statement for either the same single entry or an entry of  the  same
family.   (One  consequence  of this rule is that a task can execute accept
statements only for its own entries.)  A task body can  contain  more  than
one accept statement for the same entry.

For the elaboration of an entry declaration, the discrete range, if any, is
evaluated  and  the  formal  part,  if  any,  is  then  elaborated as for a
subprogram declaration.

Execution of an accept statement starts with the evaluation  of  the  entry
index  (in  the  case of an entry of a family).  Execution of an entry call
statement starts with the evaluation of the entry name;  this  is  followed
by any evaluations required for actual parameters in the same manner as for
a  subprogram call (see 6.4).  Further execution of an accept statement and
of a corresponding entry call statement are synchronized.

If a given entry is called by only one task, there are  two  possibilities:

  -  If  the  calling  task  issues  an  entry  call  statement  before   a
     corresponding  accept  statement  is  reached  by  the task owning the
     entry, the execution of the calling task is suspended.

  -  If a task reaches an accept statement prior to any call of that entry,
     the execution of the task is suspended until such a call is  received.

When an entry has been called and a corresponding accept statement has been
reached,  the  sequence  of  statements, if any, of the accept statement is
executed by the called task (while the  calling  task  remains  suspended).
This  interaction is called a rendezvous.  Thereafter, the calling task and
the task owning the entry continue their execution in parallel.

If several  tasks  call  the  same  entry  before  a  corresponding  accept
statement  is reached, the calls are queued;  there is one queue associated
with each entry.  Each execution of an accept statement  removes  one  call
from the queue.  The calls are processed in the order of arrival.

An  attempt  to  call  an  entry of a task that has completed its execution
raises the exception TASKING_ERROR at the point of the call, in the calling
task;  similarly, this exception is raised at the point of the call if  the
called  task  completes  its  execution before accepting the call (see also
9.10 for the case when the called task becomes  abnormal).   The  exception
CONSTRAINT_ERROR  is  raised  if  the  index of an entry of a family is not
within the specified discrete range.



                                  9 - 17








Examples of entry declarations:

    entry READ(V : out ITEM);
    entry SEIZE;
    entry REQUEST(LEVEL)(D : ITEM);  --  a family of entries

Examples of entry calls:

    CONTROL.RELEASE;                      --  see 9.2 and 9.1
    PRODUCER_CONSUMER.WRITE(E);           --  see 9.1
    POOL(5).READ(NEXT_CHAR);              --  see 9.2 and 9.1
    CONTROLLER.REQUEST(LOW)(SOME_ITEM);   --  see 9.1













































                                  9 - 18








Examples of accept statements:

    accept SEIZE;

    accept READ(V : out ITEM) do
       V := LOCAL_ITEM;
    end READ;

    accept REQUEST(LOW)(D : ITEM) do
       ...
    end REQUEST;

Notes:

The formal part given in an accept statement is not elaborated; it is  only
used to identify the corresponding entry.

An  accept  statement   can  call  subprograms  that issue entry calls.  An
accept statement need not  have  a  sequence  of  statements  even  if  the
corresponding  entry  has  parameters.   Equally, it can have a sequence of
statements even if the corresponding entry has no parameters.  The sequence
of statements of an accept statement can include return statements.  A task
can call its own entries but it will, of course,  deadlock.   The  language
permits  conditional  and  timed  entry  calls  (see 9.7.2 and 9.7.3).  The
language rules ensure that a task can only be in one entry queue at a given
time.

If the bounds of  the  discrete  range  of  an  entry  family  are  integer
literals,  the  index (in an entry name or accept statement) must be of the
predefined type INTEGER (see 3.6.1).

References:  abnormal task 9.10, actual parameter part 6.4, completed  task
9.4,    conditional    entry   call   9.7.2,   conformance   rules   6.3.1,
constraint_error exception  11.1,  designate  9.1,  discrete  range  3.6.1,
elaboration  3.1 3.9, enumeration literal 3.5.1, evaluation 4.5, expression
4.4, formal part 6.1, identifier 2.3, indexed component 4.1.1, integer type
3.5.4, name 4.1, object 3.2, overloading 6.6  8.7,  parallel  execution  9,
prefix  4.1,  procedure  6,  procedure  call 6.4, renaming declaration 8.5,
return statement 5.8, scope 8.2, selected component 4.1.3, selector  4.1.3,
sequence  of  statements  5.1,  simple  expression  4.4,  simple  name 4.1,
subprogram 6, subprogram body 6.3, subprogram declaration 6.1, task 9, task
body 9.1, task specification 9.1, tasking_error exception 11.1, timed entry
call 9.7.3



9.6  Delay Statements, Duration, and Time


The execution of a delay statement evaluates  the  simple  expression,  and
suspends  further  execution of the task that executes the delay statement,
for at least the duration specified by the resulting value.

    delay_statement ::= delay simple_expression;



                                  9 - 19








The simple expression must be of the predefined fixed point type  DURATION;
its value is expressed in seconds;  a delay statement with a negative value
is equivalent to a delay statement with a zero value.

Any  implementation  of  the  type  DURATION  must  allow representation of
durations (both positive and negative) up to at least  86400  seconds  (one
day);   the  smallest  representable  duration,  DURATION'SMALL must not be
greater than twenty milliseconds (whenever possible, a  value  not  greater
than  fifty  microseconds should be chosen).  Note that DURATION'SMALL need
not correspond to the basic clock cycle, the named number SYSTEM.TICK  (see
13.7).














































                                  9 - 20









The  definition  of  the  type  TIME  is provided in the predefined library
package CALENDAR.  The function CLOCK returns the current value of TIME  at
the  time  it is called.  The functions YEAR, MONTH, DAY and SECONDS return
the corresponding values for a given value of the type TIME;  the procedure
SPLIT returns all four  corresponding  values.   Conversely,  the  function
TIME_OF  combines  a  year  number,  a  month  number,  a day number, and a
duration, into a value of  type  TIME.   The  operators  "+"  and  "-"  for
addition  and  subtraction  of  times  and  durations,  and  the relational
operators for times, have the conventional meaning.

The exception TIME_ERROR is raised by the function TIME_OF  if  the  actual
parameters do not form a proper date.  This exception is also raised by the
operators  "+"  and  "-" if, for the given operands, these operators cannot
return a date whose year number  is  in  the  range  of  the  corresponding
subtype, or if the operator "-" cannot return a result that is in the range
of the type DURATION.

    package CALENDAR is
       type TIME is private;

       subtype YEAR_NUMBER  is INTEGER  range 1901 .. 2099;
       subtype MONTH_NUMBER is INTEGER  range 1 .. 12;
       subtype DAY_NUMBER   is INTEGER  range 1 .. 31;
       subtype DAY_DURATION is DURATION range 0.0 .. 86_400.0;

       function CLOCK return TIME;

       function YEAR   (DATE : TIME) return YEAR_NUMBER;
       function MONTH  (DATE : TIME) return MONTH_NUMBER;
       function DAY    (DATE : TIME) return DAY_NUMBER;
       function SECONDS(DATE : TIME) return DAY_DURATION;

       procedure SPLIT (DATE    : in  TIME;
                        YEAR    : out YEAR_NUMBER;
                        MONTH   : out MONTH_NUMBER;
                        DAY     : out DAY_NUMBER;
                        SECONDS : out DAY_DURATION);

       function TIME_OF(YEAR    : YEAR_NUMBER;
                        MONTH   : MONTH_NUMBER;
                        DAY     : DAY_NUMBER;
                        SECONDS : DAY_DURATION := 0.0) return TIME;


       function "+"  (LEFT : TIME;     RIGHT : DURATION) return TIME;
       function "+"  (LEFT : DURATION; RIGHT : TIME)     return TIME;
       function "-"  (LEFT : TIME;     RIGHT : DURATION) return TIME;
       function "-"  (LEFT : TIME;     RIGHT : TIME)     return DURATION;

       function "<"  (LEFT, RIGHT : TIME) return BOOLEAN;
       function "<=" (LEFT, RIGHT : TIME) return BOOLEAN;
       function ">"  (LEFT, RIGHT : TIME) return BOOLEAN;
       function ">=" (LEFT, RIGHT : TIME) return BOOLEAN;



                                  9 - 21








       TIME_ERROR : exception;  --  can be raised by TIME_OF, "+", and "-"

    private
       -- implementation-dependent
    end;




















































                                  9 - 22








Examples:

    delay 3.0;  --  delay 3.0 seconds

    declare
       use CALENDAR;
       --  INTERVAL is a global constant of type DURATION
       NEXT_TIME : TIME := CLOCK + INTERVAL;
    begin
       loop
          delay NEXT_TIME - CLOCK;
          --  some actions
          NEXT_TIME := NEXT_TIME + INTERVAL;
       end loop;
    end;

Notes:

The second example causes the loop to be repeated every INTERVAL seconds on
average.    This   interval  between  two  successive  iterations  is  only
approximate.  However, there will be no cumulative drift  as  long  as  the
duration of each iteration is (sufficiently) less than INTERVAL.


References:   adding  operator  4.5,  duration  C,  fixed point type 3.5.9,
function call 6.4, library unit 10.1, operator 4.5, package 7, private type
7.4, relational operator 4.5, simple expression 4.4, statement 5,  task  9,
type 3.3




9.7  Select Statements


There  are three forms of select statements.  One form provides a selective
wait for one or more alternatives.  The other two provide  conditional  and
timed entry calls.

    select_statement ::= selective_wait
       | conditional_entry_call | timed_entry_call


References:   selective  wait  9.7.1,  conditional  entry call 9.7.2, timed
entry call 9.7.3




9.7.1  Selective Waits


This form of the select statement allows a combination of waiting for,  and
selecting  from,  one  or  more  alternatives.  The selection can depend on
conditions associated with each alternative of the selective wait.


                                  9 - 23








    selective_wait ::=
        select
          select_alternative
       {or
          select_alternative}
       [else
          sequence_of_statements]
        end select;

    select_alternative ::=
       [when condition =>]
          selective_wait_alternative

    selective_wait_alternative ::= accept_alternative
       | delay_alternative | terminate_alternative

    accept_alternative ::= accept_statement [sequence_of_statements]

    delay_alternative  ::= delay_statement  [sequence_of_statements]

    terminate_alternative ::= terminate;

A selective wait must contain at least one accept alternative.  In addition
a selective wait can contain either a terminate alternative (only one),  or
one or more delay alternatives, or an else part;  these three possibilities
are mutually exclusive.

A  select alternative is said to be open if it does not start with when and
a condition, or if the  condition  is  TRUE.   It  is  said  to  be  closed
otherwise.

For  the execution of a selective wait, any conditions specified after when
are evaluated in some order that is not  defined  by  the  language;   open
alternatives are thus determined.  For an open delay alternative, the delay
expression  is  also  evaluated.  Similarly, for an open accept alternative
for an entry of a family, the entry index is also evaluated.  Selection and
execution of one open alternative, or of the else part, then completes  the
execution  of  the  selective  wait;   the  rules  for  this  selection are
described below.

Open accept alternatives are  first  considered.   Selection  of  one  such
alternative  takes  place  immediately  if  a  corresponding  rendezvous is
possible, that is, if there is a corresponding entry call issued by another
task and waiting to be accepted.   If  several  alternatives  can  thus  be
selected,  one  of them is selected arbitrarily (that is, the language does
not  define  which  one).   When  such  an  alternative  is  selected,  the
corresponding  accept  statement  and  possible  subsequent  statements are
executed.  If no rendezvous is immediately possible and there  is  no  else
part,  the  task  waits  until  an  open  selective wait alternative can be
selected.

Selection of the other forms of alternative or of an else part is performed
as follows:




                                  9 - 24








  -  An open delay alternative will be selected if  no  accept  alternative
     can  be  selected before the specified delay has elapsed (immediately,
     for a negative or zero delay in the absence of  queued  entry  calls);
     any  subsequent  statements  of the alternative are then executed.  If
     several delay alternatives can thus be selected (that is, if they have
     the same delay), one of them is selected arbitrarily.

  -  The else part is selected and its statements are executed if no accept
     alternative  can  be  immediately  selected,  in  particular,  if  all
     alternatives are closed.

  -  An open terminate alternative is selected if the conditions stated  in
     section  9.4 are satisfied.  It is a consequence of other rules that a
     terminate alternative cannot be selected while there is a queued entry
     call for any entry of the task.










































                                  9 - 25








The exception PROGRAM_ERROR is raised if all alternatives  are  closed  and
there is no else part.

Examples of a select statement:

    select
       accept DRIVER_AWAKE_SIGNAL;
    or
       delay 30.0*SECONDS;
       STOP_THE_TRAIN;
    end select;

Example of a task body with a select statement:

    task body RESOURCE is
       BUSY : BOOLEAN := FALSE;
    begin
       loop
          select
             when not BUSY =>
                accept SEIZE do
                   BUSY := TRUE;
                end;
          or
             accept RELEASE do
                BUSY := FALSE;
             end;
          or
             terminate;
          end select;
       end loop;
    end RESOURCE;

Notes:

A  selective  wait  is  allowed to have several open delay alternatives.  A
selective wait is allowed to have several open accept alternatives for  the
same entry.

References:   accept  statement  9.5, condition 5.3, declaration 3.1, delay
expression 9.6, delay statement 9.6, duration 9.6, entry  9.5,  entry  call
9.5,  entry index 9.5, program_error exception 11.1, queued entry call 9.5,
rendezvous 9.5, select statement 9.7, sequence of statements 5.1, task 9




9.7.2  Conditional Entry Calls


A conditional entry call issues an entry call that is then  canceled  if  a
rendezvous is not immediately possible.

    conditional_entry_call ::=
       select


                                  9 - 26








           entry_call_statement
          [sequence_of_statements]
       else
           sequence_of_statements
       end select;




















































                                  9 - 27








For  the  execution  of  a  conditional entry call, the entry name is first
evaluated.  This  is  followed  by  any  evaluations  required  for  actual
parameters as in the case of a subprogram call (see 6.4).

The  entry  call  is  canceled  if the execution of the called task has not
reached a point where it is ready to accept the call (that  is,  either  an
accept statement for the corresponding entry, or a select statement with an
open  accept alternative for the entry), or if there are prior queued entry
calls for this entry.  If the called task has reached a  select  statement,
the  entry  call is canceled if an accept alternative for this entry is not
selected.

If the entry call  is  canceled,  the  statements  of  the  else  part  are
executed.   Otherwise,  the  rendezvous  takes  place;   and  the  optional
sequence of statements after the entry call is then executed.

The  execution  of  a  conditional  entry   call   raises   the   exception
TASKING_ERROR  if  the called task has already completed its execution (see
also 9.10 for the case when the called task becomes abnormal).

Example:

    procedure SPIN(R : RESOURCE) is
    begin
       loop
          select
             R.SEIZE;
             return;
          else
             null;  --  busy waiting
          end select;
       end loop;
    end;

References:  abnormal task 9.10, accept  statement  9.5,  actual  parameter
part  6.4,  completed task 9.4, entry call statement 9.5, entry family 9.5,
entry index 9.5, evaluation 4.5, expression 4.4,  open  alternative  9.7.1,
queued  entry  call  9.5, rendezvous 9.5, select statement 9.7, sequence of
statements 5.1, task 9, tasking_error exception 11.1




9.7.3  Timed Entry Calls


A timed entry call issues an entry call that is canceled if a rendezvous is
not started within a given delay.

    timed_entry_call ::=
       select
           entry_call_statement
          [sequence_of_statements]
       or
           delay_alternative


                                  9 - 28








       end select;
























































                                  9 - 29








For the execution of a timed entry call, the entry name is first evaluated.
This is followed by any evaluations required for actual  parameters  as  in
the  case of a subprogram call (see 6.4).  The expression stating the delay
is then evaluated, and the entry call is finally issued.

If  a  rendezvous  can  be  started  within  the  specified  duration   (or
immediately,  as  for  a  conditional  entry  call,  for a negative or zero
delay), it is performed and the optional sequence of statements  after  the
entry  call  is  then executed.  Otherwise, the entry call is canceled when
the specified duration has expired, and the optional sequence of statements
of the delay alternative is executed.

The execution of a timed entry call raises the exception  TASKING_ERROR  if
the called task completes its execution before accepting the call (see also
9.10 for the case when the called task becomes abnormal).

Example:

    select
       CONTROLLER.REQUEST(MEDIUM)(SOME_ITEM);
    or
       delay 45.0;
       --  controller too busy, try something else
    end select;

References:   abnormal  task  9.10,  accept statement 9.5, actual parameter
part  6.4,  completed  task  9.4,  conditional  entry  call  9.7.2,   delay
expression  9.6,  delay  statement  9.6, duration 9.6, entry call statement
9.5, entry family 9.5, entry index 9.5,  evaluation  4.5,  expression  4.4,
rendezvous 9.5, sequence of statements 5.1, task 9, tasking_error exception
11.1




9.8  Priorities


Each  task  may  (but  need  not)  have a priority, which is a value of the
subtype PRIORITY (of the type INTEGER) declared in the  predefined  library
package  SYSTEM  (see  13.7).   A  lower  value indicates a lower degree of
urgency;  the range of priorities is implementation-defined.  A priority is
associated with a task if a pragma

    pragma PRIORITY (static_expression);

appears in the corresponding task specification;  the priority is given  by
the  value  of  the  expression.   A  priority  is associated with the main
program if such a pragma appears in its  outermost  declarative  part.   At
most  one such pragma can appear within a given task specification or for a
subprogram that is a library unit, and these are the  only  allowed  places
for  this  pragma.   A  pragma  PRIORITY  has  no  effect if it occurs in a
subprogram other than the main program.




                                  9 - 30








The specification of a priority  is  an  indication  given  to  assist  the
implementation  in the allocation of processing resources to parallel tasks
when there are more tasks eligible for  execution  than  can  be  supported
simultaneously  by  the  available  processing  resources.   The  effect of
priorities on scheduling is defined by the following rule:

     If two tasks with different priorities are both eligible for execution
     and could sensibly be executed using the same physical processors  and
     the  same  other processing resources, then it cannot be the case that
     the task with the lower priority is executing while the task with  the
     higher priority is not.














































                                  9 - 31








For  tasks of the same priority, the scheduling order is not defined by the
language.  For tasks without explicit priority, the  scheduling  rules  are
not  defined,  except  when such tasks are engaged in a rendezvous.  If the
priorities  of  both  tasks  engaged  in  a  rendezvous  are  defined,  the
rendezvous  is executed with the higher of the two priorities.  If only one
of the two priorities is defined, the rendezvous is executed with at  least
that  priority.   If  neither is defined, the priority of the rendezvous is
undefined.

Notes:

The priority of a  task  is  static  and  therefore  fixed.   However,  the
priority  during  a  rendezvous  is  not  necessarily  static since it also
depends on the priority of the task calling the entry.   Priorities  should
be  used  only to indicate relative degrees of urgency;  they should not be
used for task synchronization.

References:  declarative part 3.9, entry call statement 9.5,  integer  type
3.5.4,  main program 10.1, package system 13.7, pragma 2.8, rendezvous 9.5,
static expression 4.9, subtype 3.3, task 9, task specification 9.1




9.9  Task and Entry Attributes


For a task object or value T the following attributes are defined:

T'CALLABLE    Yields the  value  FALSE  when  the  execution  of  the  task
              designated  by  T  is either completed or terminated, or when
              the task is abnormal.  Yields the value TRUE otherwise.   The
              value of this attribute is of the predefined type BOOLEAN.

T'TERMINATED  Yields the  value  TRUE  if  the  task  designated  by  T  is
              terminated.   Yields the value FALSE otherwise.  The value of
              this attribute is of the predefined type BOOLEAN.

In addition, the representation attributes STORAGE_SIZE, SIZE, and  ADDRESS
are defined for a task object T or a task type T (see 13.7.2).

The  attribute COUNT is defined for an entry E of a task unit T.  The entry
can be either a single entry or an entry of a family (in  either  case  the
name  of  the  single  entry  or  entry family can be either a simple or an
expanded name).  This attribute is only allowed within the body of  T,  but
excluding  within any program unit that is, itself, inner to the body of T.

E'COUNT       Yields the number of entry  calls  presently  queued  on  the
              entry E (if the attribute is evaluated by the execution of an
              accept  statement for the entry E, the count does not include
              the calling task).  The value of this  attribute  is  of  the
              type universal_integer.

Note:



                                  9 - 32








Algorithms  interrogating  the attribute E'COUNT should take precautions to
allow for the increase of the value of this attribute  for  incoming  entry
calls, and its decrease, for example with timed entry calls.

References:   abnormal  task  9.10,  accept statement 9.5, attribute 4.1.4,
boolean type 3.5.3, completed task 9.4, designate  9.1,  entry  9.5,  false
boolean  value  3.5.3, queue of entry calls 9.5, storage unit 13.7, task 9,
task object 9.2, task type 9.1,  terminated  task  9.4,  timed  entry  call
9.7.3, true boolean value 3.5.3, universal_integer type 3.5.4
















































                                  9 - 33








9.10  Abort Statements


An  abort  statement  causes  one  or  more  tasks to become abnormal, thus
preventing any further rendezvous with such tasks.

    abort_statement ::= abort task_name {, task_name};

The determination of the type of each task name uses the fact that the type
of the name is a task type.

For the execution of an abort statement, the given task names are evaluated
in some order that is not defined by the language.  Each  named  task  then
becomes abnormal unless it is already terminated;  similarly, any task that
depends  on  a named task becomes abnormal unless it is already terminated.

Any abnormal task whose execution is suspended at an  accept  statement,  a
select  statement,  or  a  delay statement becomes completed;  any abnormal
task whose execution is suspended at an entry call, and that is not yet  in
a corresponding rendezvous, becomes completed and is removed from the entry
queue;   any  abnormal task that has not yet started its activation becomes
completed (and hence also terminated).  This completes the execution of the
abort statement.

The completion of any other abnormal task need not happen before completion
of the abort statement.  It must happen no later  than  when  the  abnormal
task  reaches a synchronization point that is one of the following: the end
of its activation;  a point where it causes the activation of another task;
an entry call; the start or the end  of  an  accept  statement;   a  select
statement;   a  delay  statement;   an  exception  handler;   or  an  abort
statement.  If a task that calls an  entry  becomes  abnormal  while  in  a
rendezvous,  its  termination  does not take place before the completion of
the rendezvous (see 11.5).

The call of an entry of an abnormal task raises the exception TASKING_ERROR
at the place of the call.  Similarly, the exception TASKING_ERROR is raised
for any task that has called an entry of an abnormal  task,  if  the  entry
call  is still queued or if the rendezvous is not yet finished (whether the
entry call is an entry call statement, or  a  conditional  or  timed  entry
call);   the  exception  is  raised  no  later  than  the completion of the
abnormal task.  The value of the attribute CALLABLE is FALSE for  any  task
that is abnormal (or completed).

If  the  abnormal completion of a task takes place while the task updates a
variable, then the value of this variable is undefined.  Example:

    abort USER, TERMINAL.all, POOL(3);

Notes:

An abort statement should be  used  only  in  extremely  severe  situations
requiring  unconditional termination.  A task is allowed to abort any task,
including itself.




                                  9 - 34








References:  abnormal in rendezvous 11.5, accept statement 9.5,  activation
9.3,  attribute  4.1.4,  callable  (predefined  attribute) 9.9, conditional
entry call 9.7.2, delay statement  9.6,  dependent  task  9.4,  entry  call
statement  9.5,  evaluation  of  a  name 4.1, exception handler 11.2, false
boolean value 3.5.3, name 4.1, queue of entry calls  9.5,  rendezvous  9.5,
select  statement  9.7,  statement 5, task 9, tasking_error exception 11.1,
terminated task 9.4, timed entry call 9.7.3


















































                                  9 - 35








9.11  Shared Variables


The normal means of communicating values between tasks is  by  entry  calls
and accept statements.

If  two  tasks  read  or  update  a  shared  variable  (that is, a variable
accessible by both), then neither of them may  assume  anything  about  the
order  in  which  the  other  performs its operations, except at the points
where they synchronize.  Two tasks are synchronized at the start and at the
end of their rendezvous.  At the start and at the end of its activation,  a
task  is  synchronized  with  the task that causes this activation.  A task
that has completed its execution is synchronized with any other task.

For the actions performed by a program  that  uses  shared  variables,  the
following assumptions can always be made:

  -  If between two synchronization points of a task,  this  task  reads  a
     shared  variable  whose  type  is  a  scalar  or access type, then the
     variable is not updated by any other task at any  time  between  these
     two points.

  -  If between two synchronization points of a task, this task  updates  a
     shared  variable  whose  type  is  a  scalar  or access type, then the
     variable is neither read nor updated by any other  task  at  any  time
     between these two points.

The  execution  of  the program is erroneous if any of these assumptions is
violated.

If a given task reads the value of a shared variable, the above assumptions
allow an implementation to maintain local copies of the value (for example,
in registers or in some other form of temporary storage);  and for as  long
as  the  given task neither reaches a synchronization point nor updates the
value of the shared variable, the above assumptions  imply  that,  for  the
given  task,  reading  a  local  copy  is  equivalent to reading the shared
variable itself.

Similarly, if a given task updates the value  of  a  shared  variable,  the
above  assumptions  allow an implementation to maintain a local copy of the
value, and to defer the effective store of the local copy into  the  shared
variable until a synchronization point, provided that every further read or
update  of the variable by the given task is treated as a read or update of
the local copy.  On the other hand, an implementation  is  not  allowed  to
introduce  a  store,  unless  this  store  would  also  be  executed in the
canonical order (see 11.6).

The pragma SHARED can be used to specify that every read  or  update  of  a
variable  is a synchronization point for that variable;  that is, the above
assumptions always hold for the given variable  (but  not  necessarily  for
other variables).  The form of this pragma is as follows:

    pragma SHARED(variable_simple_name);




                                  9 - 36








This  pragma  is  allowed  only  for  a  variable  declared  by  an  object
declaration and whose type is  a  scalar  or  access  type;   the  variable
declaration  and  the  pragma  must  both occur (in this order) immediately
within the same declarative part or package specification;  the pragma must
appear before any occurrence of the name of the variable, other than in  an
address clause.

An  implementation must restrict the objects for which the pragma SHARED is
allowed to objects for which each of direct reading and direct updating  is
implemented as an indivisible operation.

References:    accept   statement  9.5,  activation  9.3,  assignment  5.2,
canonical order 11.6, declarative  part  3.9,  entry  call  statement  9.5,
erroneous  1.6,  global  8.1, package specification 7.1, pragma 2.8, read a
value 6.2, rendezvous 9.5, simple name 3.1 4.1, task 9, type 3.3, update  a
value 6.2, variable 3.2.1









































                                  9 - 37








9.12  Example of Tasking


The following example defines a buffering task to smooth variations between
the  speed  of  output  of  a producing task and the speed of input of some
consuming  task.   For  instance,  the  producing  task  may  contain   the
statements

    loop
       --  produce the next character CHAR
       BUFFER.WRITE(CHAR);
       exit when CHAR = ASCII.EOT;
    end loop;

and the consuming task may contain the statements

    loop
       BUFFER.READ(CHAR);
       --  consume the character CHAR
       exit when CHAR = ASCII.EOT;
    end loop;

The  buffering  task contains an internal pool of characters processed in a
round-robin fashion.  The pool has two indices, an  IN_INDEX  denoting  the
space  for the next input character and an OUT_INDEX denoting the space for
the next output character.

    task BUFFER is
       entry READ (C : out CHARACTER);
       entry WRITE(C : in  CHARACTER);
    end;

    task body BUFFER is
       POOL_SIZE : constant INTEGER := 100;
       POOL      : array(1 .. POOL_SIZE) of CHARACTER;
       COUNT     : INTEGER range 0 .. POOL_SIZE := 0;
       IN_INDEX, OUT_INDEX : INTEGER range 1 .. POOL_SIZE := 1;
    begin
       loop
          select
             when COUNT < POOL_SIZE =>
                accept WRITE(C : in CHARACTER) do
                   POOL(IN_INDEX) := C;
                end;
                IN_INDEX := IN_INDEX mod POOL_SIZE + 1;
                COUNT    := COUNT + 1;
          or when COUNT > 0 =>
                accept READ(C : out CHARACTER) do
                   C := POOL(OUT_INDEX);
                end;
                OUT_INDEX := OUT_INDEX mod POOL_SIZE + 1;
                COUNT     := COUNT - 1;
          or
             terminate;
          end select;


                                  9 - 38








       end loop;
    end BUFFER;























































                                  9 - 39