How do I make an infinite task kick-off?

I have a function that I would like to repeat as much as possible. I don’t want it blocking my main thread though. So I separated it into a task, which looks like this:

task body Placeholder_Name is 
accept Start do 
   loop
      Iterate;
   end loop;
end Start;
end Placeholder_Name;

When I call Placeholder_Name_Holder.Start I just want the program to continue over that line after kicking it off, so I can continue with the rest of the program. But instead it waits, which makes sense since the loop is not completed yet. But how would I get it to ignore that behavior?

Are you doing something like:

Procedure Main is
  Task Placeholder_Name   -- …
  Task body Placeholder_Name   -- …
Begin
  -- …
   Placeholder_Name.Start;
  -- …
End Main;

Because what you want in your description is a task running at library-level (or like this, at the ‘main’ level).

If you have this construct nested within subprograms though it’s not going to work because it is going to need to terminate in order to return, which it cannot.

Just move the loop out of the `accept` statement:

task body Placeholder_Name is
begin
  accept Start;
  loop
     Iterate;
  end loop;
end Placeholder_Name;
2 Likes

I was doing something like

task type Worker is 
   accept Start;
   accept Stop;
end Worker;
task body Worker is 
   accept Start do 
      loop
         exit when stop_condition;
         Iterate;
      end loop;
   end Start;
   accept Stop do 
      Set_Stop_Condition_And_Shutdown;
   end Stop;
end Worker;

My_Worker : Worker;

procedure Init_Stuff is begin
   My_Worker.Start; 
   -- Want to continue immediately after starting off the task so I can end the procedure.
end Init_Stuff;

Ideally I’d want to skip the library level and go with the pattern I outlined here. Might not be possible.

This did allow the loop to keep running while passing the My_Worker.Start, though it did lead to an unreachable error with the Stop accept block.

Edit: I’m going to go review the Ada 22 book, and come back in a few hours once I achieve exactly what I’m looking for. I think I’m after some weird edge case behavior I can’t properly describe (yet).

You’re on the right track.
The first code you presented had the loop within the accept, so the reason it appeared to stall out was because within the accept both the caller and callee are synchronized. (Think about space-ships being locked together as they dock together. They can fly around and do their own thing independently, when not locked together.)

task body Worker is
   stop_condition : Boolean := False;
begin 
   accept Start; -- We hold here until we receive the START.
   loop          -- and we do this loop until STOP_CONDITION.
      exit when stop_condition;
      Iterate;
      select  -- Here we process any STOP rendezvous. 
         accept Stop;            -- if we do process it,
         stop_condition:= True;  -- then we set STOP_CONDITION
      else                       -- Otherwise…
        null;                    -- We do nothing and LOOP again.
      end select;
   end loop;
end Worker;

The above should do what you’re trying to do.

1 Like

One can exit the loop containing select just like any other loop:

loop
   select
      accept Stop;
      exit;
   else
      Iterate;
   end select;
end loop;
2 Likes

I have settled on something that works for my needs as of now. Thank you to all who replied. Every reply here gave me something to look at and something to learn.

-- my final worker body (simplified)
accept Start do 
   start_stuff;
end Start;

loop
   select
      accept Stop do
         stop_stuff;
      end Stop;
      exit;
   else
      do_limiter_stuff_to_avoid_overprocessing;
      Iterate;
   end select;
end loop;

At first, I thought that multi-threading in Ada was painful, but after a day or two I certainly like it more, probably because I’m not completely confused anymore. I do hate learning new things. For some weird reason I couldn’t wrap my head around this, but then it just made sense, haven’t had that happen with too many things before. Maybe I’m slow.
I thought to myself that perhaps it wasn’t as bad as another language, and so I looked up the syntax for multithreading elsewhere. After looking around, I appreciate Ada a bit more.
The one complaint I had initially is that I didn’t know that the else statement was valid in a select loop. I kept trying to put it in something that looked like:

select 
   accept Stop;
   stop_stuff;
or
   Iterate;
end select;

Because I wrongly assumed that the only valid thing to put in a select loop was an or which had me so damn sad for a while, chasing my tail. Then I posted on the forum after cursing at my monitor for a while, and gave up.

While writing this (victory?) post I went and RTFM, and there it was 9.7.1 Selective accept paragraph two. Or even the great example at 9.7.1 Selective Accept paragraph 23 through 24!

I’m getting better at reading the manual, but at first it was a nightmare because my reading comprehension for something written like this was really low. A massive mistake I made was not using the official reference manual. I’ve been slowly using it more and more instead of using a search engine, and it’s been saving me the more I read it. Don’t know why I haven’t been doing that. I’ve been spoiled with other languages that just have a stackoverflow post for everything. Improvement is improvement though. Still glad I made the mistake in the first place! :]

Looking back at one of my first posts I had several fundamental misunderstandings.
Misunderstandings such as putting an accept Stop do... after the now understandably infinite loop, which would prevent the task from even setting the stop condition in the first place. Or putting the loop INSIDE of the accept block, causing it to block. In block = bloc-FUUUUUUUU. I scrolled up and cringed, I have to right my wrongs! Thanks for the help.

5 Likes

Note that you can also have a delay alternative here which allows Stop to be accepted while waiting for the delay to end instead of only accepting before the delay.

It is helpful you imagine how you would implement a select statement.

Task has a queue where entry calls are placed. The select statement inspects this queue. One of the or-ed accept alternatives takes one item of its kind from the queue (the syntactical order ignored) if none is there (and open), then or delay or or terminate or else fires in.

On the entry call side the client places its call into the queue and waits. The call can be aborted while the item is still in the queue (timed entry call). When the item is taken from the queue the caller still waits until the accept statement of the callee is within the do sequence. This state is called rendezvous. At this point it cannot be aborted. When the rendezvous is over the entry call finishes (or requeued).

Exceptions inside the rendezvous propagate on both sides. Rendezvous can requeue the call to an entry of the same or another task. The client may be able to abort it if the requeue statement has with about clause.

What about using access task? Fire and Forget :slight_smile:

And remember “new …”

I use it to read in large amounts of data without sitting to wait for the task to finish.

To get further explanation: try to ask google (AI Mode):

Ada programming: can I use “access task” to make a routine run after leaving the routine which did start it?

reinert

That is interesting! But wouldn’t that behaviour be just a dangling access type and a leaky task? If you can leave the context for which it was created and “loose” access to it… that does not sound good. But maybe that is where your

hint is trying to convey :slight_smile:

Best regards,
Fer

I remember there was a memory leak issue with finalization of a task still running while deallocating it. I do not know if the new Ada standard resolves the issue, but the pattern to free a task safely is:

   Task_Ptr.Stop;  -- Ask the task to terminate
   while not Task_Ptr'Terminated loop -- Wait for completion
      delay 0.001;
   end loop;
   Free (Task_Ptr); -- Deallocate

It is a good software developing policy to free all resources allocated even if you do not need to. You might find a lot of hidden bugs this way.

1 Like

I haven’t checked the RM, but I’m sure tasks must terminate as part of their finalization when you call Unchecked_Deallocate.

I include a “wait”, “halt” and “resume” in the actual loop (protected object). Just in case someone could benefit from my “solution”:


   protected body pause1_t is

      entry wait1 when ok is
      begin
         null;
      end wait1;

      procedure halt1 is
      begin
         ok := false;
      end halt1;

      procedure resume1 is
      begin
         ok := true;
      end resume1;

   end pause1_t;

``
Remember before leaving your main program: 
   if your_supertask1 /= null then
         abort your_supertask1.all;
   end if;

Yes, you get a deadlock if you deallocate a task that never reaches/has a terminate alternative. The issue is different, there is (was?) a race condition between the task accepted terminate or an explicit request and becoming terminated. If you deallocated it within that interval you would leak some amount o memory.

Maybe the GNAT/AdaCore extension of finalize solves it?

Do you mean the finally hack? I do not see how it could help.

Anyway the issue is minor. In practice you would never start and stop tasks like crazy. Mission-critical and embedded systems run never ending tasks. In other cases if really need a lot of tasks. e.g. in a server, you deploy a pool of.

I have a particular application where tasks are created as required (to answer queries on an incoming port, in a light switch management application). In that case, I have another task that manages a list of tasks. The creator stashes a pointer to the task on a list and when that task has done its work, as its last operation it tells the management task that it is ready to die. The management task then disposes of it and removes it from the list.