My package level task never ends

I have a unit test for the Jorvik profile which is quite challenging. The aim is to test a ring buffer reading and writing from two different tasks. The tests run through, AUnit posts the final results but the test program never terminates.

I’m updated today to GNAT 16.1.0.

Has anybody an idea why this could be the case?

pragma License (Modified_Gpl);
pragma Ada_2022;
pragma Extensions_Allowed (On);
pragma Profile (Jorvik);

with AUnit.Assertions;
with Ada.Numerics.Float_Random;
with Ada.Strings.Fixed;
with Ada.Real_Time;
with Ada.Text_IO;

package body AdaCL.Trace_Buffer.Test is
   use AUnit.Assertions;
   use type Ada.Real_Time.Time_Span;
   use type Ada.Real_Time.Time;

   package S renames Ada.Strings;
   package S_F renames Ada.Strings.Fixed;

   type Signal_Type is
      (None,
       Test_3);

   protected Start_Signal is
      procedure Set (S : Signal_Type);
      function Get return Signal_Type;
   private
      Signal : Signal_Type := None;
   end Start_Signal;

   task Fill_Buffer;

   Gen     : Ada.Numerics.Float_Random.Generator;
   Input   : constant String                  := "Test Message";
   Timeout : constant Ada.Real_Time.Time_Span := Ada.Real_Time.To_Time_Span (2.0);

   --  Zufälliger Delay zwischen Min und Max (in Sekunden)
   function Random_Delay
      (Min : Duration := 0.000_001;  -- 1µs
       Max : Duration := 0.001) return Duration  -- bis 1ms
   is
      Random : constant Float    := Gen.Random;
      Retval : constant Duration := Min + Duration (Random * Float (Max - Min));
   begin
      return Retval;
   end Random_Delay;

   protected body Start_Signal is
      procedure Set (S : Signal_Type) is
      begin
         Signal := S;
      end Set;

      function Get return Signal_Type is (Signal);
   end Start_Signal;

   task body Fill_Buffer is
      Start : constant Ada.Real_Time.Time := Ada.Real_Time.Clock;
   begin
      --  Wait for signal to start test 3.
      --
      while Start_Signal.Get /= Test_3 loop
         delay Random_Delay;

         Assert (Ada.Real_Time.Clock < Start + Timeout, "Write loop should not time out");
      end loop;

      for I in 1 .. 7 loop
         Trace_Buffer.Put_Line (Input & I'Image);

         delay Random_Delay;
      end loop;

      Ada.Text_IO.Put_Line ("Write End");
      Ada.Text_IO.Flush;

      --  Signal that write as finished
      --
      Start_Signal.Set (None);
   end Fill_Buffer;

   procedure Test_Buffer_Line_03 (T : in out AUnit.Test_Cases.Test_Case'Class) is
      pragma Unreferenced (T);

      Text  : Text_Type;
      ID    : ID_Type;
      Start : constant Ada.Real_Time.Time := Ada.Real_Time.Clock;
   begin
      Trace_Buffer.Reset;

      Start_Signal.Set (Test_3);

      for I in 1 .. 7 loop
         while not Trace_Buffer.Has_Line loop
            delay Random_Delay;

            Assert (Ada.Real_Time.Clock < Start + Timeout, "Read Loop should not time out");
         end loop;

         Trace_Buffer.Get_Line (Text, ID);

         Assert (S_F.Trim (Text, S.Right), Input & I'Image, "Output text should be equal input text.");
      end loop;

      Assert (Trace_Buffer.Has_Line = False, "No lines avalable.");
      Assert (Trace_Buffer.Pending_Lines = 0, "No lines left");

      --  wait for writer to finish
      --
      while Start_Signal.Get /= None loop
         delay Random_Delay;
      end loop;

      Ada.Text_IO.Put_Line ("Read End");
      Ada.Text_IO.Flush;

      return;
   end Test_Buffer_Line_03;

   --: -----------------------------------------------------------------------

   overriding procedure Register_Tests (T : in out Test_Case) is
      use AUnit.Test_Cases.Registration;
   begin
      Register_Routine (T, Test_Put_Line_01'Access, "Test_Put_Line_01 : put one line.");
      Register_Routine (T, Test_Get_Line_01'Access, "Test_Get_Line_01 : get one line.");
      Register_Routine (T, Test_Buffer_Line_01'Access, "Test_Buffer_Line_01 : buffer one line.");
      Register_Routine (T, Test_Buffer_Line_02'Access, "Test_Buffer_Line_02 : buffer seven lines.");
      Register_Routine (T, Test_Buffer_Line_03'Access, "Test_Buffer_Line_03 : async buffer seven lines.");

      return;
   end Register_Tests;
end AdaCL.Trace_Buffer.Test;

Full source under: Ada Class Library / Git / [cc9027] /adacl_embedded

Why are you randomizing a delay?
Is there anything keeping you from using an entry and synchronizing that way?
(Reminder: you can rename/reuse entries as procedures in generics.)

Because this is a unit test and this is what I wish to test. Note that is only 1 of 5 tests. I removed the other 4 tests but you can look them up at SourceForge. I might fine tune the test later — but there is no point in fine tuning if the task itself doesn’t work.

Yes, the Jorvik profile. See An Introduction to Jorvik, the New Tasking Profile in Ada… | AdaCore. If it wasn’t for Jorvik I would just use a declare block for the task and then I wouldn’t need any synchronisation at all.

If you know a trick how to test a Jorvik profile library using a unrestricted AUnit test harness I would love to hear about it.

?
Max_Entry_Queue_Length => 1 should be enough.

Task Example is
  Entry Start;
  Entry Pause;
  Entry Stop;
End Example;

Task Body Example is
Begin
  -- Do preamble stuff.
  accept Start;
  -- Do the first part.
  accept Pause;
  -- Do the stuff to process the previous and set up for the next.
  accept Pause;
  accept Stop;
  -- Do clean-up, etc.
End Example;

--…
Procedure AHaven_Test is
Begin
   Example.Start; -- Wait until EXAMPLE is ready to process.
   -- Other stuff.
   Example.Pause; -- Allow mid-point processing,
   Example.Pause; -- and continue on when able.
   -- Other other stuff.
   Example.Stop; -- Do the clean-up in the task.
End;

IIRC, that should work. (If the test function has Example in-scope.)

Check the ring buffer behaviour upon buffer overrun. If you overwrite then both indices must move. If you don’t it should block (on an entry or doing busy waiting on the state) or else raise.

BTW, ring buffer does not need protected object. It can be lock-free even in an N-M scenario.

That results in:

src/adacl-trace_buffer-test.adb|66 col 3 error| violation of restriction "Max_Task_Entries = 0"
src/adacl-trace_buffer-test.adb|66 col 3 error| from profile "Jorvik" at line 21

And I have tried all of this already. The test is designed this awkwardly because I tried the easier and cleaner options already and they didn’t work with Jorvik.

The ring buffer works fine. The 5 test actually succeed. AUnit prints the final report. But the program still doesn’t stop. You need to use Ctrl-C.

>make test
alr build --validation
ⓘ Building adacl_embedded_test_tasking=7.2.0/adacl_embedded_test_tasking.gpr...
Compile
   [Ada]          adacl-trace_buffer-test.adb
Bind
   [gprbind]      adacl_embedded_test-main.bexch
   [Ada]          adacl_embedded_test-main.ali
Link
   [link]         adacl_embedded_test-main.adb
ld: warning: -no_pie is deprecated when targeting new OS versions
✓ Build finished successfully in 1.49 seconds.

alr run --skip-build

OK AdaCL.Trace_Buffer : Test_Put_Line_01 : put one line.
OK AdaCL.Trace_Buffer : Test_Get_Line_01 : get one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_01 : buffer one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_02 : buffer seven lines.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_03 : async buffer seven lines.

Total Tests Run:   5
Successful Tests:  5
Failed Assertions: 0
Unexpected Errors: 0
Write End
^C Interrupted by user
make: *** [test] Error 1

It’s interesting that “Write End” is shown after the AUnit output so I added and end of test synchronisation as well. But that made no difference:

>make test
alr build --validation
ⓘ Building adacl_embedded_test_tasking=7.2.0/adacl_embedded_test_tasking.gpr...
Compile
   [Ada]          adacl_embedded_test-suite.adb
   [Ada]          adacl-trace_buffer-test.adb
Bind
   [gprbind]      adacl_embedded_test-main.bexch
   [Ada]          adacl_embedded_test-main.ali
Link
   [link]         adacl_embedded_test-main.adb
ld: warning: -no_pie is deprecated when targeting new OS versions
✓ Build finished successfully in 2.21 seconds.

alr run --skip-build
Write End
Read End

OK AdaCL.Trace_Buffer : Test_Put_Line_01 : put one line.
OK AdaCL.Trace_Buffer : Test_Get_Line_01 : get one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_01 : buffer one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_02 : buffer seven lines.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_03 : async buffer seven lines.

Total Tests Run:   5
Successful Tests:  5
Failed Assertions: 0
Unexpected Errors: 0
^C Interrupted by user
make: *** [test] Error 1

But just for good measure I removed the call to the buffer and the asserts and the result is the same. It’s not the buffer.

There is an incongruity then: the compiler is saying the restriction of entry-queue is 0, whereas the page you showed says 1.

This is not so bad though; Ada has superb constructs (packages, generics, and tasks), so, I would suppose that you could chunk your task into parts:

Package Body Example is
  Generic
    Data_Set : in out Stuff;
  Procedure Reset_Data;  

  Procedure Reset_Data is
     Package Data is
     End Data;

     Package Body Data is 
     Begin
        -- Fill your data here.
        For Item of Whatever loop
          Item:=  Random_Value;
        End loop;
     End Data;
  Begin
    Delay Random_Time;
  End Reset_Data;

  Procedure Test is
    Procedure Reset is new Reset_Data( Whatever => Actual_Whatever );
  Begin
    Reset;
    --Do testing here.
    Reset; -- Re-randomize stuff.
    -- Do more testing.
  End;

End Example;

There you are. Obviously simplified, but similar should work.

One for protected, zero for task - made that same mistake when I started to work with restricted run-times for the Raspberry-Pi-Pico. Thinking of it: on the Raspberry-Pi-Pico you would never notice if a task never terminates.

I do not know if this will help, but SPARK can do flow analysis for the Jorvik profile… So maybe try throwing your program to GNATprove and see if you get any actual error.

Best,
Fer

You are late with that one. To reiterate: The actual object under test has been proven and the unit tests are successful.

alr build --validation
Note: Building adacl_embedded=7.2.0/adacl_embedded.gpr...
Compile
   [Ada]          adacl_embedded_config.ads
   [Ada]          adacl-trace_buffer.adb
Build Libraries
   [gprlib]       AdaCL_Embedded.lexch
   [archive]      libAdaCL_Embedded.a
   [index]        libAdaCL_Embedded.a
Success: Build finished successfully in 0.97 seconds.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C "prove_log_off"	test
alr build --validation
Note: Building adacl_embedded_prove_log_off=7.2.0/adacl_embedded_prove_log_off.gpr...
Compile
   [Ada]          adacl_embedded_config.ads
   [Ada]          adacl-trace_buffer.adb
   [Ada]          adacl-text_io.adb
   [Ada]          adacl.ads
Build Libraries
   [gprlib]       AdaCL_Embedded.lexch
   [archive]      libAdaCL_Embedded.a
   [index]        libAdaCL_Embedded.a
Bind
   [gprbind]      dummy-main.bexch
   [Ada]          dummy-main.ali
Link
   [link]         dummy-main.adb
Success: Build finished successfully in 0.98 seconds.
alr gnatprove
Phase 1 of 3: generation of data representation information ...
Phase 2 of 3: generation of Global contracts ...
Phase 3 of 3: flow analysis and proof ...
Summary logged in /Users/Shared/Work/Projects/AdaCL/develop/adacl_embedded/prove_log_off/obj/validation/gnatprove/gnatprove.out
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C "prove_no_tasking"	test
alr build --validation
Note: Building adacl_embedded_prove_no_tasking=7.2.0/adacl_embedded_prove_no_tasking.gpr...
Compile
   [Ada]          adacl_embedded_config.ads
   [Ada]          adacl-trace_buffer.adb
   [Ada]          adacl-text_io.adb
   [Ada]          adacl.ads
Build Libraries
   [gprlib]       AdaCL_Embedded.lexch
   [archive]      libAdaCL_Embedded.a
   [index]        libAdaCL_Embedded.a
Bind
   [gprbind]      dummy-main.bexch
   [Ada]          dummy-main.ali
Link
   [link]         dummy-main.adb
Success: Build finished successfully in 0.99 seconds.
alr gnatprove
Phase 1 of 3: generation of data representation information ...
Phase 2 of 3: generation of Global contracts ...
Phase 3 of 3: flow analysis and proof ...
Summary logged in /Users/Shared/Work/Projects/AdaCL/develop/adacl_embedded/prove_no_tasking/obj/validation/gnatprove/gnatprove.out
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C "prove_tasking"	test
alr gnatprove
Phase 1 of 3: generation of data representation information ...
Phase 2 of 3: generation of Global contracts ...
Phase 3 of 3: flow analysis and proof ...
Summary logged in /Users/Shared/Work/Projects/AdaCL/develop/adacl_embedded/prove_tasking/obj/development/gnatprove/gnatprove.out
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C "test_log_off"	test
alr build --validation
Note: Building adacl_embedded_test_log_off=7.2.0/adacl_embedded_test_log_off.gpr...
Compile
   [Ada]          adacl_embedded_config.ads
   [Ada]          adacl-trace_buffer.adb
   [Ada]          adacl-text_io.adb
   [Ada]          adacl.ads
   [Ada]          adacl_embedded_test-suite.adb
   [Ada]          adacl-trace_buffer-test.adb
Build Libraries
   [gprlib]       AdaCL_Embedded.lexch
   [archive]      libAdaCL_Embedded.a
   [index]        libAdaCL_Embedded.a
Bind
   [gprbind]      adacl_embedded_test-main.bexch
   [Ada]          adacl_embedded_test-main.ali
Link
   [link]         adacl_embedded_test-main.adb
Success: Build finished successfully in 1.65 seconds.
alr run --skip-build

OK AdaCL.Trace_Buffer : Test_Put_Line_01 : put one line.
OK AdaCL.Trace_Buffer : Test_Get_Line_01 : get one line.

Total Tests Run:   2
Successful Tests:  2
Failed Assertions: 0
Unexpected Errors: 0
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C "test_no_tasking"	test
alr build --validation
Note: Building adacl_embedded_test_no_tasking=7.2.0/adacl_embedded_test_no_tasking.gpr...
Compile
   [Ada]          adacl_embedded_config.ads
   [Ada]          adacl-trace_buffer.adb
   [Ada]          adacl_embedded_test-suite.adb
   [Ada]          adacl-trace_buffer-test.adb
Build Libraries
   [gprlib]       AdaCL_Embedded.lexch
   [archive]      libAdaCL_Embedded.a
   [index]        libAdaCL_Embedded.a
Bind
   [gprbind]      adacl_embedded_test-main.bexch
   [Ada]          adacl_embedded_test-main.ali
Link
   [link]         adacl_embedded_test-main.adb
Success: Build finished successfully in 1.66 seconds.
alr run --skip-build

OK AdaCL.Trace_Buffer : Test_Put_Line_01 : put one line.
OK AdaCL.Trace_Buffer : Test_Get_Line_01 : get one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_01 : buffer one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_01 : buffer seven lines.

Total Tests Run:   4
Successful Tests:  4
Failed Assertions: 0
Unexpected Errors: 0
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C "test_tasking"	test
alr build --validation
Note: Building adacl_embedded_test_tasking=7.2.0/adacl_embedded_test_tasking.gpr...
Compile
   [Ada]          adacl_embedded_config.ads
   [Ada]          adacl-trace_buffer.adb
   [Ada]          adacl_embedded_test-suite.adb
   [Ada]          adacl-trace_buffer.adb
   [Ada]          adacl-trace_buffer-test.adb
Build Libraries
   [gprlib]       AdaCL_Embedded.lexch
   [archive]      libAdaCL_Embedded.a
   [index]        libAdaCL_Embedded.a
Bind
   [gprbind]      adacl_embedded_test-main.bexch
   [Ada]          adacl_embedded_test-main.ali
Link
   [link]         adacl_embedded_test-main.adb
Success: Build finished successfully in 1.72 seconds.
alr run --skip-build
Write End
Read End

OK AdaCL.Trace_Buffer : Test_Put_Line_01 : put one line.
OK AdaCL.Trace_Buffer : Test_Get_Line_01 : get one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_01 : buffer one line.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_02 : buffer seven lines.
OK AdaCL.Trace_Buffer : Test_Buffer_Line_03 : async buffer seven lines.

Total Tests Run:   5
Successful Tests:  5
Failed Assertions: 0
Unexpected Errors: 0
 Interrupted by user
  • GNATprove for all three possible configurations.
  • AUnit for all three possible configurations.

Right at the end when all proves and test are long finished the system hangs. Right now I only ask why that is.

What GNATprove flags are you using? This does seem like a bug with the tools, but try running alr gnatprove --level=4, which may add additional checks.

EDIT: oh, so it is the system and not the program… That sounds like some finalization or underlying issue with the implementation of tasks and their termination >:frowning:

What extensions are you using? Why? Try doing this in Ada and we might be able to help.

Hello,

Since everybody was distracted by the complexity and started to chaise red herrings I decided to created minimum example:

pragma Profile (Jorvik);

package Test_Task is
   task A_Task;
end Test_Task;
pragma Profile (Jorvik);

with Ada.Text_IO;

package body Test_Task is
   task body A_Task is
   begin
      Ada.Text_IO.Put_Line ("+ Test_Task.A_Task");

      delay 1.0;

      Ada.Text_IO.Put_Line ("- Test_Task.A_Task");
      Ada.Text_IO.Flush;
   end A_Task;
end Test_Task;
pragma Profile (Jorvik);

with Ada.Text_IO;

procedure Test_Task.Main is
begin
   Ada.Text_IO.Put_Line ("+ Test_Task.Main");

   Ada.Text_IO.Put_Line ("- Test_Task.Main");
   Ada.Text_IO.Flush;
end Test_Task.Main;

When build with pragma Profile (Jorvik); the program will output but never terminate.

+ Test_Task.A_Task
+ Test_Task.Main
- Test_Task.A_Task
- Test_Task.Main

If you remove the pragma Profile (Jorvik); the code will work as expected. So the problem lies with the Jorvik profile. The question is: Is that a bug or intentional. As Jorvik is meant for embedded run-times it might be intentional.

And before someone chases another red herring: If you remove the delay the result is the same - just the output is in a different order.

Please have a look at the new reduced example. BTW: Some of the Ada_2022 features are or where considered extensions - It’s all valid Ada 2022 code.

Mmmm… here is my guess… Maybe the Jorvik profile requires that the program never terminates and maybe calls the Last_Chance_Handler or a forever-task that maybe waits for a reset or something similar… Maybe it is meant to keep the device and the program running in a dormant state? I have not read the AARM nor the different changes that Jorvik imposes…

Not quite. If no task is present then then the main procedure running on the main task will terminate just fine. But it could still be that Jorvik profile requires that a user defined task never terminates. I wish that someone who knows the profile would chaim in.

I just bit the bullet and ask Grok with the reduced case:


It is intentional (by design).

The Jorvik profile (like the stricter Ravenscar profile) includes the restriction No_Task_Termination. This means tasks are never allowed to terminate (normally or abnormally). This is a core design choice for predictability, schedulability analysis, certification, and embedded/hard real-time systems.

Why the program doesn’t terminate

  • Your task A_Task completes its body (after the delay).
  • Under No_Task_Termination, the runtime treats this as a violation or simply prevents normal program shutdown.
  • The environment task (which executes the main subprogram) cannot terminate while dependent tasks exist, and the profile enforces that tasks must run forever (typically in an infinite loop).
  • Without the profile, full Ada allows tasks to terminate normally, and the program can shut down once the main subprogram and all dependent tasks complete.

This matches exactly what you observed. Removing the delay changes output order but not the hanging behavior, as expected.

Official references

From the Ada Reference Manual (RM D.13) and GNAT documentation:

  • Jorvik explicitly includes:
    pragma Restrictions (..., No_Task_Termination, ...);
    
  • Documentation repeatedly states: “tasks are never supposed to terminate for any reason, including normally” under Ravenscar/Jorvik.

Recommended pattern for Jorvik/Ravenscar

Tasks (and often the environment task in Main) should contain infinite loops:

task body A_Task is
begin
   Ada.Text_IO.Put_Line ("+ Test_Task.A_Task");
   loop
      -- Your work here (e.g., delay until Next_Time;)
      delay 1.0;
   end loop;
end A_Task;

For the main procedure in embedded contexts, a common idiom is an infinite loop at low priority after initialization.

Summary

This is not a bug — it’s the expected behavior of the Jorvik profile for safety-critical/embedded use cases. The “minimum example” correctly demonstrates the restriction. If you need full Ada task termination semantics, don’t use Jorvik (or Ravenscar). For embedded runtimes, this is exactly why the profile exists.


It’s amazing how good A.I. have become when it comes to programming. And not just main stream languages like Python. Grok does Ada just as confident. Still, if an actual human would confirm then I would fell better.