From: Reto Buerki on
Hi,

The test code below implements a simple alarm clock which should wakeup
the task waiting on the "Wait_For_Wakeup" entry.

With GNAT 4.3.2 on Debian/stable this does not work. The main task
remains queued on the entry even though the Wakeup-handler is invoked
correctly by the Timing_Event.

Compiling the same code with GNAT 4.4.1-5 (on Debian/SID) or GNAT GPL
2009 leads to the expected behavior: The main task terminates after the
alarm has fired. This indicates a bug in FSF GNAT 4.3.2.

--------------------

with Ada.Text_IO;
with Alarm_Clock;

procedure Main is
My_Alarm : Alarm_Clock.Alarm_Type;
begin
Ada.Text_IO.Put_Line ("Starting alarm ...");
My_Alarm.Start;

Ada.Text_IO.Put_Line ("Waiting for alarm ...");
My_Alarm.Wait_For_Wakeup;

Ada.Text_IO.Put_Line ("ALARM!!");
end Main;

--------------------

with Ada.Real_Time.Timing_Events;

package Alarm_Clock is

use Ada.Real_Time;

protected type Alarm_Type is
procedure Start;
procedure Wakeup (Event : in out Timing_Events.Timing_Event);
entry Wait_For_Wakeup;
private
Timer : Timing_Events.Timing_Event;
Alarm_Fired : Boolean := False;
end Alarm_Type;

end Alarm_Clock;

--------------------

package body Alarm_Clock is

protected body Alarm_Type is
procedure Start is
begin
Timer.Set_Handler (In_Time => Seconds (3),
Handler => Wakeup'Access);
end Start;

procedure Wakeup (Event : in out Timing_Events.Timing_Event) is
begin
Alarm_Fired := True;
end Wakeup;

entry Wait_For_Wakeup when Alarm_Fired is
begin
null;
end Wait_For_Wakeup;
end Alarm_Type;

end Alarm_Clock;

--------------------

Avoiding the usage of Ada.Real_Time.Timing_Events by implementing a
delaying task does not solve the problem. So it's not Ada real time related.

We suspect that the Alarm_Fired-barrier of the Wait_For_Wakeup entry is
not correctly re-evaluated after the Wakeup()-handler has been called.

Switching the compiler is a solution we consider as last resort.

Does anybody know what the actual problem could be? Is there a different
way to implement the same functionality or a "workaround" which would
avoid this problem?

Thanks in advance,
- reto
From: Dmitry A. Kazakov on
On Thu, 24 Sep 2009 19:02:14 +0200, Reto Buerki wrote:

> The test code below implements a simple alarm clock which should wakeup
> the task waiting on the "Wait_For_Wakeup" entry.
>
> With GNAT 4.3.2 on Debian/stable this does not work. The main task
> remains queued on the entry even though the Wakeup-handler is invoked
> correctly by the Timing_Event.
>
> Compiling the same code with GNAT 4.4.1-5 (on Debian/SID) or GNAT GPL
> 2009 leads to the expected behavior: The main task terminates after the
> alarm has fired. This indicates a bug in FSF GNAT 4.3.2.

It also works under GNAT Pro 6.2.1.

[...]

> Avoiding the usage of Ada.Real_Time.Timing_Events by implementing a
> delaying task does not solve the problem. So it's not Ada real time related.
>
> We suspect that the Alarm_Fired-barrier of the Wait_For_Wakeup entry is
> not correctly re-evaluated after the Wakeup()-handler has been called.
>
> Switching the compiler is a solution we consider as last resort.

Switching to a never version looks plausible to me.

> Does anybody know what the actual problem could be? Is there a different
> way to implement the same functionality or a "workaround" which would
> avoid this problem?

I was always wondering the rationale behind introducing
Ada.Real_Time.Timing_Events. The delay statement does the very same thing:

with Ada.Real_Time; use Ada.Real_Time;
...
Event : Time;
begin
...
Put_Line ("Starting alarm ...");
Event := Clock + To_Time_Span (3.0);

Put_Line ("Waiting for alarm ...");
delay until Event;
Put_Line ("ALARM!!");

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Brad Moore on
Dmitry A. Kazakov wrote:
> I was always wondering the rationale behind introducing
> Ada.Real_Time.Timing_Events. The delay statement does the very same thing:
>
> with Ada.Real_Time; use Ada.Real_Time;
> ...
> Event : Time;
> begin
> ...
> Put_Line ("Starting alarm ...");
> Event := Clock + To_Time_Span (3.0);
>
> Put_Line ("Waiting for alarm ...");
> delay until Event;
> Put_Line ("ALARM!!");
>

In this trivial example, a simple delay is probably all you need.

However, consider a slightly different twist on the alarm example.
Suppose you have a burglar alarm where if you trip the alarm
you have 1 minute to correctly enter the security code, or
the police are called. If you correctly enter the code in time
the alarm is canceled. This gets you into the realm where
the timing events package is more useful.

You could accomplish this also using a task to monitor the timeout, but
that could be considered a "heavier" approach depending on the
implementation of the timing events package. Timing events provide a
simpler abstraction, if all you want is an event to fire at sometime in
the future. Plus if you have many such sort of events in a program, you
could potentially save yourself from having to add tasks for each event
you want to monitor, resulting in less system resources being used.

Brad

--------------------------------------------

eg.

with Ada.Text_IO; use Ada;

with Burglar_Alarm;

procedure Test_Timers is
My_Alarm : Burglar_Alarm.Alarm_Type;
Code : Burglar_Alarm.Security_Code_Type;
begin
Text_IO.Put_Line ("Tripping alarm ...");
Burglar_Alarm.Trip (My_Alarm);

while Burglar_Alarm.Is_Tripped (My_Alarm) loop
begin

Text_IO.Put_Line ("Enter Security Code: ");
Code := Burglar_Alarm.Security_Code_Type'Value (Text_IO.Get_Line);
Burglar_Alarm.Disarm (Alarm => My_Alarm,
Key => Code);
exception
when Constraint_Error =>
null; -- Silently ignore bad codes
end;
end loop;

Text_IO.Put_Line ("Entered Correct Code!");

end Test_Timers;

------------------------------------------------------------

with Ada.Real_Time.Timing_Events; use Ada.Real_Time;

package Burglar_Alarm is

type Alarm_Type is limited private;

procedure Trip
(Alarm : in out Alarm_Type);

type Security_Code_Type is range 0 .. 9999;

procedure Disarm
(Alarm : in out Alarm_Type;
Key : Security_Code_Type);
-- Once an Alarm has been tripped, you have
-- 1 minute to enter the security code to
-- disable the alarm, or else the police are called

function Is_Tripped (Alarm : Alarm_Type) return Boolean;

private
protected type Alarm_Type is
function Is_Tripped return Boolean;
procedure Start_Timer;
procedure Disarm (Key : Security_Code_Type);
procedure Call_Police (Event : in out Timing_Events.Timing_Event);
private
Timer : Timing_Events.Timing_Event;
Tripped : Boolean := False;
Sound_Alarm : Boolean := False;
Security_Code : Security_Code_Type := 1234; -- set to some
unique value;
end Alarm_Type;
end Burglar_Alarm;

----------------------------------------------------------

with Ada.Text_IO; use Ada;
package body Burglar_Alarm is

protected body Alarm_Type is

procedure Call_Police (Event : in out Timing_Events.Timing_Event) is
pragma Unreferenced (Event);
begin
Text_IO.Put_Line ("Come out with your hands up!");
end Call_Police;

procedure Disarm (Key : Security_Code_Type) is
Cancelled : Boolean;
begin
if Key = Security_Code then
Timer.Cancel_Handler (Cancelled);
Tripped := False;
end if;
end Disarm;

function Is_Tripped return Boolean is
begin
return Tripped;
end Is_Tripped;

procedure Start_Timer is
begin
Tripped := True;
Timer.Set_Handler (In_Time => Minutes (1),
Handler => Call_Police'Access);
end Start_Timer;

end Alarm_Type;

procedure Disarm
(Alarm : in out Alarm_Type;
Key : Security_Code_Type) is
begin
Alarm.Disarm (Key);
end Disarm;

function Is_Tripped (Alarm : Alarm_Type) return Boolean is
begin
return Alarm.Is_Tripped;
end Is_Tripped;

procedure Trip (Alarm : in out Alarm_Type) is
begin
Alarm.Start_Timer;
end Trip;

end Burglar_Alarm;
From: Dmitry A. Kazakov on
On Fri, 25 Sep 2009 02:50:32 -0600, Brad Moore wrote:

> Dmitry A. Kazakov wrote:
>> I was always wondering the rationale behind introducing
>> Ada.Real_Time.Timing_Events. The delay statement does the very same thing:
>>
>> with Ada.Real_Time; use Ada.Real_Time;
>> ...
>> Event : Time;
>> begin
>> ...
>> Put_Line ("Starting alarm ...");
>> Event := Clock + To_Time_Span (3.0);
>>
>> Put_Line ("Waiting for alarm ...");
>> delay until Event;
>> Put_Line ("ALARM!!");
>
> In this trivial example, a simple delay is probably all you need.
>
> However, consider a slightly different twist on the alarm example.
> Suppose you have a burglar alarm where if you trip the alarm
> you have 1 minute to correctly enter the security code, or
> the police are called. If you correctly enter the code in time
> the alarm is canceled. This gets you into the realm where
> the timing events package is more useful.
>
> You could accomplish this also using a task to monitor the timeout, but
> that could be considered a "heavier" approach depending on the
> implementation of the timing events package. Timing events provide a
> simpler abstraction, if all you want is an event to fire at sometime in
> the future. Plus if you have many such sort of events in a program, you
> could potentially save yourself from having to add tasks for each event
> you want to monitor, resulting in less system resources being used.

You must do it per a monitor task anyway. Your design is not scalable, and
the code is not conformant to Ada, because you are using potentially
blocking Put_Line within a protected action (in Call_Police).

When an alarm is triggered, that happens at the context of a protected
action. The protected action is quite limited to what you could do there.
That is only non-blocking and *very* short stuff. This means that you won't
be able to "call police" from there. You could set some flags, but you
could not initiate any "non-instant" action otherwise than by releasing a
task. Non-instant actions are for tasks. You need a task. If the key input
task gets blocked beyond reach of asynchronous transfer of control, which
is your case, because Ada does not require Text_IO abortable (yet
misleadingly provides an example of in RM (:-)), anyway, that means, you
inescapably need a second task.

-----------
Timing_Events does not have any advantages over delay or asynchronous
transfer of control. I guess it was provided for some low-level stuff like
interrupts from hardware timers. However there was already a support for
interrupts mapped into protected operations, so it really puzzles me why
Timing_Events were added.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Ludovic Brenta on
Dmitry A. Kazakov wrote on comp.lang.ada:
> Timing_Events does not have any advantages over delay or asynchronous
> transfer of control. I guess it was provided for some low-level stuff like
> interrupts from hardware timers. However there was already a support for
> interrupts mapped into protected operations, so it really puzzles me why
> Timing_Events were added.

The reasons are explained in AI95-297. It seems you've never written
any low-level code before, or cared about it?

[1] http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ais/ai-00297.txt?rev=1.25

--
Ludovic Brenta.