Starting tasks again - ada

I recently started Ada programming and now I'm stuck.
I created a program with multiple tasks. The main-task is managing incoming communication and as a consequence starts working-tasks or transfers data to the working-tasks.
The working-tasks are all of the same kind but with different identifiers.
They do their work and should finish after that. For example:
task body Access_Protected is
begin
accept Start(foo: in Integer; foo2: out Integer)
do something
end Start;
while Go_loop loop
select
accept Quit do
Go_loop := false;
end Quit;
or
accept Insert(foo3: in Integer)
do something
if something = 0 then
Go_loop := false;
end if;
end Insert;
or delay 2.0;
end select;
end loop;
end Access_Protected;
I understand that the working-task should be terminated when the Go_loop is finished. Am I right?
It works to start the task one time but when the main-task tries to restart the working-task by calling the Start procedure, nothing happens.
Can someone please tell me which point I am missing.

A task and subprogram are somewhat related in that when the body is completed the construct ends, this is to say that the construct ends with it's appropriate end; in the case of a procedure control returns to the caller, in the case of a function the exception PROGRAM_ERROR is raised, and in the case of a task the controlling "thread" terminates.
What's happening in your particular problem, it seems, boils down to the following:
Package Example is
Task Type Message_Task is
Entry Execute;
End Message_Task;
End Example;
Package Body Example is
Task Body Message_Task is
Use Ada.Text_IO;
Begin
accept Execute do
Put_Line( "Rendezvous!" );
end Execute;
delay 0.2; -- Stub delay.
Put_Line( "Finishing Task." );
-- Task Ends Here.
End Message_Task;
End Example;
--...
Test : Example.Message_Task;
--...
Test.Execute;
-- Test.Execute can't be accepted here because it can only accept "Execute"
-- the one time, as per the body's definition.
The reason that this really is like your problem is because, likewise once you say "X.Start(1,2)" another call to Start doesn't reset the position of the task's execution back up to that accept.
If you wanted the task to "stay alive" for further processing you could do one of two options.
Option 1 -- set up a 'protocol':
Package Example is
Task Type Message_Task is
Entry Initialization;
Entry Execute;
Entry Quit;
End Message_Task;
End Example;
Package Body Example is
Task Body Message_Task is
Use Ada.Text_IO;
Has_quit : Boolean := False;
Begin
Main:
loop
select
accept Initialization do
null;
end Initialization;
accept Execute do
null;
end Execute;
or
accept Quit do
Has_Quit := True;
end Quit;
end select;
Exit Main when Has_Quit;
end loop Main;
End Message_Task;
End Example;
Option 2 -- Allow termination.
Package Example is
Task Type Message_Task is
Entry Initialization;
Entry Execute;
End Message_Task;
End Example;
Package Body Example is
Task Body Message_Task is
Use Ada.Text_IO;
Has_quit : Boolean := False;
Begin
accept Initialization do
null;
end Initialization;
Main:
loop
select
accept Execute do
null;
end Execute;
or
terminate;
end select;
end loop Main;
End Message_Task;
End Example;
The subtle difference is Option 2 gets rid of the Quit entry, allowing the task to 'rest' on the terminate alternative while Option 1 is more explicit in control (and required in some cases), but requiring that Initialization & Execute be called in pairs.

A task only runs until it reaches the end of its main sequence of statements (ignoring various technicalities).
If you want a task to do something, and then pause until it receives an external trigger, you should put a loop around the statements you have in the task body.

Related

Gnat 2020 non-preemptive tasking windows

I am trying to achieve true non-preemptive tasking using gnat 2020 CE on a windows 10 environment. I have placed this in the gnat.adc file:
pragma Task_Dispatching_Policy(Non_Preemptive_FIFO_Within_Priorities);
Without this gnat.adc setting, the tasks switched back and forth a great deal, as you would expect with preemptive tasking. Putting the pragma in the gnat.adc file seemed to affect the granularity of the task switching on my test program below, in that it lengthened the time that each task executed consecutive loops, but they still switched over to each other eventually. Here is the test program:
with Ada.Text_IO; Use Ada.Text_Io;
procedure Test is
Task Type One is
End;
Task Type Two;
Task body One is
x : Integer := 0;
Begin
put_line("one");
Loop
x:=x+1;
if x > 10000000 Then
exit;
end if;
End Loop;
Put_line("Task one done, x=" & x'img);
End;
Task body Two is
x : Integer := 0;
Begin
put_line("two");
Loop
x:=x+1;
if x > 1000 Then
exit;
end if;
End Loop;
Put_line("Task two done, x=" & x'img);
End;
a : One;
B : two;
begin
Null;
End;
Here is the compile line:
gnatmake -gnat2012 -gnatX -f -g test.adb -gnatwA -I. -I.. -D obj
And here is the output:
one
two
Task two done, x= 1001
Task one done, x= 10000001
I expected the opposite, that task one would execute first, which it did, but that it would also finish first because there's no reason for it to yield to two without preemption. It looks to me like I am not actually getting non-preemptive tasking, and I would like to know why.
Thanks in advance.
edit
After looking at Jeffery's comment, I found the 2012 attribute 'with CPU', and produced test code :
With System.Multiprocessors; use System.Multiprocessors;
with Ada.Text_IO; Use Ada.Text_Io;
procedure Test is
Task Type One with Cpu=>1 is
End;
Task Type Two with Cpu=> 1 is
end;
x,y : integer := 0;
limit : integer := 1000000;
Task body One is
Begin
put_line("one");
Loop
if y > 0 then
raise tasking_error;
end if;
x:=x+1;
if x > limit Then
exit;
end if;
End Loop;
Put_line("Task one done, x=" & x'img);
Exception
When others =>
put_line("task one died, x=" & x'img);
End;
Task body Two is
Begin
put_line("two");
Loop
y:=y+1;
if y > limit Then
exit;
end if;
End Loop;
Put_line("Task two done, y=" & y'img);
Exception
When others =>
put_line("task two died");
End;
a : One;
B : two;
begin
put_line(Number_Of_CPUs'img & " cpu's");
While (x < limit+1) or (y < limit+1) loop
Delay 0.0;
End Loop;
put_line("main done, x " & x'img & " y " & y'img);
End;
which produces output
one
two
24 cpu's
task one died, x= 310528
Task two done, y= 1000001
^C
(of course, I have to ctl-c out since main never finishes.)
This happens whether or not I have the scheduling pragma in gnat.adc. Does Windows just not respect processor assignment and/or the scheduling pragma?

Ada suppress unreachable code or missing return

I have a tagged type that implements a number of functions. In one case I need one of these functions to instead enter an infinite loop. Unfortunately as far as I can tell there is no way for me to compile this such that it doesn't raise a warning. I wish to still use -gnatwe to ensure there are no warnings in my code, but how can I implement such a function.
Here is what the function looks like:
function Foo (This : Some_Type) return Some_Type'Class is
begin
loop
Do_Useful_Stuff_Indefinitely_With (This);
end loop;
-- return This; (if needed?)
end Foo;
I have tried pragma (No_Return) except that is only applicable for procedures (and the Foo function is used as a proper function elsewhere so must have the same signature).
I also tried pragma Suppress (All_Checks) but that still raised a warning for unreachable code or missing return statement error.
Is there any way whatsoever to have a once-off function that runs forever without raising a warning?
Taking the same example that Jean-François provided, you can avoid the warning by declaring and calling a "private" procedure (you don't have to declare it in spec) wrapping your loop as in the following :
package body foo is
procedure Infinite_Loop is
begin
loop
null;
end loop;
end Infinite_Loop;
function bar return integer is
begin
Infinite_Loop;
return 12;
end bar;
end foo;
pragma Suppress (All_Checks) acts on run-time checks. Won't help you there. Leave that alone unless you focus on performance, but then you have -p option to do it using command line switches
The pragma Suppress suppresses compiler-generated run-time checks. If a run-time check is disabled, an exception may be suppressed and undefined behavior could result. pragma Suppress is used at the programmer's risk.
You need the return statement, but you can wrap it around 2 pragma warnings statements (A case where have you tried turning it off and on again? works)
pragma warnings(off,"unreachable code");
return This;
pragma warnings(on,"unreachable code");
note that the text is optional but enables to filter other warnings that could occur (if needed). It's better since turning off all warnings is generally bad practice.
Note that you have to turn warnings on again after the statement.
Self-contained demo. foo.adb looks like:
package body foo is
function bar return integer is
begin
loop
null;
end loop;
pragma warnings(off,"unreachable code");
return 12;
pragma warnings(on,"unreachable code");
end bar;
end foo;
foo.ads looks like:
package foo is
function bar return integer;
end foo;
If I comment out the pragma lines:
$ gcc -c -gnatwe foo.adb
foo.adb:8:05: warning: unreachable code
uncommenting them removes the warning.

Call to a volatile function in interfering context is not allowed in SPARK

I'm currently learning Ada during a university course on real-time programming languages and have a question about SPARK.
I'm working on a project with a task that monitors an off-grid power supply. This task is crucial for machine safety and should therefore be as error free as possible, say proven with SPARK. I was able to get a few things running with other questions on stackoverflow but I still run into errors that I was not able to fix with quick searches in the user guide.
The error is call to a volatile function in interfering context is not allowed in SPARK with reference to the line if monitoring_interface.is_all_config_set then ... in
task body monitoring_task is
next_time : Time;
begin
-- Initialisation of next execution time
next_time := Clock;
-- Superloop
loop
Put_Line ("Run task monitoring");
-- Load monitor configuration
monitor_pfc_voltage.config := monitoring_interface.get_monitor_pfc_voltage_config;
monitor_pfc_current.config := monitoring_interface.get_monitor_pfc_current_config;
monitor_output_voltage.config := monitoring_interface.get_monitor_output_voltage_config;
monitor_output_current.config := monitoring_interface.get_monitor_output_current_config;
-- Check if module has been configured correctly
-- Don't do anything otherwise
if monitoring_interface.is_all_config_set then -- <= erroneous line
do_monitoring;
end if;
next_time := next_time + TASK_PERIOD;
delay until next_time;
end loop;
end monitoring_task;
The function is_all_config_set is defined within a protected type that I use for inter task communication.
package PSU_Monitoring is
... Declaration of some types (Monitor_Config_T) ...
protected type Monitoring_Interface_T is
function is_all_config_set return Boolean;
procedure set_monitor_pfc_voltage_config (new_monitor_config : in Monitor_Config_T);
function get_monitor_pfc_voltage_config return Monitor_Config_T;
procedure set_monitor_pfc_current_config (new_monitor_config : in Monitor_Config_T);
function get_monitor_pfc_current_config return Monitor_Config_T;
procedure set_monitor_output_voltage_config (new_monitor_config : in Monitor_Config_T);
function get_monitor_output_voltage_config return Monitor_Config_T;
procedure set_monitor_output_current_config (new_monitor_config : in Monitor_Config_T);
function get_monitor_output_current_config return Monitor_Config_T;
private
-- Configuration for PFC intermediate voltage
monitor_pfc_voltage_config : Monitor_Config_T;
monitor_pfc_voltage_config_set : Boolean := False;
-- Configuration for PFC inductor current
monitor_pfc_current_config : Monitor_Config_T;
monitor_pfc_current_config_set : Boolean := False;
-- Configuration for output voltage
monitor_output_voltage_config : Monitor_Config_T;
monitor_output_voltage_config_set : Boolean := False;
-- Configuration for output inductor current
monitor_output_current_config : Monitor_Config_T;
monitor_output_current_config_set : Boolean := False;
end Monitoring_Interface_T;
monitoring_interface : Monitoring_Interface_T;
private
... Declaration of a task and some private constants and subprograms ...
end PSU_Monitoring
The respective body is
package body PSU_Monitoring is
protected body Monitoring_Interface_T is
function is_all_config_set return Boolean is
begin
return monitor_pfc_voltage_config_set and monitor_pfc_current_config_set and monitor_output_voltage_config_set and monitor_output_current_config_set;
end is_all_config_set;
procedure set_monitor_pfc_voltage_config (new_monitor_config : in Monitor_Config_T) is
begin
monitor_pfc_voltage_config := new_monitor_config;
monitor_pfc_voltage_config_set := True;
end set_monitor_pfc_voltage_config;
function get_monitor_pfc_voltage_config return Monitor_Config_T is
begin
return monitor_pfc_voltage_config;
end get_monitor_pfc_voltage_config;
procedure set_monitor_pfc_current_config (new_monitor_config : in Monitor_Config_T) is
begin
monitor_pfc_current_config := new_monitor_config;
monitor_pfc_current_config_set := True;
end set_monitor_pfc_current_config;
function get_monitor_pfc_current_config return Monitor_Config_T is
begin
return monitor_pfc_current_config;
end get_monitor_pfc_current_config;
procedure set_monitor_output_voltage_config (new_monitor_config : in Monitor_Config_T) is
begin
monitor_output_voltage_config := new_monitor_config;
monitor_output_voltage_config_set := True;
end set_monitor_output_voltage_config;
function get_monitor_output_voltage_config return Monitor_Config_T is
begin
return monitor_output_voltage_config;
end get_monitor_output_voltage_config;
procedure set_monitor_output_current_config (new_monitor_config : in Monitor_Config_T) is
begin
monitor_output_current_config := new_monitor_config;
monitor_output_current_config_set := True;
end set_monitor_output_current_config;
function get_monitor_output_current_config return Monitor_Config_T is
begin
return monitor_output_current_config;
end get_monitor_output_current_config;
end Monitoring_Interface_T;
... Definition of the remaining subprograms defined in the specification file ...
end PSU_Monitoring;
What is the problem here?
As Jeffrey was saying, we need to see the part of the program where the error is flagged. In general, this is related to functions with side effects, see reference manual:
http://docs.adacore.com/spark2014-docs/html/lrm/packages.html#external-state-variables
The same error message can be observed if you use the Clock function from the Real-Time package in the "wrong" way:
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main with SPARK_Mode is
Last : Time := Clock;
begin
-- some stuff happening here ...
if Clock > Last + Milliseconds(100) then
Put_Line("Too late");
end if;
end Main;
Clock is a function that has side effects (it returns different values every time you call it), and in this example the function is used in what's called an "interfering context" (see link above for a definition).
The solution would be to rewrite your code slightly:
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main with SPARK_Mode is
Last : Time := Clock;
begin
-- some code
declare
now : Time := Clock;
begin
if now > Last + Milliseconds(100) then
Put_Line("Too late");
end if;
end;
end Main;
So, basically, what you do is isolate calls to functions with side effects into a separate statement, saving the result in a variable, and then use the variable where you had your call before. This trick should help with your call to the protected object, as well.

Fire and forget entry/accept mechanism in Ada

Is there a pattern for a fire and forget mechanism in Ada? When I call a task entry, I don't want the caller to be blocked until the message has been processed. I would like the task to be asynchronous. What I've tried is
loop
select
accept xxx(params) do
-- save the parameters in a queue
end accept;
...
else
-- pick the next item off the queue and process it
end select;
end loop;
It looks like a clumsy mechanism. Maybe fire and forget is the wrong term. I've also tried one task filling up the queue and another taking entries off the queue. Is there a better way of implementing asynchronous tasks in Ada.
If you’re using Ada 2012, the way to go would be to use Ada.Containers.Unbounded_Synchronized_Queues (or the Bounded version): your user code calls Enqueue, your server task calls Dequeue which blocks if the queue is empty.
If not, the normal approach would be to use your own protected object to encapsulate a queue (which is how the Ada 2012 packages do it). Something like
package Parameters is new Ada.Containers.Vectors (Positive, Parameter);
protected Queue is
procedure Put (P : Parameter);
entry Get (P : out Parameter);
private
The_Queue : Parameters.Vector;
end Queue;
protected body Queue is
procedure Put (P : Parameter) is
begin
The_Queue.Append (P);
end Put;
entry Get (P : out Parameter) when not The_Queue.Is_Empty is
begin
P := The_Queue.First_Element;
The_Queue.Delete_First;
end Get;
end Queue;
and then
task body Server is
P : Parameter;
begin
loop
Queue.Get (P);
-- process P
end loop;
end Server;

On keydown in Ada

I would need to execute some function when the user presses the "escape" key in my Ada program. I know we can retrieve what he enters thanks to get_line but it's not exactly what I need to do.
Indeed, I don't want to stop the program until he enters "escape".
First, is it possible ?
It is possible to get the characters without the need to press enter using :
Ada.Text_IO.Get_Immediate (Answer)
with Answer, a Character.
And the escape key is ASCII 27, so you can check whether Character'Pos(Answer) equals 27 or not. Also, as suggested in the comments, you can also compare Answer to Ada.Characters.Latin_1.ESC.
Here is an example of a program that display "Yeah!!!1!!1!" in a loop until the key ESC is pressed.
with Ada.Characters.Latin_1;
with Ada.Text_IO;
procedure Test is
Finished : Boolean := False;
task Escape_Task;
task body Escape_Task is
Answer : Character;
begin
loop
Ada.Text_IO.Get_Immediate(Answer);
if Answer = Ada.Characters.Latin_1.ESC then
Finished := True;
exit;
end if;
end loop;
end Escape_Task;
begin
while not finished loop
Ada.Text_IO.Put_Line("Yeahh!!!1!!1!");
end loop;
end Test;

Resources