Rationale for Ada 2005: Tasking and Real-Time

RUSTOP
BACKNEXT

ENG

2. Task termination

@ In the Introduction we mentioned the problem of how tasks can have a silent death in Ada 95. This happens if a task raises an exception which is not handled by the task itself. Tasks may also terminate because of going abnormal as well as terminating normally. The detection of task termination and its causes can be monitored in Ada 2005 by the package Ada.Task_Termination whose specification is essentially

  1        with Ada.Task_Identification; use Ada.Task_Identification;
  2        with Ada.Exceptions; use Ada.Exceptions;
  3        package Ada.Task_Termination is
  4                pragma Preelaborable(Task_Termination);
  5                type Cause_Of_Termination is (Normal, Abnormal, Unhandled_Exception);
  6                type Termination_Handler is access protected
  7                procedure (Cause : in Cause_Of_Termination; T : in Task_Id; X : in Exception_Occurrence);
  8                procedure Set_Dependents_Fallback_Handler (Handler : in Termination_Handler);
  9                function Current_Task_Fallback_Handler return Termination_Handler;
 10                procedure Set_Specific_Handler (T : in Task_Id; Handler : in Termination_Handler);
 11                function Specific_Handler (T : in Task_Id) return Termination_Handler;
 12        end Ada.Task_Termination;

@ (Note that the above includes use clauses in order to simplify the presentation; the actual package does not have use clauses. We will use a similar approach for the other predefined packages described in this paper.) The general idea is that we can associate a protected procedure with a task. The protected procedure is then invoked when the task terminates with an indication of the reason passed via its parameters.

@ The protected procedure is identified by using the type Termination_Handler which is an access type referring to a protected procedure.

@ The association can be done in two ways. Thus (as in the Introduction) we might declare a protected object Grim_Reaper

  1        protected Grim_Reaper is
  2                procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence);
  3        end Grim_Reaper;

@ which contains the protected procedure Last_Gasp. Note that the parameters of Last_Gasp match those of the access type Termination_Handler.

@ We can then nominate Last_Gasp as the protected procedure to be called when the specific task T dies by

  1        Set_Specific_Handler (T'Identity, Grim_Reaper.Last_Gasp'Access);

@ Alternatively we can nominate Last_Gasp as the protected procedure to be called when any of the tasks dependent on the current task becomes terminated by writing Set_Dependents_Fallback_Handler(Grim_Reaper.Last_Gasp'Access); Note that a task is not dependent upon itself and so this does not set a handler for the current task.

@ Thus a task can have two handlers. A fallback handler and a specific handler and either or both of these can be null. When a task terminates (that is after any finalization but just before it vanishes), the specific handler is invoked if it is not null. If the specific handler is null, then the fallback handler is invoked unless it too is null. If both are null then no handler is invoked.

@ The body of protected procedure Last_Gasp might then output various diagnostic messages

  1        procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence) is
  2        begin
  3                case C is
  4                        when Normal =>
  5                                null;
  6                        when Abnormal =>
  7                                Put ("Something nasty happened to task ");
  8                                Put_Line (Image(T));
  9                        when Unhandled_Exception =>
 10                                Put ("Unhandled exception occurred in task ");
 11                                Put_Line (Image(T));
 12                                Put (Exception_Information(X));
 13                end case;
 14        end Last_Gasp;

@ There are three possible reasons for termination, it could be normal, abnormal (caused by abort), or because of propagation of an unhandled exception. In the last case the parameter X gives details of the exception occurrence whereas in the other cases X has the value Null_Occurrence.

@ Initially both specific and fallback handlers are null for all tasks. However, note that if a fallback handler has been set for all dependent tasks of T then the handler will also apply to any task subsequently created by T or one of its descendants. Thus a task can be born with a fallback handler already in place.

@ If a new handler is set then it replaces any existing handler of the appropriate kind. Calling either setting procedure with null for the handler naturally sets the appropriate handler to null.

@ The current handlers can be found by calling the functions Current_Task_Fallback_Handler or Specific_Handler; they return null if the handler is null.

@ It is important to realise that the fallback handlers for the tasks dependent on T need not all be the same since one of the dependent tasks of T might set a different handler for its own dependent tasks.

@ Thus the fallback handlers for a tree of tasks can be different in various subtrees. This structure is reflected by the fact that the determination of the current fallback handler of a task is in fact done by searching recursively the tasks on which it depends.

@ Note that we cannot directly interrogate the fallback handler of a specific task but only that of the current task. Moreover, if a task sets a fallback handler for its dependents and then enquires of its own fallback handler it will not in general get the same answer because it is not one of its own dependents.

@ It is important to understand the situation regarding the environment task. This unnamed task is the task that elaborates the library units and then calls the main subprogram. Remember that library tasks (that is tasks declared at library level) are activated by the environment task before it calls the main subprogram.

@ Suppose the main subprogram calls the setting procedures as follows

  1        procedure Main is
  2                protected RIP is
  3                        protected procedure One ( ... );
  4                        protected procedure Two ( ... );
  5                end;
  6                ...
  7        begin
  8                Set_Dependents_Fallback_Handler (RIP.One'Access);
  9                Set_Specific_Handler (Current_Task, RIP.Two'Access);
 10                ...
 11        end Main;

@ The specific handler for the environment task is then set to Two (because Current_Task is the environment task at this point) but the fallback handler for the environment task is null. On the other hand the fallback handler for all other tasks in the program including any library tasks is set to One.

@ Note that it is not possible to set the fallback handler for the environment task.

@ The astute reader will note that there is actually a race condition here since a library task might have terminated before the handler gets set. We could overcome this by setting the handler as part of the elaboration code thus

  1        package Start_Up is
  2                pragma Elaborate_Body;
  3        end;
  4        with Ada.Task_Termination; use Ada.Task_Termination;
  5        package body Start_Up is
  6        begin
  7                Set_Dependents_Fallback_Handler (RIP.One'Access);
  8        end Start_Up;
  9        with Start_Up;
 10        pragma Elaborate (Start_Up);
 11        package Library_Tasks is
 12                ... -- declare library tasks here
 13        end;

@ Note how the use of pragmas Elaborate_Body and Elaborate ensures that things get done in the correct order.

@ Some minor points are that if we try to set the specific handler for a task that has already terminated then Tasking_Error is raised. And if we try to set the specific handler for the null task, that is call Set_Specific_Handler with parameter T equal to Null_Task_Id, then Program_Error is raised. These exceptions are also raised by calls of the function Specific_Handler in similar circumstances.

Rationale for Ada 2005: Tasking and Real-Time

@ENGRUSTOPBACKNEXT

2. Завершение задачи

@ Во Введении мы упоминали проблему которая возникает у задач в процессе т.н. тихой смерти на Аде 95. Это случается когда в задаче возникает исключение которое не обрабатывается непосредственно задачей. Задачи могут также закончиться аварийно так же как и обычно. Обнаружение завершения задачи и его причин может быть проведено в Аде 2005 при помощи пакета Ada.Task_Termination, спецификация которого следующая:

  1        with Ada.Task_Identification; use Ada.Task_Identification;
  2        with Ada.Exceptions; use Ada.Exceptions;
  3        package Ada.Task_Termination is
  4                pragma Preelaborable(Task_Termination);
  5                type Cause_Of_Termination is (Normal, Abnormal, Unhandled_Exception);
  6                type Termination_Handler is access protected
  7                procedure (Cause : in Cause_Of_Termination; T : in Task_Id; X : in Exception_Occurrence);
  8                procedure Set_Dependents_Fallback_Handler (Handler : in Termination_Handler);
  9                function Current_Task_Fallback_Handler return Termination_Handler;
 10                procedure Set_Specific_Handler (T : in Task_Id; Handler : in Termination_Handler);
 11                function Specific_Handler (T : in Task_Id) return Termination_Handler;
 12        end Ada.Task_Termination;

@ (Отметим, что вышеупомянутое включает выражения использования чтобы упростить представление; у фактического пакета нет выражений использования. Мы будем использовать подобный подход для других предопределенных пакетов, описанных в этой статье). Основная идея состоит в том, что мы можем связать защищенную процедуру с задачей. Защищенная процедура вызывается когда задача заканчивается с индикацией причины, которая передаётся через ее параметры.

@ Защищенная процедура идентифицируется типом Termination_Handler, который является ссылочным типом обращающимся к защищенной процедуре.

@ Ассоциация может быть сделана двумя способами. Таким образом (как во Введении) мы могли бы объявить защищенный объект Grim_Reaper:

  1        protected Grim_Reaper is
  2                procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence);
  3        end Grim_Reaper;

@ который содержит защищенную процедуру Last_Gasp. Отметим, что параметры Last_Gasp соответствуют таковым из ссылочного типа Termination_Handler.

@ Мы можем тогда назначить Last_Gasp как защищенную процедуру, которая будет вызвана, когда определенная задача T помрёт:

  1        Set_Specific_Handler (T'Identity, Grim_Reaper.Last_Gasp'Access);

@ Альтернативно мы можем назначить Last_Gasp как защищенную процедуру, которая будет вызываться когда любая из задач, зависящих от текущей задачи завершится написав Set_Dependents_Fallback_Handler (Grim_Reaper. Last_Gasp'Access); Отметим, что задача не может установить обработчик для самой себя.

@ Таким образом, у задачи может быть два обработчика. Обработчик системы восстановления и специфический обработчик. При завершении задачи непосредственно перед тем как задача исчезает вызвается специфический обработчик, если он не пустой. В противном случае вызывается обработчик системы восстановления, если он, в свою очередь, сам не является пустым. Если оба обработчика - пустые тогда, никакой обработчик не вызвается.

@ В теле защищенной процедуры Last_Gasp можно вывести различные диагностические сообщения:

  1        procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence) is
  2        begin
  3                case C is
  4                        when Normal =>
  5                                null;
  6                        when Abnormal =>
  7                                Put ("Something nasty happened to task ");
  8                                Put_Line (Image(T));
  9                        when Unhandled_Exception =>
 10                                Put ("Unhandled exception occurred in task ");
 11                                Put_Line (Image(T));
 12                                Put (Exception_Information(X));
 13                end case;
 14        end Last_Gasp;

@ Есть три возможных варианта завершения: нормальное, аварийное (вызванное аварийным прекращением работы), и из-за распространения необработанного исключения. В последнем случае параметр X сообщает подробности вхождения исключения, тогда как в других случаях X имеет значение Null_Occurrence.

@ Первоначально и специфический и обработчик системы восстановления являются пустыми для всех задач. Однако, если обработчик системы восстановления был установлен для всех зависимых задач T тогда, обработчик будет также относиться к любой задаче впоследствии созданной T или одним из его потомков. Таким образом, задача уже может родиться уже с обработчиком системы восстановления.

@ Если устанавливается новый обработчик, тогда он заменяет любой существующий обработчик соответствующего вида. Вызов любой процедуры установки с пустым указателем для обработчика естественно делает соответствующий обработчик пустым.

@ Текущие обработчики могут быть получены вызовом функции Current_Task_Fallback_Handler или Specific_Handler; они возвращают пустой указатель, если обработчик - пустой.

@ Важно понять, что обработчики системы восстановления для задач, зависящих от T, не обязательно должны быть одинаковыми, так как любая из зависимых задач T может установить собственный обработчик для своих собственных зависимых задач.

@ Таким образом, обработчики системы восстановления для дерева задач могут быть различными в различных поддеревьях. Эта структура отражена тем фактом, что определение текущего обработчика системы восстановления задачи фактически делается рекурсивным поиском в задачах от которых он зависит.

@ Отметим, что мы не можем непосредственно опросить обработчик системы восстановления любой задачи, мы можем это сделать только для текущей задачи. Кроме того, если задача установит обработчик системы восстановления для своих потомков и затем спросит свой собственный обработчика системы восстановления, то она не получит тот же самый ответ, потому что это не один из его собственных потомков.

@ Важно понять ситуацию относительно задачной среды. Эта неназванная задача - задача, которая разрабатывает библиотечные модули и затем вызывает основную подпрограмму. Помните, что библиотечные задачи (которые являются задачами, объявленными на библиотечном уровне) активизированы задачей среды прежде, чем будет вызвана основная подпрограмма.

@ Предположим, что основная подпрограмма вызывает процедуры установки следующим образом:

  1        procedure Main is
  2                protected RIP is
  3                        protected procedure One ( ... );
  4                        protected procedure Two ( ... );
  5                end;
  6                ...
  7        begin
  8                Set_Dependents_Fallback_Handler (RIP.One'Access);
  9                Set_Specific_Handler (Current_Task, RIP.Two'Access);
 10                ...
 11        end Main;

@ Здесь специфический обработчик для задачной среды установлен Two (потому что Current_Task - задача среды в этом пункте), но обработчик системы восстановления для задачи среды - пустой. С другой стороны обработчик системы восстановления для всех других задач в программе, включая любые библиотечные задачи установлен One.

@ Отметим, что не возможно установить обработчик системы восстановления для задачи среды.

@ Проницательный читатель отметит, что при определённых обстоятельствах библиотечная задача может закончится раньше чем обработчик будет установлен. Мы можем это преодолеть установив обработчик как часть кода элаборации следующим образом:

  1        package Start_Up is
  2                pragma Elaborate_Body;
  3        end;
  4        with Ada.Task_Termination; use Ada.Task_Termination;
  5        package body Start_Up is
  6        begin
  7                Set_Dependents_Fallback_Handler (RIP.One'Access);
  8        end Start_Up;
  9        with Start_Up;
 10        pragma Elaborate (Start_Up);
 11        package Library_Tasks is
 12                ... -- declare library tasks here
 13        end;

@ Отметим, как использование прагм Elaborate_Body и Elaborate гарантирует, что вещи сделаны в правильном порядке.

@ Некоторые незначительные пункты - то, что, если мы пытаемся установить специфический обработчик для задачи, которая уже завершена возбуждается исключение Tasking_Error. И если мы пытаемся установить специфический обработчик для пустой задачи, которая является вызовом Set_Specific_Handler с параметром T равным Null_Task_Id, тогда возбуждается исключение Program_Error. Эти исключения также возбуждаются вызовами функции Specific_Handler при подобных обстоятельствах.

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:55

шлагбаум, сравни цены - найди где дешевле и ближе купить. . .