[comp.lang.ada] Deallocation of T'STORAGE_SIZE for a terminated task

prindle@NADC.ARPA (Frank Prindle) (09/21/88)

Given:

 1. An allocator may be used with a task type to dynamically activate a task as
 a consequence of 9.3(6).  The run-time system reserves <task_type>'STORAGE_SIZE
 storage units for use by this activation as stated in 13.7.2(14).

 2. The task thus activated may eventually become completed and terminate as a
 consequence of 9.4(5,6).

 3. The task object allocated is considered inaccessible once the task has
 terminated as a consequence of 4.8(7).

 4. An implementation may (but need not) reclaim the storage reserved for the
 task object once it has terminated, as stated in 4.8(7).

 5. Automatic deallocation, when available, may be selectively inhibited via
 pragma CONTROLLED as a consequence of 4.8(10).

 6. Explicit deallocation of storage reserved for a dynamically allocated object
 is provided by instantiation of UNCHECKED_DEALLOCATION, as stated in 4.8(12).

 7. An instantiation of UNCHECKED_DEALLOCATION for a task object is guaranteed
 to have ***NO*** effect on the task designated by the task object by
 13.10.1(8)!

Then:

 If an implementation does not provide for automatic storage deallocation, what
 mechanism allows the repeated execution of dynamically activated tasks without
 eventual consumption of the entire heap (or that partition reserved for
 task objects) and the subsequent irrecoverable STORAGE_ERROR?

Notice that everything made sense up through point 6 above.  The
implication was that one could not rely on an implementation to reclaim
dynamically allocated storage at the appropriate time, and should always
explicitly deallocate every object explicitly allocated (once it has been
determined that the object is no longer required).  It is further implied
that a simple test of the 'TERMINATED attribute of the task object would
suffice to determine when a task object (i.e. the stack for use by a task
activation) is no longer required.  The killer is 7 above.  When 13.10.1(8)
says "no effect on the task", does this imply that the storage is not to be
deallocated?  But that is the very job intended to be performed by this
generic library procedure, as stated in 4.8(12)!

I think we need a clarification of 13.10.1(8) here, because at least one
implementation has taken it to mean that the storage cannot be deallocated.
Therefore, dynamic activation of transient tasks (i.e. tasks which will
eventually terminate on their own) appears to be a lost cause.

It is quite possible that yet another section of the LRM clarifies this
question, but the index leads me only in an endless circle of the paragraphs
cited above.  Any pointers would be warmly welcomed.

Sincerely,
Frank Prindle
Prindle@NADC.arpa

NCOHEN@IBM.COM (Norman Cohen) (09/22/88)

Ref: INFO-ADA Digest Volume 88 Issue 200 (Thu, Sep 22, 1988) Item #2

Consider the following program:

     PROCEDURE Main IS

        TYPE Work_Item_Type IS ...;

        TASK TYPE Work_Processor_Type IS
           ENTRY Assign_Work (Work: IN Work_Item_Type);
        END Work_Processor_Type;

        TYPE Work_Processor_Pointer_Type IS ACCESS Work_Processor_Type;

        Work_Item          : Work_Item_Type;
        New_Work_Processor : Work_Processor_Pointer_Type;

        PROCEDURE Deallocate_Work_Processor IS
           NEW Unchecked_Deallocation
              (Work_Processor_Type, Work_Processor_Pointer_Type);

        TASK BODY Work_Processor_Type IS SEPARATE;
        PROCEDURE Wait_For_Work (Work_Item: OUT Work_Item_Type);

     BEGIN

        LOOP
           Wait_For_Work (Work_Item);
           New_Work_Processor := NEW Work_Processor_Type;
           New_Work_Processor.Assign_Work (Work_Item);
           Deallocate_Work_Processor (New_Work_Processor);
        END LOOP;

     END Main;

Work processors are assigned as work items arrive.  Several work
processors may be active simultaneously, asynchronously processing their
assigned work items.  The intent of the last statement in the loop is
to deallocate storage for each work-processor task when it completes its
work, so that the storage may be used for other work processors.

If the task allocated in the third statement of the loop completes its
work and terminates before the main program advances to the fourth
statement in the loop, we would expect the storage for the task object
to be reclaimed as for an allocated object of any other type.  However,
if (as is likely) the task is still active when the fourth statement of
the loop is reached, the call on Deallocate_Work_Processor
has no effect on the task.  (In particular, the task is not aborted so
that storage for the task object can be reclaimed.)  That is the point of
Reference Manual paragraph 3.10.1(8) (which is a note, not a rule).

There are several options available to the friendly designer of a run-
time system, and several recourses available to the user whose run-time-
system designer was not inclined to be friendly.

First, we must distinguish between two kinds of storage associated with
tasks, the task control block (data maintained by the run-time system
for such purposes as task scheduling and task communication) and the
task stack (used for application data local to the task body or local to
subprograms invoked by the task).  A typical implementation has a task
object corresponding to a task control block and a representation clause
for T'Storage_Size, where T is a task type, determining the size of the
stack allocated when a task of the type is activated.

Options available to the friendly designer of a run-time system include:

- Deallocating the task stack (Work_Procesor_Type'Storage_Size bytes)
  immediately upon task termination, even if the task control block
  (Work_Processor_Type'Size bits) must remain in existence for such
  purposes as evaluating the 'Terminated attribute or raising
  Tasking_Error in any other task that calls an entry of the terminated
  task.

- Implementing a task object as a pointer to a task control block, but
  representing the task object of a terminated task by a null pointer and
  deallocating storage for the task control block itself as soon as the
  task has terminated.

- Automatically reclaiming storage for allocated task objects as soon as
  the task has terminated, provided that the task object is no longer
  accessible by access values.  (This is a restricted form of incremental
  garbage collection in which reference counts can be used.)

- Having instances of Unchecked_Deallocation set a "deallocation pending"
  flag in the task control block if the task objects for which
  deallocation was requested designate still-active tasks.  Upon
  termination, the task control block would be deallocated if its
  "deallocation pending" flag were set.

Options available to the user of a run-time system whose designer was not
inclined to be friendly include the following:

- Introduce another task, of low-priority, to explicitly manage
  allocation and deallocation of task objects.  Thus the assignment

     New_Work_Processor := NEW Work_Processor_Type;

  in the program above would be replaced by an entry call on this new
  task:

     Work_Processor_Manager.Get_New_Task (New_Work_Processor);

  The low-priority task would maintain a list of currently allocated
  tasks and periodically query the 'Terminated attribute of tasks on the
  list, deallocating a task object and removing the pointer to that
  object from the list whenever the designated task is found to be
  terminated.

- If there is a known maximum on the number of work processors that will
  be active simultaneously, declare an array of that many
  Work_Processor_Type objects.  Add an entry, called once at program
  start-up, to tell a Work_Processor_Type task its index within this
  array.  Declare a list of indices of currently idle tasks, initialized
  to a list containing every task index.  Modify the Work_Processor_Type
  task body so that, rather than terminating when it is done with its
  work, it adds its index to the list of indices of idle tasks and loops
  back to its Assign_Work entry to wait for more work.  The assignment
  statement allocating a new task would be replaced by a statement
  removing the first idle-task index from the list, then calling the
  Assign_Work entry of the corresponding array element.

Norman Cohen
IBM Research