Next: Elaboration-related Compiler Switches, Previous: Resolving Elaboration Circularities, Up: Elaboration Order Handling in GNAT [Contents][Index]
The model of execution in Ada dictates that elaboration must first take place, and only then can the main program be started. Tasks which are activated during elaboration violate this model and may lead to serious concurrent problems at elaboration time.
A task can be activated in two different ways:
Since the elaboration of a partition is performed by the environment task servicing that partition, any tasks activated during elaboration may be in a race with the environment task, and lead to unpredictable state and behavior. The static model seeks to avoid such interactions by assuming that all code in the task body is executed at elaboration time, if the task was activated by elaboration code.
package Decls is task Lib_Task is entry Start; end Lib_Task; type My_Int is new Integer; function Ident (M : My_Int) return My_Int; end Decls;
with Utils; package body Decls is task body Lib_Task is begin accept Start; Utils.Put_Val (2); end Lib_Task; function Ident (M : My_Int) return My_Int is begin return M; end Ident; end Decls;
with Decls; package Utils is procedure Put_Val (Arg : Decls.My_Int); end Utils;
with Ada.Text_IO; use Ada.Text_IO; package body Utils is procedure Put_Val (Arg : Decls.My_Int) is begin Put_Line (Arg'Img); end Put_Val; end Utils;
with Decls; procedure Main is begin Decls.Lib_Task.Start; end Main;
When the above example is compiled with the static model, an elaboration circularity arises:
error: elaboration circularity detected info: "decls (body)" must be elaborated before "decls (body)" info: reason: implicit Elaborate_All in unit "decls (body)" info: recompile "decls (body)" with -gnatel for full details info: "decls (body)" info: must be elaborated along with its spec: info: "decls (spec)" info: which is withed by: info: "utils (spec)" info: which is withed by: info: "decls (body)"
In the above example, Decls
must be elaborated prior to Main
by virtue
of a with clause. The elaboration of Decls
activates task Lib_Task
. The
static model conservatibely assumes that all code within the body of
Lib_Task
is executed, and generates an implicit Elaborate_All
pragma
for Units
due to the call to Utils.Put_Val
. The pragma implies that
both the spec and body of Utils
, along with any units they `with',
must be elaborated prior to Decls
. However, Utils
’s spec `with's
Decls
, implying that Decls
must be elaborated before Utils
. The end
result is that Utils
must be elaborated prior to Utils
, and this
leads to a circularity.
In reality, the example above will not exhibit an ABE problem at run time.
When the body of task Lib_Task
is activated, execution will wait for entry
Start
to be accepted, and the call to Utils.Put_Val
will not take place
at elaboration time. Task Lib_Task
will resume its execution after the main
program is executed because Main
performs a rendezvous with
Lib_Task.Start
, and at that point all units have already been elaborated.
As a result, the static model may seem overly conservative, partly because it
does not take control and data flow into account.
When faced with a task elaboration circularity, a programmer has several options available:
The dynamic model does not generate implicit Elaborate
and
Elaborate_All
pragmas. Instead, it will install checks prior to every
call in the example above, thus verifying the successful elaboration of
Utils.Put_Val
in case the call to it takes place at elaboration time.
The dynamic model is enabled with compiler switch -gnatE
.
Relocating tasks in their own separate package could decouple them from dependencies that would otherwise cause an elaboration circularity. The example above can be rewritten as follows:
package Decls1 is -- new task Lib_Task is entry Start; end Lib_Task; end Decls1;
with Utils; package body Decls1 is -- new task body Lib_Task is begin accept Start; Utils.Put_Val (2); end Lib_Task; end Decls1;
package Decls2 is -- new type My_Int is new Integer; function Ident (M : My_Int) return My_Int; end Decls2;
with Utils; package body Decls2 is -- new function Ident (M : My_Int) return My_Int is begin return M; end Ident; end Decls2;
with Decls2; package Utils is procedure Put_Val (Arg : Decls2.My_Int); end Utils;
with Ada.Text_IO; use Ada.Text_IO; package body Utils is procedure Put_Val (Arg : Decls2.My_Int) is begin Put_Line (Arg'Img); end Put_Val; end Utils;
with Decls1; procedure Main is begin Decls1.Lib_Task.Start; end Main;
The original example uses a single task declaration for Lib_Task
. An
explicit task type declaration and a properly placed task object could avoid
the dependencies that would otherwise cause an elaboration circularity. The
example can be rewritten as follows:
package Decls is task type Lib_Task is -- new entry Start; end Lib_Task; type My_Int is new Integer; function Ident (M : My_Int) return My_Int; end Decls;
with Utils; package body Decls is task body Lib_Task is begin accept Start; Utils.Put_Val (2); end Lib_Task; function Ident (M : My_Int) return My_Int is begin return M; end Ident; end Decls;
with Decls; package Utils is procedure Put_Val (Arg : Decls.My_Int); end Utils;
with Ada.Text_IO; use Ada.Text_IO; package body Utils is procedure Put_Val (Arg : Decls.My_Int) is begin Put_Line (Arg'Img); end Put_Val; end Utils;
with Decls; package Obj_Decls is -- new Task_Obj : Decls.Lib_Task; end Obj_Decls;
with Obj_Decls; procedure Main is begin Obj_Decls.Task_Obj.Start; -- new end Main;
The issue exhibited in the original example under this section revolves
around the body of Lib_Task
blocking on an accept statement. There is
no rule to prevent elaboration code from performing entry calls, however in
practice this is highly unusual. In addition, the pattern of starting tasks
at elaboration time and then immediately blocking on accept or select
statements is quite common.
If a programmer knows that elaboration code will not perform any entry calls, then the programmer can indicate that the static model should not process the remainder of a task body once an accept or select statement has been encountered. This behavior can be specified by a configuration pragma:
pragma Restrictions (No_Entry_Calls_In_Elaboration_Code);
In addition to the change in behavior with respect to task bodies, the static model will verify that no entry calls take place at elaboration time.