with Ada.Finalization;
package Txn_Pkg is
type Txn_Status is (Incomplete, Failed, Succeeded);
type Transaction is new Ada.Finalization.Limited_Controlled with private;
procedure Finalize (Txn : in out transaction);
procedure Set_Status (Txn : in out Transaction;
Status : in Txn_Status);
private
type Transaction is new Ada.Finalization.Limited_Controlled with
record
Status : Txn_Status := Incomplete;
pragma Atomic (Status);
. . . -- More components
end record;
end Txn_Pkg;
-----------------------------------------------------------------------------
package body Txn_Pkg is
procedure Finalize (Txn : in out Transaction) is
begin
-- Finalization runs with abort and ATC deferred
if Txn.Status = Succeeded then
Commit (Txn);
else
Rollback (Txn);
end if;
end Finalize;
. . . -- body of procedure Set_Status
end Txn_Pkg;
----------------------------------------------------------------------------
-- sample code block showing how Txn_Pkg could be used:
declare
Database_Txn : Transaction;
-- declare a transaction, will commit or abort during finalization
begin
select -- wait for a cancel key from the input device
Input_Device.Wait_For_Cancel;
-- the Status remains Incomplete, so that the transaction will not commit
then abort -- do the transaction
begin
Read (Database_Txn, . . .);
Write (Database_Txn, . . .);
. . .
Set_Status (Database_Txn, Succeeded);
-- set status to ensure the transaction is committed
exception
when others =>
Ada.Text_IO.Put_Line ("Operation failed with unhandled exception:");
Set_Status (Database_Txn, Failed);
end;
end select;
-- Finalize on Database_Txn will be called here and, based on the recorded
-- status, will either commit or abort the transaction.
end;

rationale

When an abort statement is executed, there is no way
to know what the targeted task was doing beforehand. Data for
which the target task is responsible might be left in an inconsistent
state. The overall effect on the system of aborting a task in
such an uncontrolled way requires careful analysis. The system
design must ensure that all tasks depending on the aborted task
can detect the termination and respond appropriately.

Tasks are not aborted until they reach an abort completion point
such as beginning or end of elaboration,
a delay statement, an accept statement, an entry
call, a select statement, task allocation, or the execution
of an exception handler. Consequently, the abort statement
might not release processor resources as soon as you might expect.
It also might not stop a runaway task because the task might be executing an infinite loop containing
no abort completion points. There is no guarantee that a task
will not abort until an abort completion point in multiprocessor
systems, but the task will almost always stop running right away.

An asynchronous select statement allows an external event
to cause a task to begin execution at a new point, without having
to abort and restart the task (Rationale 1995, §9.3). Because
the triggering statement and the abortable statement execute in
parallel until one of them completes and forces the other to be
abandoned, you need only one thread of control. The asynchronous
select statement improves maintainability because the
abortable statements are clearly delimited and the transfer cannot
be mistakenly redirected.

In task bodies and in the abortable part of an asynchronous select,
you should avoid assigning to nonatomic global objects, primarily
because of the risk of an abort occurring before the nonatomic
assignment completes. If you have one or more abort statements
in your application and the assignment is disrupted, the target
object can become abnormal, and subsequent uses of the object
lead to erroneous execution (Ada Reference Manual 1995, §9.8).
In the case of scalar objects, you can use the attribute 'Valid,
but there is no equivalent attribute for nonscalar objects. (See
Guideline 5.9.1 for a discussion of the 'Valid attribute.)
You also can still safely assign to local objects and call operations
of global protected objects.