+ All Categories
Home > Documents > Task Dependence and Termination in Ada - CiteSeer

Task Dependence and Termination in Ada - CiteSeer

Date post: 18-Mar-2022
Category:
Upload: others
View: 0 times
Download: 0 times
Share this document with a friend
31
Task Dependence and Termination in Ada LAURA K. DILLON University of California, Santa Barbara This article analyzes the semantics of task dependence and termination in Ada. We use a contour model of Ada tasking in examining the implications of and possible motivation for the rules that determine when procedures and tasks terminate during execution of an Ada program. The termination rules prevent the data that belong to run-time instances of scope units from being deallocated prematurely, but they are unnecessarily conservative in this regard. For task instances that are created by invoking a storage allocator, we show that the conservative termination policy allows heap storage to be managed more efficiently than a less conservative policy. The article also examines the manner in which the termination rules affect the synchronization of concurrent tasks. Master-slave and client-server applications are considered. We show that the rules for distributed termination of concurrent tasks guarantee that a task terminates only if it can no longer affect the outcome of an execution. The article is meant to give programmers a better understanding of Ada tasking and to help language designers assess the strengths and weaknesses of the termination model. Categories and Subject Descriptors: D.3.2 [Programming Languages]: Language Classifica- tions—concurrent, distributed, and parallel languages; Ada; D.3.3 [Programming Lan- guages]: Language Constructs and Features—concurrent programming structures General Terms: Languages Additional Key Words and Phrases: Ada tasking, distributed termination, master/dependent relation, task termination, tasking execution model 1. INTRODUCTION The Ada programming language has seen increased use in applications with stringent reliability requirements as programmers have mastered the features it provides for abstraction and reuse. However, the tasking fea- tures of Ada still see relatively little use, especially in the kinds of critical applications for which tasking was designed. We believe one of the reasons that programmers are reluctant to use tasking in safety-critical applica- tions is that they find tasking difficult to learn and to understand. This research was partially supported by NSF/ARPA grant CCR-9014382 and NSF grant CCR-9505392. Author’s address: Computer Science Department, University of California, Santa Barbara, CA 93106; email: [email protected]. Permission to make digital / hard copy of part or all of this work for personal or classroom use is granted without fee provided that the copies are not made or distributed for profit or commercial advantage, the copyright notice, the title of the publication, and its date appear, and notice is given that copying is by permission of the ACM, Inc. To copy otherwise, to republish, to post on servers, or to redistribute to lists, requires prior specific permission and/or a fee. © 1997 ACM 1049-331X/97/0100 –0080 $03.50 ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997, Pages 80 –110.
Transcript

Task Dependence and Termination in Ada

LAURA K. DILLONUniversity of California, Santa Barbara

This article analyzes the semantics of task dependence and termination in Ada. We use acontour model of Ada tasking in examining the implications of and possible motivation for therules that determine when procedures and tasks terminate during execution of an Adaprogram. The termination rules prevent the data that belong to run-time instances of scopeunits from being deallocated prematurely, but they are unnecessarily conservative in thisregard. For task instances that are created by invoking a storage allocator, we show that theconservative termination policy allows heap storage to be managed more efficiently than a lessconservative policy. The article also examines the manner in which the termination rulesaffect the synchronization of concurrent tasks. Master-slave and client-server applications areconsidered. We show that the rules for distributed termination of concurrent tasks guaranteethat a task terminates only if it can no longer affect the outcome of an execution. The article ismeant to give programmers a better understanding of Ada tasking and to help languagedesigners assess the strengths and weaknesses of the termination model.

Categories and Subject Descriptors: D.3.2 [Programming Languages]: Language Classifica-tions—concurrent, distributed, and parallel languages; Ada; D.3.3 [Programming Lan-guages]: Language Constructs and Features—concurrent programming structures

General Terms: Languages

Additional Key Words and Phrases: Ada tasking, distributed termination, master/dependentrelation, task termination, tasking execution model

1. INTRODUCTION

The Ada programming language has seen increased use in applicationswith stringent reliability requirements as programmers have mastered thefeatures it provides for abstraction and reuse. However, the tasking fea-tures of Ada still see relatively little use, especially in the kinds of criticalapplications for which tasking was designed. We believe one of the reasonsthat programmers are reluctant to use tasking in safety-critical applica-tions is that they find tasking difficult to learn and to understand.

This research was partially supported by NSF/ARPA grant CCR-9014382 and NSF grantCCR-9505392.Author’s address: Computer Science Department, University of California, Santa Barbara, CA93106; email: [email protected] to make digital /hard copy of part or all of this work for personal or classroom useis granted without fee provided that the copies are not made or distributed for profit orcommercial advantage, the copyright notice, the title of the publication, and its date appear,and notice is given that copying is by permission of the ACM, Inc. To copy otherwise, torepublish, to post on servers, or to redistribute to lists, requires prior specific permissionand / or a fee.© 1997 ACM 1049-331X/97/0100–0080 $03.50

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997, Pages 80–110.

Laura K. Dillon
Text Box
© ACM, (1997). This is the author's version of the work. It is posted here by permission of ACM for your personal use. Not for redistribution. The definitive version was published in ACM TOSEM, http://doi.acm.org/10.1145/237432.237459

Concurrency increases the complexity of programming, making concur-rent programs harder to write and to reason about than sequential ones.The increase in complexity is reflected in proof systems for concurrentprogramming languages. The verification of a concurrent program requires“local proofs,” which establish correctness of the sequential components(processes) in the program; this step is roughly equivalent to proving that asequential program is correct. The verification of a concurrent program,however, also requires a “global proof,” which shows that processes syn-chronize and communicate properly during execution.1 The global proof iscombinatorial in nature, as it must account for all possible orders in whichactions performed by asynchronous processes can occur.In the case of Ada, however, we believe that peculiarities in the defini-

tions of some of the tasking primitives compound the problem of under-standing programs. The analysis that follows shows that the semantics oftask dependence and termination in Ada, which affects the manner inwhich tasks synchronize upon completing execution, is particularly confus-ing; seemingly arbitrary details in the definitions of these concepts makethem hard to motivate and to remember, thereby providing a likely sourceof programming errors.The Ada Language Reference Manual (ALRM) [United States Depart-

ment of Defense 1983] and other natural-language descriptions of taskingare subject to incompleteness and misinterpretation. Formal definitions ofAda tasking are precise, but difficult for ordinary programmers to under-stand. Moreover, such descriptions provide little help in visualizing theeffects of potential interactions among asynchronous tasks in a program.In Dillon [1993] we describe a visual execution model for Ada tasking

that is designed to help programmers understand the semantics of tasking.Derived from Johnston’s contour model for block-structured programminglanguages [Johnston 1971], it uses contour diagrams to illustrate theexecution of a tasking program in a concrete pictorial fashion. In thisarticle, we use the model to analyze the semantics of task dependence andtermination in Ada. The article clarifies the termination rules defined inthe ALRM and examines their implications and possible justification. Theanalysis is intended to give programmers a better understanding of Adatasking and to help language designers assess the strengths and weak-nesses of the termination model. The tasking execution model in Dillon[1993] was designed five years before Ada 95 [Intermetrics 1994]. However,because the task termination model of Ada has not changed, the analysis inthis article applies equally well to Ada 95.Section 2 discusses some related work, which helps establish the context

for the article. We introduce the contour model in Section 3, and we presentthe definitions and rules that govern the termination of Ada procedures

1 The global proof for a program in which processes share memory is called a proof of“noninterference” [Owicki and Gries 1976], and the global proof for a program in whichprocesses pass messages is called a proof of “cooperation” [Apt et al. 1980; Gerth and deRoever 1984].

Task Dependence and Termination in Ada • 81

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

and tasks in Section 4. The next two sections consider issues relating torun-time management of storage: Section 5 explains how Ada’s terminationrules affect the management of stack storage, and Section 6 explains howthey affect the management of heap storage. We consider issues relating tosynchronization of terminating procedures and tasks in the next twosections: Section 7 examines master-slave synchronization, and Section 8examines client-server synchronization. A summary and concluding re-marks appear in Section 9. Finally, the appendix outlines proofs of twoobservations that are needed for analyzing the implications of the termina-tion rules. These proofs require a more precise definition of execution thanis provided by most natural-language descriptions of Ada.

2. RELATED WORK

The ALRM and its clarifications [ACM SIGAda 1989] provide the definitivereferences for all Ada language features. A variety of Ada language textbooks [Barnes 1996; Gehani 1983] describe the language in a more tutorialformat, and several specialize in Ada tasking [Gehani 1984; Shumate1988]. None of these descriptions, however, explain the motivation fornuances in the definitions of the dependence and termination rules in Ada.Ichbiah et al. [Ichbiah 1979] discuss the rationale behind the design of

the Green language, which was the precursor to Ada. However, the differ-ences between tasking in Green and tasking in Ada are substantial. Arationale for the design of Ada 83 was developed after it became both anANSI and military standard [Ichbiah et al. 1991]. The goal of the rationaleis to impart the overall philosophy of the language design and to explainthe motivation behind key features of the language. The rationale mentionsthe need to protect the integrity of referencing environments as motivationfor Ada’s termination policy, but does not explain how the rules accomplishthis goal.Outstanding problems that relate to Ada tasking and to the usability of

tasking in real-time applications are discussed in ACM SIGAda [1990].These and other problems led to the formulation of the Ada 9X require-ments [Ada 9X Project Office 1990], which address the need to reclaim heapstorage, in general, and space used for control blocks of tasks that arecreated by an allocator, in particular. Requirements also address problemsof access to a task outside of its direct master. A rationale for the design ofAda 95, which uses essentially the same termination model as Ada 83,appears in Intermetrics [1995]. That report does not discuss the rationalebehind the definitions of the dependence relations or of the terminationrules. These definitions are discussed in Baker and Riccardi [1985], whichdescribes a run-time supervisor for Ada tasking.The contour model is an execution model for block-structured program-

ming languages and is used extensively in teaching applications. Differ-ences between Johnston’s original contour model [Johnston 1971] and themodel described below are discussed in Dillon [1993]. Johnston’s model is

82 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

the basis for a textbook on programming language structures [Organick etal. 1978] and has been used to illustrate the semantics of parameterpassing in Ada [Berry 1988] and multitasking in PL/1 [Berry 1975a]. Anautomated contour model provides a visual semantics for ALGOL programs[Thomas and Organick 1974]. A contour model of Ada tasking similar to theone described below appears in Belkhouche and Lawrence [1990]. Theirmodel is intended to clarify the semantics of the concurrency constructsand to be used in evaluating their complexity.The execution model outlined in Brindle et al. [1989] is designed to

support debugging of Ada tasking programs and to provide for display ofexecution states of concurrent programs. The debugger for Ada taskingdescribed in Feldman and Moran [1989] provides a graphical abstractionfor rendezvous that helps the user visualize the effects of interprocesssynchronization and communication. A compiler/interpreter for Ada task-ing, called Small-Ada, has been found to be effective in teaching concur-rency constructs [Lopes et al. 1993]. Small-Ada automatically scrolls thesource code of tasks as they execute. It provides user-selectable taskingoptions for controlling the speed of execution and the scheduling of readytasks. An automated debugger helps the user identify and correct certainclasses of anomalies. None of these interpreters and debuggers displaysnested contours representing run-time activations of program units.

3. OVERVIEW OF THE CONTOUR MODEL

This section describes those features of the contour model of Ada taskingthat are needed for the analysis of Ada’s termination rules. It assumesfamiliarity with the fundamentals of tasking in Ada. A more comprehensivedescription of the model appears in Dillon [1993].For simplicity, we discuss only a restricted subset of Ada in this article.

The analysis below considers issues pertaining to the interaction betweenprocedures and tasks, as these issues illustrate the main problems oftermination. We also briefly mention a straightforward generalization tolibrary units. We do not, however, consider packages, generics, or excep-tions, which introduce complications that are beyond the scope of thisarticle.The contour model defines a method for translating a program into an

abstract representation usually called an algorithm and defines an ab-stract machine that interprets algorithms to simulate program executions.The abstract machine generates sequences of records of execution fromthe algorithm for a program. Each record of execution describes an instan-taneous program state, and the sequencing of the records shows theprogression of states over time. The contour model interpreter simulatesconcurrency by interleaving parallel threads of control. It is nondetermin-istic in that it can generate different execution sequences from the samealgorithm to represent different interleavings of parallel threads of control

Task Dependence and Termination in Ada • 83

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

and different choices for resolving nondeterminism within threads. Wedescribe records of execution in some detail below. For brevity, however, wedo not describe the representation of an Ada program as an algorithm, buttalk in a loose sense of directly executing the program as is typical forin-class use of the contour model to describe other programming languages[Berry 1975a; 1975b].Figure 1 contains a sample program fragment, which we use to illustrate

key features of the contour model, and a record of execution for a state thatcan occur during an execution of this program. Line numbers (s1, s2, etc.)in the program fragment are provided for ease of reference; they are notpart of the program. The program fragment shows only those portions ofthe program that are needed for discussing task dependence and termina-tion. In a complete program, a sequence of declarations would replace theellipses at lines s2, s4, and s10; and one or more statements would replacethe ellipses at lines s6, s13, and s16. The comment at line s17 representsa complete instruction; in our contour model, a complete instruction isadded to the end of the body of every scope unit that can have dependents.Section 4 describes the semantics of this instruction.

Fig. 1. The dependence diagram and contour diagram for a program state that is produced byexecuting the program fragment ELAB.

84 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

The record of execution in Figure 1 consists of a contour diagram and adependence diagram.2 The contour diagram contains a contour (labeledrectangle) for each instance of a procedure or task that is active in theprogram state and a processor (II symbol) for each thread of control.3 Forease of reference, we label all contours and processors with names that arederived from the appropriate program names. The dependence diagramshows the hierarchy of masters and dependents in a program state. Section4 defines this hierarchy and explains how the termination rules make useof the master and dependent relations.In Figure 1 the outermost contour, labeled elab, depicts an instance of

the main procedure ELAB. Because the instance of the main procedure ina program requires a thread of control, we regard the main procedure asdefining a task, rather than a procedure; we refer to elab as the root taskinstance of the program state. Contour t1 represents the task instancethat is spawned when the task instance elab elaborates the declaration forT at lines s8–14, and contour p1 represents the procedure instance that iscreated when the task instance t1 executes the call to P at line s12.4 Thetwo processors depict the threads of control for the task instances elab andt1; subscripts distinguish the processors for each thread.In the discussion that follows, we identify the representation of a

run-time abstraction with the abstraction itself, in order to avoid lengthyand often awkward terminology. For example, we refer to the “processor fora thread of control” more simply as a “thread of control” and to a “contourfor a task instance” (“contour for a procedure instance”) more concisely as a“task instance” (“procedure instance”). Unless specifically qualified, theword “instance” refers to either a task instance or a procedure instance. Wealso refer to an instance in a given program state (record of execution) byname, without additional qualification (e.g., t1).The conventions for drawing a program state require that

—each thread in a contour diagram is placed directly inside the instancethat has control of the thread and

—the nesting of instances mirrors the nesting of declarations in theprogram.

This latter convention ensures that an instance is global to all instancesthat are nested within it. For example, in Figure 1 we place the thread fort1 inside of p1, since p1 has control of this thread. Moreover, we draw thethread for elab inside of elab and outside of both p1 and t1 because elab hascontrol of its own thread. We nest t1 and p1 directly in elab, since the

2 In general, a record of execution contains additional special-purpose diagrams; for instance,one such diagram shows the status of entry queues and the conditions on which instances areblocked. We only show those diagrams that are relevant to the discussion.3 Processors and contours are abstractions; they do not represent physical devices.4 More precisely, the interpreter executes instructions on behalf of the procedure and taskinstances that have control of the respective threads; we refer to threads and to procedure andtask instances as active entities because this practice results in more concise descriptions,which we feel are easier to understand.

Task Dependence and Termination in Ada • 85

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

declarations of the bodies for T and P are nested directly in ELAB. Thenesting of instances makes apparent the fact that elab is global to both p1and t1, but that t1 is not global to p1 and vice versa.An instruction pointer (short horizontal arrow to the right of a thread)

designates a thread’s locus of control. In Figure 1 the thread for elab is atline s18, and the thread for t1 is at line s6. We circle a thread to signifythat the instance that has control of the thread is blocked. An instanceblocks if it reaches an instruction that cannot proceed because some otherinstance is not at a required synchronization point; for example, aninstance may block if it needs to rendezvous with a concurrent taskinstance. In our model a task instance always blocks when it terminates.We say that an instance is ready if it has control of a thread and is notblocked. In Figure 1 the instance elab has executed its complete instruc-tion, but it cannot execute s18 until t1 terminates; it therefore blocks. Onthe other hand, the instance p1, which has control of the thread for t1, isready to execute a statement in the body of P. When t1 terminates weremove the circle from around the thread for elab, in effect changing theexecution status of elab from blocked to ready.In each instance in a program state, a data array (upper left corner)

shows the bindings of local identifiers to values. The data arrays in Figure1 show only those bindings required for the discussion that follows.Elaboration of a task declaration spawns a thread for a new task instance;as shown in Figure 1, we represent the task value that is created byelaborating the task declaration by a dashed arrow that points to thisthread. The other value shown in Figure 1 is called a return point. In acontour model, it is customary to extend each procedure with an (implicit)declaration for a return point; we use the identifier %RET for referencingthis value. When executing a call statement, the caller passes a value forthe return point to the callee: this value designates the caller, as well asthe location to which the callee will transfer control when it terminates. Asshown in Figure 1, we use a solid arrow, called a dynamic link, todesignate the caller. Thus, the procedure instance p1 will return control ofthe thread for t1 to t1 at line s13 when it terminates. Figure 1 does notshow procedure values, since the representation of procedure values isinconsequential for the discussion of task dependence and termination.The program fragment in Figure 1 illustrates one of the two methods for

spawning tasks in Ada: it shows a task declaration, which produces a newtask instance when elaborated. The program fragment in Figure 2 illus-trates the second method for spawning tasks in Ada: an instance can call ageneric storage allocator to create a new instance of a designated type; ifthe designated type is a task type, the storage allocator creates a new taskinstance. The program fragment in Figure 2 contains access type declara-tions in lines s3 and s5; both declarations specify TT as the designatedtype, although they appear in the declarative parts of different scope units.The allocator expressions in lines s9 and s10 spawn new task instancesand return access values, or references, that designate the new instances.

86 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

The contour diagram in Figure 2 shows how we represent the values ofaccess objects and of access types. A solid arrow to the thread for thedesignated task instance represents a task access value; for example, thevalue that p1 binds to O designates tt1, so the arrow that represents thisvalue points to the thread for tt1. The value that is bound to an access typeidentifies the collection of task instances that are designated by objects ofthe access type; we discuss collections in Section 6.For the discussion in subsequent sections, we require the concept of a

dynamic chain, or (reverse) call chain, which is associated with a threadof control. A thread’s dynamic chain enumerates the instances that areexecuting the thread, in reverse-call order. To traverse a thread’s dynamicchain we start at the instance that directly encloses the thread and followdynamic links to the task instance at the base of the chain. For example, inFigure 1 the dynamic chain of the thread for t1 is ^p1, t1&; in Figure 2 thedynamic chain of the thread for alloc is ^p1, alloc&; and in both of thesefigures the dynamic chains of all other threads consist of only the respec-

Fig. 2. A program state produced by executing the program fragment ALLOC.

Task Dependence and Termination in Ada • 87

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

tive base task instances. The transitive closure of the call relation definesan invocation relation: one instance invokes another if the former callsthe latter or if the former calls an instance that invokes the latter. In theprevious examples, all invocation relationships are direct: t1 invokes p1 inthe program state in Figure 1, and alloc invokes p1 in the program state inFigure 2. In general, an instance invokes all instances that precede it onthe dynamic chain of the thread that it executes; the instance at the frontof the dynamic chain has control of the thread.To understand the justification for the termination policy defined in the

ALRM, we analyze relationships among the referencing environmentsof the instances in a program state. An instance’s referencing environmentdetermines the identifiers that are visible in the instance—that is, theidentifiers that the instance can reference when it has control of a thread—and the values that the instance accesses when it references these identifi-ers. When an instance references a local identifier, it accesses a value in itsown data array. When an instance references a global identifier, it accessesthe value that one of its global instances binds to the identifier: by default,the closest global instance that contains a binding for the identifiersupplies the value; however, an expanded name can designate that adifferent global instance supply the value [United States Department ofDefense 1983, 4.3.1]. Thus, an instance’s local referencing environmentconsists of the bindings in its own data array, and its global referencingenvironment consists of the bindings in the data arrays of all global(enclosing) instances. For example, in the program state shown in Figure 1,p1 can access the values both of its own local identifiers and, because elabis global to p1, of the identifiers in the data array of elab. The bindings inp1 constitute p1’s local referencing environment, and the bindings in elabconstitute p1’s global referencing environment.An instance that has control of a thread can access values directly by

referencing identifiers, as described above; alternately, it can access valuesindirectly by selecting access values; for example, in Figure 2 the instancep1 can reference the identifier O to obtain an access value, which it canthen select to access tt1. A value is accessible in an instance if one of thefollowing conditions hold:

—it belongs to a binding in the referencing environment of the instance or—it is designated by an access value that is accessible in the instance.

A value is inaccessible in a program state if it is not accessible in anyinstance in that state. In the program state in Figure 1, the task instancet1 is accessible in both elab and p1: both of the latter instances can accesst1 by referencing T, and p1 can also access t1 by selecting the dynamic linkin the return point that is bound to %RET. Although t1 is accessible in elaband in p1, its local data are not; only t1 can access the values in its owndata array (and only after p1 returns control to t1). No values in thisprogram state are inaccessible.The following rule for adjoining new instances to a program state ensures

that the nesting of program instances conforms to the scope rules in Ada:

88 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

Adj. A new instance is nested directly in the instance that declares itsbody (i.e., the instance that elaborates the declaration for the procedure ortask body).

Consider, for example, the program fragment in Figure 2. When alloc callsP it nests p1 directly in its own contour, as shown in the Contour Diagramin Figure 2, since the reference to P is a local reference; however, when p1evaluates the allocator expressions, it nests the new task instances directlyin alloc, since alloc declares the body for tasks of type TT.

4. TERMINATION IN ADA

The procedure and task instances created during execution of an Adaprogram pass through a series of phases during their lifetimes. Normally,a task instance synchronizes with its creator before it activates;5 followingactivation, it executes the statements in its body; on completing its body,the task instance may need to synchronize with other concurrent instances,after which it terminates. We regard a procedure instance as passingthrough similar phases, although the phase of a procedure instance is onlyrelevant if the procedure instance has dependents. In the discussion below,we distinguish the following phases in the lifetimes of instances.6

Running. The instance has been activated and has not finished execut-ing its body.

Completed. The instance has executed its body and has not yet termi-nated.

Terminated. The instance has terminated; this phase is relevant onlyfor task instances, as the lifetime of a procedure instance ends when theinstance terminates.

Superscripts on the names of instances in the dependency diagram showthe current phases of the corresponding instances. For example, instanceelab is completed, and both p1 and t1 are running in Figure 1; and p1 iscompleted, and the remaining instances are all running in Figure 2.The blocked instances are completed in Figures 1 and 2. This coincidence

is not accidental: these figures show program states that help illustrateaspects of Ada’s termination rules, as discussed below. When a taskinstance terminates, it becomes permanently blocked. However, an in-stance that is running or that is completed can be either blocked or ready.For example, when an instance calls an entry in another task instance, ifthe latter instance has not reached a matching accept statement, theformer instance blocks, although it is still running. By the same token, aninstance that is completed is ready if all its dependents are terminated.

5 The ALRM defines the activation of a task to be the initial part of the execution of the taskbody, during which the task elaborates the body’s declarative part [United States Departmentof Defense 1983, 9-3].6 Additional phrases are required to model creation and abortion of tasks.

Task Dependence and Termination in Ada • 89

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

When a procedure instance terminates it deallocates its local data andreturns control to the caller. Once the callee’s data are deallocated, and thecaller has control of the thread, the callee is inaccessible. Thus, the lifetimeof a procedure instance ends when the instance terminates. When a taskinstance terminates it deallocates its local data and enters its terminatedphase. The lifetime of a task instance does not necessarily end when theinstance terminates because a terminated task instance can be accessible,in which case concurrent instances must be allowed to interrogate itsattributes and to invoke its entries. The ALRM does not explicitly stipulatewhen the lifetime of a task instance ends and when all informationpertaining to the task instance can be deallocated. Section 6 considerscircumstances under which it is safe to end the lifetime of a task instance.Two relations, defined in the ALRM, are key to understanding the

termination rules: (1) the master relation, which associates one or moremasters with each task instance, and (2) its inverse, the dependentrelation, which associates one or more dependents with each instance thatis a master. For the discussion of these relations, we classify a taskinstance as elaborated, if it is spawned by elaborating a task declaration,or allocated, if it is spawned by invoking a task allocator. In either casethe parent of a task instance refers to the instance that spawns the taskinstance.7

Every task instance in a program state, except the root instance of themain procedure, has exactly one direct master, as described by thefollowing rules.

Master 1. The parent of an elaborated task instance is also the directmaster of the elaborated task instance.

Master 2. The instance that declares the (access) type of an allocatorexpression is the direct master of all allocated task instances that arespawned by evaluating the expression.

For example, in Figure 1 the task instance t1 is elaborated, so its parent,elab, is also its direct master; on the other hand, in Figure 2 both tt1 andtt2 are allocated: p1 spawns tt1 when it evaluates the allocator expressionin s9, and it spawns tt2 when it evaluates the allocator expression in s10.The root instance alloc is the direct master of tt1, since it declares OUTR,the type associated with the allocator expression in s9, whereas, p1 is thedirect master of tt2, since it declares INNR, the type associated with theallocator expression in s10. Note that the direct master in Master 2 musteither be the parent or be global to the parent, since the access type isvisible in the parent. For example, in Figure 2 the instance p1 is both thedirect master and the parent of tt2; whereas alloc, the direct master of tt1,is global to p1, the parent of tt1.

7 This terminology is not standard. Brindel et al. [1989] call the instance that spawns a newtask instance the “parent frame” and call the task instance that invokes the parent frame the“parent task.” Baker and Riccardi [1985] use a “parent” to refer to this parent task. In Barnes[1993], the task instance that invokes the direct master is called the “parent.”

90 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

In addition to a direct master, a task instance may have any number ofindirect masters. An instance is an indirect master of a task instance ifeither (1) it invokes (directly or indirectly) a master of the task instance or(2) it is a master of a master of the task instance. In Figure 2 the instancealloc is an indirect master of tt2, since it invokes tt2’s direct master. A taskinstance depends on its masters.The preceding definitions associate a hierarchy of masters and depen-

dents with each program state. The dependence diagram displays thishierarchy using the following conventions.

—The names of a dependent and of its master are connected by an inclinedarrow, with the master’s name above and the dependent’s name belowthe arrow, and with the arrow directed away from the dependent’s nameand toward the master’s name.

—The names of a callee and of its caller are connected by a vertical linesegment, with the caller’s name above and the callee’s name below thesegment.

The chain of masters of a task instance enumerates the instance’smasters in the order in which they occur on the path from the instance’sdirect master to the root task instance. Referring to the previous figures,we obtain the following chains of masters.

Figure 1 Figure 2

Task Instance Chain of Masters Task Instance Chain of Masters

elab ^ & alloc ^ &t1 ^elab& tt1 ^alloc&

tt2 ^p1, alloc&

Two rules determine when instances terminate during execution of anAda program. The first rule pertains to instances that have completed. Aninstance completes when it finishes executing the statements in its body;thus, a completed instance does not require a thread of control.

Term 1. An instance terminates when it is completed and when all of itsdependents are terminated.

This rule explains why elab blocks in the program state shown in Figure 1:it is completed, but it cannot terminate until t1 terminates.The second termination rule pertains to task instances that are executing

select statements. In Ada, the select statement allows a task instancethat contains entries (remote procedures) to selectively accept calls onthese entries from concurrent instances. (The rendezvous that result fromsuch remote procedure calls are the primary means by which concurrentinstances synchronize and communicate during execution of an Ada pro-gram.) Selective wait alternatives specify the entries on which a taskinstance will accept calls. A boolean guard can precede a selective waitalternative, giving the task instance some control over the selection ofalternatives: if the guard is true the alternative is open and can be

Task Dependence and Termination in Ada • 91

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

selected, and if the guard is false the alternative is closed and cannot beselected. In addition to selective wait alternatives, a select statement maycontain a terminate alternative. The rule for selecting a terminatealternative requires that some master of the task instance is completed,that each nonterminated dependent of this master is at a select statement,and that each of these select statements contain an open terminate alterna-tive; under these circumstances, the ALRM specifies that all dependents ofthis master must terminate, a process known as distributed termina-tion. For brevity, we say that a master is asleep if it is completed and ifeach nonterminated dependent of the master is at a select statement thatcontains an open terminate alternative. When a master is asleep, the(nonterminated) dependents of the master are ready to select terminatealternatives. Using this terminology, the second termination rule becomes:

Term 2. A task instance terminates when it reaches a select statementwith an open terminate alternative if some master of the task instance isasleep.

Section 8 discusses distributed termination in more detail and providesan example that illustrates the application of this rule.We have not considered library units up to this point in order to simplify

the terminology and examples. When a program imports library units, wedo not regard the main procedure as defining a root task; instead, weimplicitly wrap the entire program, including the code for the requiredlibrary units, in an environment program. Conceptually, an environmentprogram consists (1) of a declarative part, which contains declarations forall objects in the library units and for the main procedure, and (2) of a body,which calls the main procedure and then completes. Under these conven-tions, an instance of the environment program serves both as the root taskinstance for the hierarchy of master and dependents and as the directmaster of all library-task instances. When an instance of the main proce-dure is executing, it belongs to the dynamic chain of the thread for the rootenvironment-task instance. According to the termination rules, therefore,the main procedure instance cannot terminate until its dependents haveterminated, even if a library unit declares the dependents’ task type;however, the main procedure instance does not have to wait for library-taskinstances to terminate, since it is not a master of the library-task in-stances.The next section examines how the termination rules affect traditional

stack-based methods for managing the dynamic chains of threads of controlduring execution of a program. Subsequent sections consider issues relat-ing to the management of allocator storage and to synchronization anddistributed termination of concurrent instances. For simplicity, we againlimit the discussion to Ada programs that declare all the necessary proce-dures and tasks; however, as indicated in the previous paragraph, theresults apply equally well to programs that import procedures and tasksfrom library units.

92 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

5. RECLAIMING STACK STORAGE

Block-structured programming languages allow relatively efficient memorymanagement schemes: the dynamic chains of threads of control can bemaintained as run-time stacks because of the last-in-first-out manner inwhich a thread enters and leaves instances. However, when multiplethreads share referencing environments, an instance may finish executingbefore concurrent instances are finished accessing the data objects that itdeclares. In the program state in Figure 1, for example, elab has completedand no longer requires a thread of control. However, the remaining in-stances can access elab’s local data, so elab must not deallocate its datauntil t1 terminates. In general, the termination rules must guarantee thatan instance does not terminate as long as any of its data are accessible.The following observation identifies necessary conditions for local data in

an instance i_global to be accessible in an instance i_local: if i_local is aprocedure instance, then either i_global or a dependent of i_global mustinvoke i_local; alternately, if i_local is a task instance, then it must dependon i_global. Figure 3 depicts the possible situations. Ellipses in the contourdiagram signify instances that are directly or indirectly nested in or equalto one another; similarly, ellipses in the dependence diagrams representthe reflexive transitive closure of the union of the dependence and invoca-tion relations. We write i_local?–

– i_global to mean that i_local is nested ini_global (i.e., i_global is global to i_local).

Observation 1. Consider a program state containing instances i_localand i_global. Either let t_local denote the task instance that invokesi_local, or, if i_local is a task instance, let t_local denote i_local. If i_local?–

i_global, then either i_global invokes i_local, or t_local depends on i_global.

The proof of this observation uses induction on the total number ofinstances that are created or terminated during execution; details appearin the appendix.Observation 1 guarantees that, if i_global is completed, then its local

data can be accessed only in its dependents and in instances invoked by itsdependents. Thus, if an instance meets the conditions of Term 1, it can

Fig. 3. Data local to i_global are accessible in i_local only if (1) i_global invokes i_local or (2)t_local depends on i_global.

Task Dependence and Termination in Ada • 93

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

safely terminate. Similarly, if i_global is executing a select statement thatcontains an open terminate alternative and if some master of i_global isasleep, then Observation 1 guarantees that i_global’s local data can beaccessed only in instances that are executing threads for dependents of thesleeping master; as these instances are ready to select terminate alterna-tives (all other dependents having already terminated), all (nonterminated)dependents of the sleeping master can terminate without compromising thereferencing environments of other concurrent instances. Thus, if an in-stance meets the conditions of either Term 1 or Term 2, it can safelyterminate.The previous analysis demonstrates that an instance does not deallocate

its data prematurely on completing execution. However, Ada’s terminationpolicy can prevent a callee from terminating and returning control to thecaller in circumstances where the callee can safely deallocate its data. Forexample, in Figure 2 the termination rules force p1 to block until tt2terminates; whereas the visibility and scope rules ensure that p1’s localdata cannot be accessed in tt2 or in any instance that tt2 invokes, so thatterminating p1 would not compromise the referencing environments of anyinstances in this or in any subsequent program state.The situation that we have just described arises because, as indicated by

the Adj rule, the instance that declares a task body determines the globalreferencing environment for all task instances that execute this body; if theformer instance is global to a master of one of these task instances, thenthis master cannot terminate unless the task instance is terminated,although this master’s local data are not accessible in the task instance orin any procedure instance that the task instance invokes.A less conservative termination policy might replace the rules Master 1

and Master 2, which define the direct master of a task instance, with thefollowing rule:

Master 3. The instance that declares a task body is the direct master ofall task instances that execute this body.

Figure 4 shows how this rule would affect the program state of Figure 2.The dependence diagram shows that, with this definition, tt1 and tt2depend only on alloc, and that p1 does not have dependents. Thus, thecontour diagram in this program state is identical to that shown in Figure2, except that p1 is ready to terminate in Figure 4. If p1 is the next instanceto execute, then it will deallocate its local data and return control to alloc,permitting alloc to proceed while tt2 is still running.Replacing the rules Master 1 and Master 2 with the rule Master 3 does

not affect the validity of Observation 1. Like the termination policy in Ada,therefore, this less conservative termination policy guarantees that aninstance does not terminate if a concurrent instance can access its localdata, unless this concurrent instance is ready to select a terminate alterna-tive. For example, in Figure 4 the global instance alloc cannot terminateuntil both tt1 and tt2 terminate.

94 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

6. RECLAIMING ALLOCATOR STORAGE

In Ada, an allocator expression can instantiate the generic new operationwith an arbitrary designated type. When evaluated, the expression createsa new value of the designated type and returns an access value, orreference, that designates the new value. Access values can be assigned toglobal variables and passed to and from subprograms as parameters. Thus,an allocated value can remain accessible long after its parent terminates.For this reason, allocated values cannot be placed on the run-time stacks ofthreads of control; they must be kept in a separate storage area, where theycan be retained until they are no longer accessible, at the least. The storagearea for allocated values is often called the heap. In this section we showthat the rule Master 2, which defines the direct master of an allocated taskinstance, permits an implementation of Ada to efficiently determine whenit can safely reclaim tasks that are stored on the heap.The problems that a programming language implementation must ad-

dress in managing heap storage are well known. One of the more signifi-cant problems relates to the deallocation of garbage, or values that can nolonger affect the outcome of an execution. Although the ALRM does notrequire that an implementation reclaim garbage, failure to do so wastesheap storage; many applications require efficient garbage collection to bepractical [Ada 9X Project Office 1990].The ALRM defines collections to help in managing heap storage:

Coll 1. Each access type has an associated collection, defined as theset of allocated values that are designated by values of the access type.

As illustrated in Figure 2, the value for an access type shows the associatedcollection: the set {tt1} represents the collection that alloc binds to OUTR,and {tt2} represents the collection that p1 binds to INNR. A collection can beimplemented as a region in the heap from which designated values for theassociated access type are allocated. A language implementation can then

Fig. 4. A hypothetical program state for ALLOC, produced using Master 3 to define the directmaster relation.

Task Dependence and Termination in Ada • 95

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

reclaim the collection for an access type, very efficiently, when it termi-nates the instance that declares the access type.The following observation shows that, for most allocated values, this

tempered strategy for reclaiming garbage is sound. Figure 5 shows acontour diagram for a program state that satisfies the hypotheses of theobservation.

Observation 2. Consider a program state containing an instance i_glo-bal that declares an access type named AT. Let c denote the collection thati_global binds to AT. Then values in c can be accessed in an instance i_localonly if i_localv i_global.

This observation is a trivial consequence of the typing rules in Ada: toaccess a value in c the instance i_local must reference an expression of typeAT; hence, the binding for AT in i_global must be visible in i_local. Notethat, if AT is a task access type, then i_global is the direct master of thetask instances in c.Rephrasing Observation 2, we can say that an instance can access the

values designated by a collection only from within the instance thatdeclares the associated access type; when this latter instance terminatesthese values become inaccessible, and if they are not task instances, theycan be reclaimed as garbage. However, a task instance does not have to beaccessible to affect the outcome of an execution; for example, an inacces-sible task instance can modify the values of global variables, and it canengage in rendezvous by invoking entries of other task instances. Aninaccessible task instance is garbage only if it is either terminated or readyto select a terminate alternative. Ada’s conservative termination policy,based on the rules Master 1, Master 2, Term 1, Term 2, and Coll 1, ensuresthat, when an instance that declares a task access type terminates, all taskinstances that belong to the associated collection have become garbage;thus, an implementation can reclaim all of these task instances at thispoint. The less conservative termination policy discussed previously, inwhich we replace Master 1 and Master 2 with Master 3, does not provide

Fig. 5. The values in c are accessible in i_local only if i_local v i_global.

96 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

this guarantee. For example, in Figure 4 the instance p1 can terminatewhile tt2, which belongs to the collection that p1 binds to INNR, is stillrunning; we would not, therefore, want an implementation to reclaim thiscollection when it terminates p1.We can salvage the less conservative termination policy if we associate

collections with designated types, rather than access types. In other words,we can replace Master 1 and Master 2 with Master 3, if we also replace Coll1 with the following rule:

Coll 2. Each type that is designated by an access type declaration hasan associated collection, which is defined as the set of allocated values ofthe designated type.

These modifications produce a termination policy that is consistent with animplementation that reclaims the values in a collection when it terminatesthe instance that declares the designated type. Figure 6 shows how thesemodifications would affect the program state shown in Figure 2. In theprogram state in Figure 6, alloc is the direct master of both tt1 and tt2, sop1 can terminate and return control to alloc. The allocated task instanceswould then run concurrently with alloc; they would be deallocated whenalloc terminates, since they belong to the collection associated with TT.The main tradeoffs between Ada’s termination policy and a less conser-

vative policy based on the rules Master 3, Term 1, Term 2, and Coll 2 relateto the utilization of storage and the potential for parallelism. The lessconservative termination policy increases concurrency, but it also delaysthe reclamation of inaccessible allocated objects. For task instances thatare allocated this delay might be justified by the desire to increaseparallelism. However, most allocated values are passive. An inaccessibleallocated value can affect the outcome of a computation only if it is a taskinstance; delaying the reclamation of inaccessible values that are not taskinstances would serve no purpose.

Fig. 6. A hypothetical program state for ALLOC, produced using Master 3 to define the directmaster relation and using Coll 2 to associate collections with derived types.

Task Dependence and Termination in Ada • 97

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

7. SYNCHRONIZING TERMINATION OF SLAVE TASKS

Language design issues that relate to the run-time management of storageaffect the efficiency of language implementations. Because a language’stermination policy determines the manner in which instances synchronizetheir termination, it also affects the ease of writing programs. This sectionexamines the degree to which Ada’s termination policy supports the syn-chronization required for master-slave applications.A master-slave application is one in which a parent spawns one or more

slave task instances to perform specific subtasks on behalf of the parent. Itis common, in this context, to refer to the parent as a “master,” although ifthe slave is allocated this use of “master” is not consistent with thedefinition for “master” in the ALRM. In master-slave applications, slavesmust terminate before their parents, because, logically, execution of theslaves is part of execution of the parent.The program fragment in Figure 7 is for a simple master-slave applica-

tion and produces the program state shown, in which the instance produce1has spawned prod11 and prod21, slave instances that concurrently producevalues for I1 and I2. Clearly, produce1 must not terminate until its slavesterminate. Term 1 provides the required synchronization in this examplebecause produce1 is both the direct master and the parent of the slave taskinstances.The synchronization that is required for master-slave applications is

more difficult to realize if the direct master of a slave is global to the slave’sparent. Consider, for example, how the program state shown in Figure 7would be affected if, instead of using Master 1 in defining the direct masterrelation, we were to use Master 3. Then prod_cons1 would be the directmaster of prod11 and prod21, and so produce1 would not be blocked; it couldtherefore terminate, and an instance of CHECK could execute concurrentlywith the slaves that produce1 spawns to initialize the shared variables I1and I2. Thus, the program would permit erroneous race conditions in theuse of these variables. The race conditions could be eliminated if theprogrammer were to move the declarations for PROD1 and PROD2 to thedeclarative part of PRODUCE, but then PROD1 and PROD2 would not bevisible in CHECK.A similar situation can arise using Ada’s termination policy. The termi-

nation rules provide the synchronization required for master-slave applica-tions only if the parent of a slave is also the slave’s direct master. Forexample, the following version of PRODUCE has essentially the same effectas the original version, except that it serializes the activation of the slavetask instances.

procedure PRODUCE istype PACC1 is access PROD1;type PACC2 is access PROD2;P1 : PACC1;P2 : PACC2;

98 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

begin– –PRODUCEP1 ;5 new PROD1;P2 ;5 new PROD2;

end PRODUCE;

However, as we have already seen, the direct master can be global to theparent. For instance, if we were to move the declarations for PACC1 andPACC2 from the declarative part of PRODUCE to the declarative part of

Fig. 7. A master-slave application and a program state for it.

Task Dependence and Termination in Ada • 99

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

PROD_CONS so that the declarations are visible in both PRODUCE andCHECK, the resulting program would admit erroneous race conditions.Whenever the direct master for a slave instance is global to the slave’sparent, this situation can arise.

8. DISTRIBUTED TERMINATION

Client-server applications require a different pattern of synchronizationthan master-slave applications. In this section, we show how Ada’s termi-nation policy provides for distributed termination of a client-server applica-tion.The body of a server task generally contains an indefinite loop state-

ment. On each iteration of the loop, the server executes a selective waitstatement containing a terminate alternative. This design permits theserver to repeatedly accept calls from clients and, when the clients nolonger require the server, to eventually terminate. Term 2 provides for suchdistributed termination of concurrent task instances. To understand howdistributed termination works, it is necessary to understand what thecondition, in Master 2, that a master is asleep implies of a program state.The following observation gives necessary conditions for an instance

i_local to invoke an entry of a task instance t_accbl: the direct master oft_accbl must either invoke or be i_local, or it must be a master either ofi_local or of the task instance that invokes i_local. Figure 8 illustrates thepossible scenarios.

Observation 3. Consider a program state containing a task instancet_accbl and an instance i_local, t_accbl Þ i_local. Let d_master denote thedirect master of t_accbl, and let t_local either denote the task instance thatinvokes i_local or, if i_local is a task instance, denote i_local. Then t_accblis accessible in i_local only if one of the following conditions holds:

Fig. 8. The instance i_local can access t_accbl only if (1) i_local is d_master or is invoked byd_master or (2) t_local depends on d_master.

100 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

(1) The instance i_local either is d_master or is invoked by d_master.(2) The instance t_local depends on d_master.

Thus, a task instance can be accessed only in instances that executethreads for its direct master or for dependents of its direct master.8

Before discussing the proof of Observation 3, we prove that, if a programstate contains a sleeping master, then no concurrent instance can execute acall to an entry that belongs to any of the sleeping master’s dependents.

Observation 4. Consider a task instance t_accbl with a master that isasleep. If a concurrent instance i_local can access t_accbl, then eitheri_local is completed, or i_local is executing a select statement with an openterminate alternative.

PROOF. Let s_master denote a master of t_accbl that is asleep; let t_localeither denote the task instance that invokes i_local or, if i_local is a taskinstance, denote i_local; and let d_master denote the direct master oft_accbl. Then Observation 3 applies.Condition (2) of Observation 3 implies that t_local depends on s_master.

In this case, therefore, i_local is ready to select an open terminate alterna-tive.Alternately, if condition (1) of Observation 3 holds, and i_local is not

completed, then none of the masters from d_master through t_local on thechain of masters for t_accbl is completed; the sleeping s_master musttherefore come after t_local. But then t_local also depends on s_master, andso i_local, which is in control of the thread for t_local, is ready to select anopen terminate alternative. e

Observation 4 implies that no thread can invoke the entries of a server taskinstance if some master of the server is asleep. Thus, the server canterminate.Observation 3 follows easily from Observations 1 and 2 when t_accbl is

allocated. However, Observation 2 does not apply when t_accbl is elabo-rated, since an elaborated task instance can be accessed from outside of itsdirect master. For example, in the program state in Figure 9 the taskinstance t_accbl1 is accessible in i_local1, although i_local1 is not nested ind_master1, the direct master of t_accbl1. This example is representative inthat Ada’s scope rules ensure that an elaborated task instance is accessibleoutside its master only if it is passed as a parameter to a nonenclosinginstance. The appendix proves Observation 3 for the case that t_accbl is

8 Observation 3 depends on the restriction to procedures and tasks; in particular, we mustpreclude functions. A function can return a task instance that it elaborates to a caller, inwhich case, the caller can call the task’s entries (which event would raise an exception, sincethe task instance is necessarily terminated). However, this case is regarded as pathologicaland of no practical use; it has been recommended that this case be considered erroneousbecause it complicates the implementation of storage recovery for terminated tasks [Good-enough 1990].

Task Dependence and Termination in Ada • 101

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

elaborated by induction on the number of unit instances that receivet_accbl as a parameter.The program fragment in Figure 10 shows why the rule Term 2 does not

permit an instance to select an open terminate alternative as soon as somemaster has completed, but requires that the instance wait until all depen-dents of the completed master are either terminated or ready to selectterminate alternatives. As shown, execution of this fragment can produce aprogram state in which the master main of the task instance svr1 hascompleted and in which svr1 has reached an open terminate alternative. Theinstruction pointer for svr1 references the two open alternatives that svr1can selectively execute. If either usr1 or usr2 calls svr1’s entry in asubsequent program state, then svr1 must select the accept alternative, soit must not select the terminate alternative in the state shown. However,when usr1 and usr2 terminate or reach open terminate alternatives, then allthe task instances can terminate.Finally, note that Term 2 allows the sleeping master to be an indirect

master in case execution of the direct master reaches an open terminatealternative. This situation is illustrated by the program state in Figure 11.This state has a single successor, which is produced when the thread svr_b1executes the select statement at s17. The terminate alternative in line s20is open, and main, an indirect master of svr_b1, is asleep. Therefore, svr_b1

Fig. 9. The task instance t_accbl1 is accessible from outside of its master d_master1.

102 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

must select its terminate alternative on its next machine cycle. When svr_b1terminates it awakens svr_a1, which then also terminates. When svr_a1terminates it deallocates the thread for its direct dependent svr_b1 andawakens main. Finally, main terminates, deallocating the remainingthreads and ending the execution.Note that, if the direct master d_master of the task instance t_accbl in

Observation 3 is ready to terminate, then, since d_master has control of athread, either d_master and i_local are equal, or t_local depends ond_master. Thus, when all dependents of d_master are terminated, t_accbl isno longer accessible; no thread can interrogate its attributes or invoke itsentries. The lifetime of a task instance can end, therefore, with the

Fig. 10. The distributed termination criteria must take into account all dependents of thecompleted master.

Task Dependence and Termination in Ada • 103

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

termination of its direct master. We represent this in our contour model bydeallocating the thread for a task instance upon terminating its directmaster, as indicated in the previous paragraph.

9. CONCLUSION

We have used a contour model of Ada tasking to clarify the rules thatgovern the termination of procedures and tasks in Ada and to explore the

Fig. 11. A program state that leads to distributed termination.

104 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

implications of the termination rules. Our analysis has shown that Ada’stermination policy ensures that referencing environments are not deallo-cated prematurely, but that the policy is unnecessarily conservative in thisregard. For task instances created by invoking a storage allocator, we haveshown that the conservative termination policy permits more efficientreclamation of allocator storage than a less conservative policy. We havealso considered how Ada’s termination policy supports the synchronizationrequired in master-slave and client-server applications. In particular, wehave shown that the rules governing distributed termination in Ada ensurethat a server selects a terminate alternative only when its services can nolonger be required.A conclusion that can be drawn from our analysis is that issues relating,

on the one hand, to run-time management of storage and, on the other, tosynchronization of terminating instances have resulted in definitionswhose implications are not easily demonstrated nor understood. One appar-ent source of confusion is the dual role that the placement of task declara-tions and task access type declarations plays in the semantics. In Ada, as inall block-structured languages, the placement of a declaration determinesits scope. However, in the case of a task declaration or a task access typedeclaration, the placement of the declaration also determines how concur-rent instances synchronize their termination. The same language mecha-nism is thus used to resolve largely independent and, at times, conflictingconcerns.Programmers need to understand the intricacies of Ada tasking if they

are to make effective use of it when writing programs. Likewise, languagedesigners should understand the justification for the tasking semantics andthe implications of the semantics, if they are to take advantage of thestrengths of tasking while avoiding its pitfalls. The clarification andanalysis of the semantics of task dependence and termination presented inthis article should give programmers and language designers a betterunderstanding of tasking in Ada.

APPENDIX

A. PROOFS OF OBSERVATIONS

We first introduce notation needed for the proof of Observation 1. Amongthe sets and relations that define a program state S, we require thefollowing:

—The set of (procedure and task) instances in S: instances(S)—The nesting relation on instances in S: ?–

–s

i1?––Si2 iff i1, i2[instances~S! and i1 is nested in i2

—The invocation relation on instances in S: IS

i1 IS i2 iff i1, i2 [ instances~S! and i1 invokes i2

Task Dependence and Termination in Ada • 105

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

—The master relation MS on instances in S:

i1 MS i2 iff i1, i2 [ instances~S!,

i2 is a task instance, and i1 is a master of i2.

A formal definition of the contour model describes how instructions alterthese components of a program state. Although a formal treatment of thecontour model is beyond the scope of this article, it is not difficult to seehow various instructions affect the previous components. In the following,we assume that S9 is the successor of S that is produced by executing thenext instruction instr of a ready instance i. If instr does not create a newinstance or terminate i, then its execution has no effect on any of theaforementioned components; otherwise, its execution has one of the follow-ing effects:

Step 1. If instr terminates i then

—instances(S9) 5 instances(S)\{i}—IS9 5 IS ù (instances(S9) 3 instances(S9))—?–

–S9 5 ?–

–S ù (instances(S9) 3 instances(S9))

—MS9 5 MS ù (instances(S9) 3 instances(S9))

Step 2. If instr spawns the task instance t_new then

—instances(S9) 5 instances(S) ø {t_new}—IS9 5 IS—?–

–S9 5 ?–

–S ø {(t_new, i_global9)} ø {(t_new, i_global0) u i_global9?–

–S i_glo-

bal0}, where i_global9 declares the task body that t_new executes (see therule Adj, Section 3)

—MS9 5 MS ø {(d_master, t_new)}ø {(i_master, t_new) u i_master IS d_master or i_master MSt_master},

where the direct master d_master is determined by Master 1 and Master2, and t_master is the task instance in instances(S) such that eithert_master IS d_master or t_master 5 d_master

Step 3. If instr creates the procedure instance p_new then

—instances(S9) 5 instances(S) ø {p_new}—IS9 5 IS ø {(i_caller, p_new)} ø {(i_caller9, p_new) u i_caller9 IS i_caller}—?–

–S9 5 ?–

–S ø {(p_new, i_global9)} ø {(p_new, i_global0) u i_global9 ?–

–S

i_global0}, where i_global9 declares the procedure body that p_newexecutes (see the rule Adj)

—MS9 5 MS

Observation 1. Let S denote a program state. Assume that i_local,i_global, and t_local belong to instances(S), and that t_local is the task

106 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

instance such that t_local IS i_local or t_local 5 i_local. Then i_local?––S

i_global only if either i_global IS i_local or i_global MS t_local.

PROOF. The observation holds (vacuously) if S consists of a single roottask instance. We show that, if the property in the observation holds for aprogram state S and if the program state S9 is the successor of S in someexecution of a program, then S9 also satisfies the property. The observationthen follows by induction.If the instruction that is executed to produce S9 does not create a new

instance, then the observation follows easily from the induction hypothesisand Step 1.If S9 is produced from S by executing an instruction that spawns a new

task instance t_new, and if i_local Þ t_new, then the induction hypothesisand Step 2 imply that S9 satisfies the required property. We thereforeassume that i_local 5 t_new, and we show that t_new?–

–S9 i_global implies

i_global MS9 t_new.The rule Adj, for adjoining a new task instance to S, implies that t_new is

nested directly in the instance i_global9 that declares the body executed byt_new; thus, the assumption that t_new?–

–S9 i_global implies i_global9vS9

i_global. Moreover, the type and scope rules in Ada require that thedeclaration of the body for t_new is visible in the direct master d_master oft_new. Thus, d_mastervS9 i_global9, and we obtain the picture for S9 shownin Figure 12, in which TT denotes the identifier in the declaration of thetask body.If d_master 5 i_global, then t_new depends (directly) on i_global. Fur-

ther, if d_master?––S9 i_global, then d_master?–

–S i_global; thus, the induction

hypothesis implies that either i_global IS d_master or i_global MS t_master,where t_master [ instances(S) is the task instance satisfying t_master ISd_master or t_master 5 d_master. In all of these cases, we conclude thatt_new depends (indirectly) on i_global from Step 2.This proves that creating a new task instance preserves the property

expressed in the observation. The proof that creating a new procedureinstance preserves this property is similar. e

Figure 12.

Task Dependence and Termination in Ada • 107

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

Observation 3. Let S denote a program state. Assume that t_accbl [instances(S) is a task instance, that d_master [ instances(S) is the directmaster of t_accbl, that i_local [ instances(S), and that t_local [ instan-ces(S) is the task instance such that either t_local IS i_local or t_local 5i_local. Then i_local can access t_accbl only if one of the following holds: (1)d_master 5 i_local, (2) d_master IS i_local, or (3) d_master MS t_local.

PROOF. If t_accbl is allocated, then Observation 2 implies i_localvS

d_master, and so Observation 1 implies that (1), (2), or (3) holds.We assume, therefore, that t_accbl is elaborated, and we prove that one

of (1), (2), or (3) holds by induction on the number of instances inancestors(i_local) that receive t_accbl as an actual parameter, where ances-tors(i) [ (instances(S))*, for i [ instances(S), denotes the sequence ofinstances whose first element is i and whose successor relation enumerates

Fig. 13. If (4) or (5) holds, and (7) or (8) holds, then d_master IS i_local (top left); in all othercases, d_master MS t_local.

108 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

the ancestors of i in the order determined by the hierarchy of masters anddependents for S.Let T denote an identifier that i_local can reference to access t_accbl, and

let i_global denote the instance that declares T. Since i_localvS i_global,Observation 1 implies that (4) i_local 5 i_global, (5) i_global IS i_local, or(6) i_global MS t_local.We establish the basis for the induction by assuming that t_accbl is not

passed as an actual parameter to any instance in ancestors(i_local). In thiscase, T does not denote a formal parameter; i_global must therefore spawnt_accbl by elaborating a task declaration. Thus, i_global 5 d_master,making (4) equivalent to (1), (5) equivalent to (2), and (6) equivalent to (3).For the induction step, we assume that t_accbl is passed to some instance

in ancestors(i_local) as an actual parameter. If T is not a formal parameter,then the argument given for the basis case applies. We assume, therefore,that i_global is a procedure instance and that T is a formal parameter tothis instance. Let i_caller denote the instance that calls i_global.Now, (4)–(6) imply that i_global [ ancestors(i_local). Thus, we have

i_caller [ ancestors(i_local), and ancestors(i_caller) is the tail of ances-tors(i_local) that begins with i_caller. Applying the induction hypothesis toi_caller, we conclude that (7) d_master 5 i_caller, (8) d_master IS i_caller,or (9) d_master MS t_caller, where t_caller [ instances(S) is the taskinstance such that either t_caller IS i_caller or t_caller 5 i_caller.The above observations produce the cases shown in Figure 13. As

illustrated, each case implies either (2) or (3). e

ACKNOWLEDGMENTS

I would like to acknowledge the help and encouragement of Daniel Berry,and many useful comments and suggestions made by the anonymousreferees.

REFERENCES

ACM SIGADA. 1989. The Approved Ada Language Commentaries. ACM SIGAda, GrebynCorp., Vienna, Va.

ACM SIGADA. 1990. Proceedings of the 3rd International Workshop on Real-Time AdaIssues. ACM SIGAda, New York. Also published as Ada Lett. 10, 4 (Spring 1990).

ADA 9X PROJECT OFFICE. 1990. Ada 9X Requirements. Software Engineering Inst., CarnegieMellon Univ., Ada 9X Office, Pittsburgh, Pa.

APT, K. R., FRANCEZ, N., AND DE ROEVER, W. P. 1980. A proof system for communicatingsequential processes. ACM Trans. Program. Lang. Syst. 2, 3 (July), 359–385.

BAKER, T. AND RICCARDI, G. A. 1985. Ada tasking: From semantics to efficient implementa-tion. IEEE Softw. 2, 2, 34–46.

BARNES, J. P. 1993. Introducing Ada 9X. Ada Lett. 13, 6 (Nov./Dec.), 61–132.BARNES, J. P. 1996. Programming in Ada 95. Addison-Wesley, Reading, Mass.BELKHOUCHE, B. AND LAWRENCE, L. M. 1990. A semantic model of Ada tasking. J. Pascal,Ada Modula-2, 9, 2 (Mar./Apr.), 29–41.

BERRY, D. M. 1975a. Notes on PL/1. Univ. of California, Los Angeles, Calif. Unpublishedlecture notes.

BERRY, D. M. 1975b. Notes on the contour model. Univ. of California, Los Angeles, Calif.Unpublished lecture notes.

Task Dependence and Termination in Ada • 109

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.

BERRY, D. M. 1988. Treatment of Ada procedures: Aliasing, anomalies, and erroneousness.Tech. Rep., Unisys Corp., Santa Monica, Calif. Feb.

BRINDLE, A. E., TAYLOR, R. N., AND MARTIN, D. F. 1989. A debugger for Ada tasking. IEEETrans. Softw. Eng. 15, 3 (Mar.), 293–304.

DILLON, L. K. 1993. A visual execution model for Ada tasking. ACM Trans. Softw. Eng.Meth. 2, 4 (Oct.), 311–345.

FELDMAN, M. B. AND MORAN, M. L. 1989. Validating a demonstration tool for graphics-assisted debugging of Ada concurrent programs. IEEE Trans. Softw. Eng. 15, 3 (Mar.),305–313.

GEHANI, N. 1983. Ada, An Advanced Introduction. Prentice-Hall, Englewood Cliffs, N.J.GEHANI, N. 1984. Ada: Concurrent Programming. Prentice-Hall, Englewood Cliffs, N.J.GERTH, R. T. AND DE ROEVER, W. P. 1984. A proof system for concurrent Ada programs. InScience of Computer Programming, Vol. 4. Elsevier Science Publishers B.V. (North-Holland),Amsterdam, 159–204.

GOODENOUGH, J. 1990. Real-time tasking semantics working group: Recovering storage forterminated tasks. In Proceedings of the 3rd International Workshop on Real-Time AdaIssues. ACM SIGAda, New York, 46.

ICHBIAH, J. 1979. Rationale for the design of the Ada programming language. ACM SIG-PLAN Not. 14, 6.

ICHBIAH, J., BARNES, J., FIRTH, R., AND WOODGER, M. 1991. Rationale for the Design of theAda Programming Language. Cambridge University Press, Cambridge, Mass.

INTERMETRICS. 1994. Ada Reference Manual: Language and Standard Libraries. Version6.0. Intermetrics, Inc., Cambridge, Mass.

INTERMETRICS. 1995. Ada 95 Rationale. Intermetrics, Inc., Cambridge, Mass.JOHNSTON, J. B. 1971. The contour model of block structured processes. In Proceedings ofthe ACM Symposium on Data Structures in Programming Languages. ACM SIGPLAN Not.(Feb.), 35–82.

LOPES, A. V., FELDMAN, M. B., AND HELLER, R. S. 1993. A controlled experiment withsoftware for teaching Ada tasking. In Proceedings of Tri-Ada 93. ACM SIGAda, New York.

ORGANICK, E. I., FORSYTHE, A. I., AND PLUMMER, R. P. 1978. Programming LanguageStructures. 2nd ed. Academic Press, New York.

OWICKI, S. AND GRIES, D. 1976. An axiomatic proof technique for parallel programs. ActaInformatica 6, 4, 319–340.

SHUMATE, K. 1988. Understanding Concurrency in Ada. McGraw-Hill, New York.THOMAS, J. W. AND ORGANICK, E. I. 1974. Visible semantics for programming languages. InProceedings of the 1974 ACM National Conference. ACM, New York, 416–421.

UNITED STATES DEPARTMENT OF DEFENSE. 1983. Reference Manual for the Ada ProgrammingLanguage. ANSI/MIL-STD-1815A. United States Dept. of Defense, Washington, D.C.

Received October 1994; revised June 1995 and July 1996; accepted August 1996

110 • Laura K. Dillon

ACM Transactions on Software Engineering and Methodology, Vol. 6, No. 1, January 1997.


Recommended