comp.lang.ada
 help / color / mirror / Atom feed
* Termination of periodic tasks
@ 2014-06-15 10:10 Natasha Kerensikova
  2014-06-15 12:11 ` Dmitry A. Kazakov
                   ` (2 more replies)
  0 siblings, 3 replies; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-15 10:10 UTC (permalink / raw)


Hello,

I'm still struggling with the termination of tasks hidden in the private
part or the body of libraries, this time with a periodic task.

Basically, I'm looking for a way to have some subprogram called at
roughly regular intervals. Ada.Real_Time.Timing_Events would be
functionally what I needed, except it's accuracy is vastly more than I
need, so it's overhead is mostly wasted.

For the uses I currently have in mind, the periodic subprogram would
be a kind of GC for slowly moving resources, called every 15 min to
every day, with a time tolerance exceeding minutes.

While in GNARL Ada.Real_Time.Timing_Events is implemented using a task
awaken every 100 ms.

And same as before, the task should be able to terminate quickly when
the program terminates.

I would be perfectly happy with something like:

   task body Periodic_Task is
      loop
         select
            delay 86_400.0;
         or
            terminate;
         end select;

         Periodic_Subprogram;
      end loop;
   end task;

Except that's not available in Ada.

The "or terminate" part is only available when waiting for an entry, but
this only move the problem to the periodic calling of the entry.

A timed select to wait for an entry that exists the loop would be fine
too, but how can I detect program termination to call the entry?
Could something be worked with a user-defined Finalize?

I looked at how GNARL is doing it, but it's using an RL-specific
primitive to move the task one level above the main master, so that it's
aborted instead of waited-for on program termination. I'm a bit
reluctant to use a RL-internal primitive...

That leaves Ada.Real_Time.Timing_Events and its comparatively large
overhead, even though (I think) I can afford it, it's not very
satisfying.

How would you do it?


Thanks in advance for your help,
Natasha

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-15 10:10 Termination of periodic tasks Natasha Kerensikova
@ 2014-06-15 12:11 ` Dmitry A. Kazakov
  2014-06-15 15:23 ` J-P. Rosen
  2014-06-15 16:54 ` Jeffrey Carter
  2 siblings, 0 replies; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-15 12:11 UTC (permalink / raw)


On Sun, 15 Jun 2014 10:10:20 +0000 (UTC), Natasha Kerensikova wrote:

[...]
> How would you do it?

You have an exit event (protected object) fired from the Finalize of the
object containing a pointer to the task (task component would not work).

The task does a timed entry call for the event. From the delay alternative
it calls the worker subprogram. When the entry gets accepted the task
exits.

After Finalize fires the event. It awaits task termination (doing tight
polling of T'Terminated) and then calls to Unchecked_Deallocate of the task
pointer.

Typically for this active object pattern the task would have a class-wide
access discriminant of the object. The worker subprogram would be a
primitive operation of the object. So the call from the task body were
dispatching.

When the type is derived from and Finalize gets overridden, the task shall
be stopped *before* doing the object's finalization. This is the rationale
for the design of task-components awaited before entering Finalize. Without
an active terminate alternative that does not work. And to have a terminate
alternative is close to impossible in any realistic design. So, the task
components must be pointers.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-15 10:10 Termination of periodic tasks Natasha Kerensikova
  2014-06-15 12:11 ` Dmitry A. Kazakov
@ 2014-06-15 15:23 ` J-P. Rosen
  2014-06-16 13:54   ` Natasha Kerensikova
  2014-06-15 16:54 ` Jeffrey Carter
  2 siblings, 1 reply; 25+ messages in thread
From: J-P. Rosen @ 2014-06-15 15:23 UTC (permalink / raw)


Le 15/06/2014 12:10, Natasha Kerensikova a écrit :
> And same as before, the task should be able to terminate quickly when
> the program terminates.
Why not have a "stop" entry called by the main program when it
terminates (possibly through an exported subprogram if you don't want to
have the task public)?

-- 
J-P. Rosen
Adalog
2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
Tel: +33 1 45 29 21 52, Fax: +33 1 45 29 25 00
http://www.adalog.fr


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-15 10:10 Termination of periodic tasks Natasha Kerensikova
  2014-06-15 12:11 ` Dmitry A. Kazakov
  2014-06-15 15:23 ` J-P. Rosen
@ 2014-06-15 16:54 ` Jeffrey Carter
  2014-06-16 14:02   ` Natasha Kerensikova
  2 siblings, 1 reply; 25+ messages in thread
From: Jeffrey Carter @ 2014-06-15 16:54 UTC (permalink / raw)


On 06/15/2014 03:10 AM, Natasha Kerensikova wrote:
>
> A timed select to wait for an entry that exists the loop would be fine
> too, but how can I detect program termination to call the entry?
> Could something be worked with a user-defined Finalize?

ARM 7.6.1 says, "For the finalization of a master, dependent tasks are first 
awaited, as explained in 9.3. Then each object ... is finalized if the object 
was successfully initialized and still exists."

So the task has to terminate before Finalize is called.

-- 
Jeff Carter
"I spun around, and there I was, face to face with a
six-year-old kid. Well, I just threw my guns down and
walked away. Little bastard shot me in the ass."
Blazing Saddles
40

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-15 15:23 ` J-P. Rosen
@ 2014-06-16 13:54   ` Natasha Kerensikova
  2014-06-17 20:14     ` Charles H. Sampson
  0 siblings, 1 reply; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-16 13:54 UTC (permalink / raw)


Hello,

On 2014-06-15, J-P. Rosen <rosen@adalog.fr> wrote:
> Le 15/06/2014 12:10, Natasha Kerensikova a écrit :
>> And same as before, the task should be able to terminate quickly when
>> the program terminates.
> Why not have a "stop" entry called by the main program when it
> terminates (possibly through an exported subprogram if you don't want to
> have the task public)?

Mostly because I believe this to be too heavy for a burden for the
client, and somewhat of an abstraction leak.

As I wrote, the situations in which I felt the need are internal
resource collectors in some libraries. Since it's purely internal stuff,
it's of no business to the client, and the implementation should be
changeable at any time.

Moreover, in that situation, if the client forgets the required
"Finalize" or "On_Exit" call, the program can't stop at runtime, and
that feels like a harsh and late effect for such a common human error.

Finally, should the practice become wide spread, this means imposing on
the client a maintenance burden linear in the number of library used.
This isn't much of a problem in the current state of Ada, with a small
niche of mostly-low-complexity programs and comparatively few libraries,
but would like my libraries to survive a sudden (widely unexpected)
surge of Ada popularity.


Now none of these are technical reasons, and I would implement such a
solution if there is no "better" choice. These are only feelings coming
from internalization of best practices learned the hard way in huge and
complex software development environments.


Thanks of your help,
Natasha


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-15 16:54 ` Jeffrey Carter
@ 2014-06-16 14:02   ` Natasha Kerensikova
  2014-06-16 15:08     ` Dmitry A. Kazakov
  2014-06-16 17:08     ` Jeffrey Carter
  0 siblings, 2 replies; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-16 14:02 UTC (permalink / raw)


Hello,

On 2014-06-15, Jeffrey Carter <spam.jrcarter.not@spam.not.acm.org> wrote:
> On 06/15/2014 03:10 AM, Natasha Kerensikova wrote:
>>
>> A timed select to wait for an entry that exists the loop would be fine
>> too, but how can I detect program termination to call the entry?
>> Could something be worked with a user-defined Finalize?
>
> ARM 7.6.1 says, "For the finalization of a master, dependent tasks are first 
> awaited, as explained in 9.3. Then each object ... is finalized if the object 
> was successfully initialized and still exists."
>
> So the task has to terminate before Finalize is called.

That seems at odds with the solution proposed by Dmitry, isn't it?

Considering the following skeleton spec,

   package To_Be_Discussed is

      task type Typed_Task is
         entry Terminate;
      end Typed_Task;

      type Task_Access is access Typed_Task;

      task Singleton_Task is
         entry Terminate;
      end Singleton_Task;

      type Watcher is new Ada.Finalization.Limited_Controlled with record
         Signal_Target : Task_Access;
      end record;

      overriding procedure Finalize (Object : in out Watcher);

   end To_Be_Discussed;

As far as I can tell, Singleton_Task and task(s) created using an
allocator for Task_Access have the same master.

I understood Dimtry's solution as calling Terminate entry from
Watcher.Finalize.

However what you quote seems to indicate that Watcher.Finalize won't be
called until Singleton_Task and any allocated task in a Task_Access are
termninated by themselves.

So I'm still lost on how to solve my problem, though I still have to
experiment with ideas like the specification above.


Thanks for your help,
Natasha

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-16 14:02   ` Natasha Kerensikova
@ 2014-06-16 15:08     ` Dmitry A. Kazakov
  2014-06-16 17:08     ` Jeffrey Carter
  1 sibling, 0 replies; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-16 15:08 UTC (permalink / raw)


On Mon, 16 Jun 2014 14:02:47 +0000 (UTC), Natasha Kerensikova wrote:

> On 2014-06-15, Jeffrey Carter <spam.jrcarter.not@spam.not.acm.org> wrote:
>> On 06/15/2014 03:10 AM, Natasha Kerensikova wrote:
>>>
>>> A timed select to wait for an entry that exists the loop would be fine
>>> too, but how can I detect program termination to call the entry?
>>> Could something be worked with a user-defined Finalize?
>>
>> ARM 7.6.1 says, "For the finalization of a master, dependent tasks are first 
>> awaited, as explained in 9.3. Then each object ... is finalized if the object 
>> was successfully initialized and still exists."
>>
>> So the task has to terminate before Finalize is called.
> 
> That seems at odds with the solution proposed by Dmitry, isn't it?
> 
> Considering the following skeleton spec,
> 
>    package To_Be_Discussed is
> 
>       task type Typed_Task is
>          entry Terminate;
>       end Typed_Task;
> 
>       type Task_Access is access Typed_Task;
> 
>       task Singleton_Task is
>          entry Terminate;
>       end Singleton_Task;
> 
>       type Watcher is new Ada.Finalization.Limited_Controlled with record
>          Signal_Target : Task_Access;
>       end record;
> 
>       overriding procedure Finalize (Object : in out Watcher);
> 
>    end To_Be_Discussed;
> 
> As far as I can tell, Singleton_Task and task(s) created using an
> allocator for Task_Access have the same master.
> 
> I understood Dimtry's solution as calling Terminate entry from
> Watcher.Finalize.
> 
> However what you quote seems to indicate that Watcher.Finalize won't be
> called until Singleton_Task and any allocated task in a Task_Access are
> termninated by themselves.

It will be called. The master here is not the instance of Watcher. Watcher
would happen to be the master only if Signal_Target were its component of
the Typed_Task. Since you are using rather an access to Typed_Task,
everything is OK.

When using the solution with an entry the difference to signalling an event
is that you have to catch Tasking_Error if the task ended prematurely.
Another difference is that when you have a pool of tasks, they may share
the same exiting event.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-16 14:02   ` Natasha Kerensikova
  2014-06-16 15:08     ` Dmitry A. Kazakov
@ 2014-06-16 17:08     ` Jeffrey Carter
  2014-06-17  6:57       ` Natasha Kerensikova
  1 sibling, 1 reply; 25+ messages in thread
From: Jeffrey Carter @ 2014-06-16 17:08 UTC (permalink / raw)


On 06/16/2014 07:02 AM, Natasha Kerensikova wrote:
>
>     package To_Be_Discussed is
>
>        task type Typed_Task is
>           entry Terminate;
>        end Typed_Task;
>
>        type Task_Access is access Typed_Task;
>
>        task Singleton_Task is
>           entry Terminate;
>        end Singleton_Task;
>
>        type Watcher is new Ada.Finalization.Limited_Controlled with record
>           Signal_Target : Task_Access;
>        end record;
>
>        overriding procedure Finalize (Object : in out Watcher);
>
>     end To_Be_Discussed;

ARM 9.3 says, "If the task is created by the evaluation of an allocator for a 
given access type, it depends on each master that includes the elaboration of 
the declaration of the ultimate ancestor of the given access type."

The master of a task designated by a Task_Access is the environment task, 
assuming To_Be_Discussed to be a library unit. Meanwhile, finalization of an 
object of type Watcher will happen when you exit the scope of whatever unit 
declares the object. Those will usually be different if the object is declared 
outside of To_Be_Discussed. I'm not clear about the case where the object is 
declared inside To_Be_Discussed.

-- 
Jeff Carter
"Hello! Smelly English K...niggets."
Monty Python & the Holy Grail
08


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-16 17:08     ` Jeffrey Carter
@ 2014-06-17  6:57       ` Natasha Kerensikova
  2014-06-17  7:37         ` Dmitry A. Kazakov
                           ` (2 more replies)
  0 siblings, 3 replies; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-17  6:57 UTC (permalink / raw)


Hello,

On 2014-06-16, Jeffrey Carter <spam.jrcarter.not@spam.not.acm.org> wrote:
> ARM 9.3 says, "If the task is created by the evaluation of an allocator for a 
> given access type, it depends on each master that includes the elaboration of 
> the declaration of the ultimate ancestor of the given access type."
>
> The master of a task designated by a Task_Access is the environment task, 
> assuming To_Be_Discussed to be a library unit. Meanwhile, finalization of an 
> object of type Watcher will happen when you exit the scope of whatever unit 
> declares the object. Those will usually be different if the object is declared 
> outside of To_Be_Discussed. I'm not clear about the case where the object is 
> declared inside To_Be_Discussed.

So let's consider instead real code that actually (more or less) works:
https://github.com/faelys/natools/blob/trunk/src/natools-cron.ads
https://github.com/faelys/natools/blob/trunk/src/natools-cron.adb
(I whipped it together yesterday after thinking about Dmitry's
suggestion, but it's still only a rough draft, there are issues to iron
out before using it for real, like handling map key collisions. However
all comments are still welcome.)

Global_Worker here is either null or an access to a Worker created by an
allocator. Since as far as I can tell Natools.Cron is library-level, the
master of the worker task is the environment task.

In this implementation, the task terminates by reaching the end of its
body whenever the protected map becomes empty.

Storage is released only right before requested again when the protected
become non-empty again, so at program termination the task memory is
"leaked", but program termination is supposed to reclaim everything
anyway.

The finalization of a Cron_Entry removes the corresponding element from
the protected map, so the worker task terminates after all Cron_Entry
are finalized (or Reset to an empty state).

This leaves the problem of Cron_Entry objects declared in library level
packages:

   with Natools.Cron;

   package Test_Periodic is
      type Periodic_Action is new Natools.Cron.Callback with null record;

      overriding procedure Run (Self : in out Periodic_Action);

      Global_Entry : Natools.Cron.Cron_Entry;
   end Test_Periodic;

   with Ada.Text_IO;

   package body Test_Periodic is
      overriding procedure Run (Self : in out Periodic_Action) is
      begin
         Ada.Text_IO.Put_Line ("Hello");
      end Run;

   begin
      Global_Entry.Set (1.0, Periodic_Actoin'(null record));
   end Test_Periodic;

Now the master of Global_Entry is also the environment task. So
Global_Entry will not be finalized before the worker task terminates,
but by design the worker task will not terminate before Global_Entry is
finalized or Reset.

So unless at some point Global_Entry.Reset is called, this package will
cause the program to never terminate.

Is there a way to work around such a situation? Or should I just
document this as a caveat (among many others, as you can see at the
beginning of the spec)?


Thanks for your help,
Natasha

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  6:57       ` Natasha Kerensikova
@ 2014-06-17  7:37         ` Dmitry A. Kazakov
  2014-06-17  7:47           ` Natasha Kerensikova
  2014-06-17 12:02         ` Jacob Sparre Andersen
  2014-06-17 17:53         ` Jeffrey Carter
  2 siblings, 1 reply; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-17  7:37 UTC (permalink / raw)


On Tue, 17 Jun 2014 06:57:38 +0000 (UTC), Natasha Kerensikova wrote:

[...]
> Is there a way to work around such a situation?

The solution is always same: wrap a task pointer in a controlled object.
Manage the task from the object's Initialize and Finalize.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  7:37         ` Dmitry A. Kazakov
@ 2014-06-17  7:47           ` Natasha Kerensikova
  2014-06-17  8:45             ` Dmitry A. Kazakov
  0 siblings, 1 reply; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-17  7:47 UTC (permalink / raw)


Hello,

On 2014-06-17, Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
> On Tue, 17 Jun 2014 06:57:38 +0000 (UTC), Natasha Kerensikova wrote:
>
> [...]
>> Is there a way to work around such a situation?
>
> The solution is always same: wrap a task pointer in a controlled object.
> Manage the task from the object's Initialize and Finalize.
>
But if I change Global_Worker from an access-to-task to a controlled
object, its master will still be the environment task, so Finalize
wouldn't be called before termination of the worker task.

Or am I missing something? Is there a special construct that yield an
earlier call to Finalize than Cron_Entry? Would you happen to have a
working example?


Thanks for your help,
Natasha

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  7:47           ` Natasha Kerensikova
@ 2014-06-17  8:45             ` Dmitry A. Kazakov
  2014-06-17  9:00               ` Natasha Kerensikova
  0 siblings, 1 reply; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-17  8:45 UTC (permalink / raw)


On Tue, 17 Jun 2014 07:47:49 +0000 (UTC), Natasha Kerensikova wrote:

> Hello,
> 
> On 2014-06-17, Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
>> On Tue, 17 Jun 2014 06:57:38 +0000 (UTC), Natasha Kerensikova wrote:
>>
>> [...]
>>> Is there a way to work around such a situation?
>>
>> The solution is always same: wrap a task pointer in a controlled object.
>> Manage the task from the object's Initialize and Finalize.
>>
> But if I change Global_Worker from an access-to-task to a controlled
> object, its master will still be the environment task, so Finalize
> wouldn't be called before termination of the worker task.
> 
> Or am I missing something?

You should put it in a separate package to ensure different scopes of the
controlled object and the access-to-task type.

> Is there a special construct that yield an
> earlier call to Finalize than Cron_Entry? Would you happen to have a
> working example?

with Ada.Finalization;

package Workers is
   type Manager is new Ada.Finalization.Limited_Controlled with private;

private
   task type Worker is
      entry Live;
      entry Die;
   end Worker;
   type Worker_Ptr is access Worker;
   
   type Manager is new Ada.Finalization.Limited_Controlled with record
      Staff : Worker_Ptr;
   end record;
   overriding procedure Initialize (Handler : in out Manager);
   overriding procedure Finalize (Handler : in out Manager);

end Workers;
--------------------------------------------------------------
with Ada.Unchecked_Deallocation;

with Ada.Text_IO;  use Ada.Text_IO;

package body Workers is
   
   task body Worker is
   begin
      accept Live;
      Put_Line ("I am born!");
      loop
         select
            accept Die;
            exit;
         else delay 1.0;
            Put_Line ("I am alive!");
         end select;
      end loop;
      Put_Line ("You killed me!");
   end Worker;

   procedure Initialize (Handler : in out Manager) is
   begin
      Handler.Staff := new Worker;
      Handler.Staff.Live;
   end Initialize;
   
   procedure Finalize (Handler : in out Manager) is
      procedure Free is
         new Ada.Unchecked_Deallocation (Worker, Worker_Ptr);
   begin
      if Handler.Staff /= null then
         Handler.Staff.Die;
         while not Handler.Staff'Terminated loop
            delay 0.01;
         end loop;
         Free (Handler.Staff);
      end if;
   end Finalize;

end Workers;
-------------------------------------------------------
with Ada.Text_IO;  use Ada.Text_IO;
with Workers;      use Workers;

procedure Test is
   X : Manager;
begin
   delay 5.0;
   Put_Line ("Good bye!");
end Test;
-----------------------------------------------------

X is finalized before Workers (and Worker_Ptr), so it does not wait for all
instances to terminate.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  8:45             ` Dmitry A. Kazakov
@ 2014-06-17  9:00               ` Natasha Kerensikova
  2014-06-17 12:55                 ` Dmitry A. Kazakov
  0 siblings, 1 reply; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-17  9:00 UTC (permalink / raw)


Hello,

On 2014-06-17, Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
> [...]
> -------------------------------------------------------
> with Ada.Text_IO;  use Ada.Text_IO;
> with Workers;      use Workers;
>
> procedure Test is
>    X : Manager;
> begin
>    delay 5.0;
>    Put_Line ("Good bye!");
> end Test;
> -----------------------------------------------------
>
> X is finalized before Workers (and Worker_Ptr), so it does not wait for all
> instances to terminate.

As far as I can tell from my experimentations, this only works because
Test here is a procedure. The code I linked on github a few posts ago
also works fine that situation, since procedure-wide Cron_Entry objects
are finalized, and when the last one is finalized the tasks terminates.

The problem I'm trying to work around is when your X here is declare in
a package rather than a procedure:

   with Workers;  use Workers;
   package Test_Library is
      procedure Hello;

      X : Manager;
   end Test_Library;


   with Ada.Text_IO;  use Ada.Text_IO;
   package body Test_Library is
      procedure Hello is
      begin
         Put_Line ("Library procedure called");
      end Hello;
   end Test_Library;


   with Ada.Text_IO;   use Ada.Text_IO;
   with Test_Library;  use Test_Library;
   procedure Test_Main is
   begin
      Hello;
      delay 5.0;
      Put_Line ("Good bye!");
  end Test_Main;

Now there you get the catch-22 situation I'm hoping to solve (or failing
that, document).


Thanks for your help,
Natasha

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  6:57       ` Natasha Kerensikova
  2014-06-17  7:37         ` Dmitry A. Kazakov
@ 2014-06-17 12:02         ` Jacob Sparre Andersen
  2014-06-17 19:32           ` Natasha Kerensikova
  2014-06-17 17:53         ` Jeffrey Carter
  2 siblings, 1 reply; 25+ messages in thread
From: Jacob Sparre Andersen @ 2014-06-17 12:02 UTC (permalink / raw)


Natasha Kerensikova wrote:

> So let's consider instead real code that actually (more or less)
> works:
> https://github.com/faelys/natools/blob/trunk/src/natools-cron.ads
> https://github.com/faelys/natools/blob/trunk/src/natools-cron.adb (I
> whipped it together yesterday after thinking about Dmitry's
> suggestion, but it's still only a rough draft, there are issues to
> iron out before using it for real, like handling map key
> collisions. However all comments are still welcome.)

I've posted a pull request which eliminates all explicit memory
management from the package. :-)

... But not solved your actual problem. :-(

Greetings,

Jacob
-- 
"They that can give up essential liberty to obtain a little
 temporary safety deserve neither liberty nor safety."
                                        -- Benjamin Franklin


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  9:00               ` Natasha Kerensikova
@ 2014-06-17 12:55                 ` Dmitry A. Kazakov
  2014-06-17 14:51                   ` J-P. Rosen
  0 siblings, 1 reply; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-17 12:55 UTC (permalink / raw)


On Tue, 17 Jun 2014 09:00:47 +0000 (UTC), Natasha Kerensikova wrote:

> On 2014-06-17, Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
>> [...]
>> -------------------------------------------------------
>> with Ada.Text_IO;  use Ada.Text_IO;
>> with Workers;      use Workers;
>>
>> procedure Test is
>>    X : Manager;
>> begin
>>    delay 5.0;
>>    Put_Line ("Good bye!");
>> end Test;
>> -----------------------------------------------------
>>
>> X is finalized before Workers (and Worker_Ptr), so it does not wait for all
>> instances to terminate.
> 
> As far as I can tell from my experimentations, this only works because
> Test here is a procedure. The code I linked on github a few posts ago
> also works fine that situation, since procedure-wide Cron_Entry objects
> are finalized, and when the last one is finalized the tasks terminates.
> 
> The problem I'm trying to work around is when your X here is declare in
> a package rather than a procedure:
> 
>    with Workers;  use Workers;
>    package Test_Library is
>       procedure Hello;
> 
>       X : Manager;
>    end Test_Library;

OK. Moving it to the library level would make X and the task sharing the
same master. In which case any Finalize to be called after completion of
all dependent tasks.

When X is in a procedure then its master is the procedure and things work.

If you want a library level task, then the controlling object to shoot it
down must be at least one level deeper.

I thought about a schema when a dedicated library-level task could trigger
killing other library tasks:

   task body Killer is
      X : Event; -- Want its Finialize called upon completion
   begin
      select
         accept Dummy; -- No matter
      or terminate;
      end select;
   end Killer;

and then from the Event's Finalize you could kill all other tasks pending.

I am not a language lawyer and cannot tell if this schema must work or not. 

The RM is silent about the order in which tasks are awaited. If the tasks
shall be able to complete in any possible order this must be considered
illegal. [Which I would say is a bit over the top a requirement.] If there
shall exist at least one order in which they can complete, it could be
legal. But let's ask our language lawyers...

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 12:55                 ` Dmitry A. Kazakov
@ 2014-06-17 14:51                   ` J-P. Rosen
  2014-06-17 16:44                     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 25+ messages in thread
From: J-P. Rosen @ 2014-06-17 14:51 UTC (permalink / raw)


Le 17/06/2014 14:55, Dmitry A. Kazakov a écrit :
> I am not a language lawyer and cannot tell if this schema must work or not. 
It won't

> The RM is silent about the order in which tasks are awaited. If the tasks
> shall be able to complete in any possible order this must be considered
> illegal. [Which I would say is a bit over the top a requirement.] If there
> shall exist at least one order in which they can complete, it could be
> legal. But let's ask our language lawyers...
There is no order, because all tasks terminate together (9.3 (6..9)).

From the task you consider, you go to its master; if all tasks that
depend on that master are terminatable, then the whole bunch terminates
together. Therefore, adding a task cannot make tasks terminate if they
could not terminate without the extra task.

-- 
J-P. Rosen
Adalog
2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
Tel: +33 1 45 29 21 52, Fax: +33 1 45 29 25 00
http://www.adalog.fr


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 14:51                   ` J-P. Rosen
@ 2014-06-17 16:44                     ` Dmitry A. Kazakov
  2014-06-17 20:00                       ` Randy Brukardt
  0 siblings, 1 reply; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-17 16:44 UTC (permalink / raw)


On Tue, 17 Jun 2014 16:51:07 +0200, J-P. Rosen wrote:

> Le 17/06/2014 14:55, Dmitry A. Kazakov a écrit :
>> I am not a language lawyer and cannot tell if this schema must work or not. 
> It won't
> 
>> The RM is silent about the order in which tasks are awaited. If the tasks
>> shall be able to complete in any possible order this must be considered
>> illegal. [Which I would say is a bit over the top a requirement.] If there
>> shall exist at least one order in which they can complete, it could be
>> legal. But let's ask our language lawyers...
> There is no order, because all tasks terminate together (9.3 (6..9)).
> 
> From the task you consider, you go to its master; if all tasks that
> depend on that master are terminatable, then the whole bunch terminates
> together. Therefore, adding a task cannot make tasks terminate if they
> could not terminate without the extra task.

Thank you for clarification.

Thus per design there is no way to make a non-trivial library-level task to
complete without means outside the library level. Either some OS-specific
stuff or an explicit action/declaration in a nested scope is required.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17  6:57       ` Natasha Kerensikova
  2014-06-17  7:37         ` Dmitry A. Kazakov
  2014-06-17 12:02         ` Jacob Sparre Andersen
@ 2014-06-17 17:53         ` Jeffrey Carter
  2014-06-17 20:03           ` Randy Brukardt
  2 siblings, 1 reply; 25+ messages in thread
From: Jeffrey Carter @ 2014-06-17 17:53 UTC (permalink / raw)


On 06/16/2014 11:57 PM, Natasha Kerensikova wrote:
>
> Now the master of Global_Entry is also the environment task. So
> Global_Entry will not be finalized before the worker task terminates,
> but by design the worker task will not terminate before Global_Entry is
> finalized or Reset.
>
> So unless at some point Global_Entry.Reset is called, this package will
> cause the program to never terminate.
>
> Is there a way to work around such a situation? Or should I just
> document this as a caveat (among many others, as you can see at the
> beginning of the spec)?

AFAICT, there is no way for this situation to terminate without outside 
intervention.

-- 
Jeff Carter
"Saving keystrokes is the job of the text editor,
not the programming language."
Preben Randhol
64


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 12:02         ` Jacob Sparre Andersen
@ 2014-06-17 19:32           ` Natasha Kerensikova
  0 siblings, 0 replies; 25+ messages in thread
From: Natasha Kerensikova @ 2014-06-17 19:32 UTC (permalink / raw)


On 2014-06-17, Jacob Sparre Andersen <jacob@jacob-sparre.dk> wrote:
> Natasha Kerensikova wrote:
>
>> So let's consider instead real code that actually (more or less)
>> works:
>> https://github.com/faelys/natools/blob/trunk/src/natools-cron.ads
>> https://github.com/faelys/natools/blob/trunk/src/natools-cron.adb (I
>> whipped it together yesterday after thinking about Dmitry's
>> suggestion, but it's still only a rough draft, there are issues to
>> iron out before using it for real, like handling map key
>> collisions. However all comments are still welcome.)
>
> I've posted a pull request which eliminates all explicit memory
> management from the package. :-)

I was hesitating between posting my comments here or on the pull
request. I decided to post them here so that others can offer their
views and their explanations on the points I raise. If that's not the
best choice, please tell me so and I won't do it again.

> ... But not solved your actual problem. :-(

Basically that's my main concern: you turned a partial solution (that
works fine as long as global objects are explicitly reset) into a
complete non-solution (that never terminates). I'm afraid I'll have to
reject the pull request for this reasons.

But still, your version intrigues me. What go to such great lengths
only to eliminate memory management?

Maybe I'm too scarred by having forged myself into a C programmer that
can code for years without committing any resource leak, but I have
trouble to see what is so wrong with memory management to want so much
to eliminate it.


I understand that the less resource management the better is a good rule
of thumb, just like the less code the better. But isn't it a relatively
mild benefit to balance against all costs?

The core idea around which the package has been built, is to have a
worker task that terminate whenever the job map is empty, hoping it
would happen at program termination. And since it can also happen
temporarily during the program execution, there needs a way to
"unterminate" it when the job map is no longer empty. Since as far as I
know there is no way to restart a terminated task, reusing the task
object. So the only solution is to deallocate the terminate task object
and allocate a new one, ready to start.

My understanding of "explicit memory allocation" is stuff that involves
"new" keyword and Ada.Unchecked_Deallocation instances, and the
task-restart trick is the only explicit memory allocation I see in the
original Natools.Cron.

Getting rid of it means either an interminable static task like your
proposal, or hiding the allocation/deallocation behind a container or
some other wrapper, that seem of little value in that situation.


However, your proposal also eliminates the uses of Ada.Finalization and
of Natools.References. Even though I don't consider them as explicit
memory management, it seems you do. To me, Natools.Reference is as much
of an abstraction as Indefinite_Ordered_Maps, so I don't see any
explictness being remove through the use of the latter instead of the
former. And Ada.Finalization is about any resource management, and I
would say less for memory than for other resources. Anyway, it feels
like an extremely mild mechanism, I can't see why one would want to
avoid it.


Moreover, by removing Natools.References, you no longer have reference
semantics, so you cannot move references from one part of the code to
another, for example from the map inside the protected object to the
task.

I would guess that's why you moved the callback execution from the task
body to a procedure in the protected object. However, this has serious
drawbacks:

  * First, it becomes a bounded error to perform any potentially
blocking operation, for example Text_IO.Put_Line which I used in my
little example to test the package while developing it.

  * Second, it locks the database while running the callback, so the
callback cannot change its own period or unregister itself from the
service.

Also, not having references means copying the object around, which you
also do (but could have avoided using Update_Element), so you can no
longer store any mutable internal state in the callback object. My
little test example had a counter in it, and that is no longer possible.


Lastly, you conflated the callback interface with the job
identification, but probably realized that there can still be several
copies of the same objects, so you had to rely on a new Event_ID type.

However the Event_ID are ever-increasing values of Positive, without any
overflow management. One could probably hope that no sane
implementation-defined limit would be reached in this package, but even
though it's a fair assumption I would rather not assume it if that has
no cost.

Or is it only to solve the key collision issue? In that case I had
something else in mind (I will probably commit it tomorrow, no that I
have committed a test case for the issue).



So in summary, you did indeed eliminate explicit and
not-so-explicit-to-me memory management, but at the cost of:
  - breaking the partial solution to the problem considered,
  - forbidding potentially blocking operations in the callbacks,
  - forbidding callback management from within callbacks,
  - making it harder to write callbacks that inherit from
    Ada.Finalization.* (e.g. because they hold resources like files)
    or anything else,
  - introducing software aging with an implementation-defined threshold.

Is it really worth it? Is there a lesson here I'm failing to grasp?


Thanks for your comments,
Natasha


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 16:44                     ` Dmitry A. Kazakov
@ 2014-06-17 20:00                       ` Randy Brukardt
  2014-06-17 20:16                         ` Jeffrey Carter
  2014-06-17 21:30                         ` Simon Wright
  0 siblings, 2 replies; 25+ messages in thread
From: Randy Brukardt @ 2014-06-17 20:00 UTC (permalink / raw)


"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:k4bw956nq1hk.1fbqgpp5xu777$.dlg@40tude.net...
...
> Thus per design there is no way to make a non-trivial library-level task 
> to
> complete without means outside the library level. Either some OS-specific
> stuff or an explicit action/declaration in a nested scope is required.

Actually, there is a way, at least if you want to ensure that the program 
cleans itself up properly. We invented it for Claw, and I put it into the 
ACATS so it's pretty certain that all compilers support it.

The trick is to use Ada.Task_Identification to find out whether the 
environment task is trying to exit.

    if not Is_Callable(Environment_Task) then
        return; -- Exit this task.
   end if;

Is_Callable will only be False for the environment task if the main 
subprogram has exited and we're waiting for library-level tasks to complete. 
In that case, we want to kill off this task. (Note: Not all Ada 95 compilers 
did this at the time, some always returned true from it no matter what. But 
that would fail ACATS test CXC7004 in modern compilers, so it's unlikely 
that many get this wrong. One might want to look at that ACATS test for a 
complete example of the method.)

It can be clunky to get this into the task somewhere; it works best if the 
task is actively polling (as the message loop task in Claw is always doing).

Note: function Environment_Task was added to the package in Ada 2012. For 
earlier Ada, one needs to have the elaboration of the package containing the 
task squirrel away the task id:

   Environment_Task_Id : constant Task_Id := Current_Task;

                                               Randy.




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 17:53         ` Jeffrey Carter
@ 2014-06-17 20:03           ` Randy Brukardt
  0 siblings, 0 replies; 25+ messages in thread
From: Randy Brukardt @ 2014-06-17 20:03 UTC (permalink / raw)


"Jeffrey Carter" <spam.jrcarter.not@spam.not.acm.org> wrote in message 
news:lnpvb7$oat$3@dont-email.me...
> On 06/16/2014 11:57 PM, Natasha Kerensikova wrote:
>>
>> Now the master of Global_Entry is also the environment task. So
>> Global_Entry will not be finalized before the worker task terminates,
>> but by design the worker task will not terminate before Global_Entry is
>> finalized or Reset.
>>
>> So unless at some point Global_Entry.Reset is called, this package will
>> cause the program to never terminate.
>>
>> Is there a way to work around such a situation? Or should I just
>> document this as a caveat (among many others, as you can see at the
>> beginning of the spec)?
>
> AFAICT, there is no way for this situation to terminate without outside 
> intervention.

That's certainly true for this code as written, but there is a way to write 
the code so it will terminate. See my other message.

Note that you need both methods if you want to allow the objects to be 
declared anywhere: finalization for objects in nested scopes, and the 
Is_Callable trick for those declared at library-level.

                                   Randy.




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-16 13:54   ` Natasha Kerensikova
@ 2014-06-17 20:14     ` Charles H. Sampson
  2014-06-18  7:32       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 25+ messages in thread
From: Charles H. Sampson @ 2014-06-17 20:14 UTC (permalink / raw)


Natasha Kerensikova <lithiumcat@instinctive.eu> wrote:

> Hello,
> 
> On 2014-06-15, J-P. Rosen <rosen@adalog.fr> wrote:
> > Le 15/06/2014 12:10, Natasha Kerensikova a écrit :
> >> And same as before, the task should be able to terminate quickly when
> >> the program terminates.
> > Why not have a "stop" entry called by the main program when it
> > terminates (possibly through an exported subprogram if you don't want to
> > have the task public)?
> 
> Mostly because I believe this to be too heavy for a burden for the
> client, and somewhat of an abstraction leak.

     I'm having trouble understanding how this is too heavy. In most
programs I've written, there's a possibility of stopping. Usually the
need to stop is detected by or, most commonly, propagated to the main
program. The main program then signals all of its library packages to do
whatever is necessary for stopping. An exported Stop subprogram seems a
quite natural way to do that.

     I've even used implementations that have exported Stop subprograms
in all library packages, some of them null. That enforces the
abstractions in that the main program doesn't need to know which library
packages need to be wrapped up.

                        Charlie
-- 
Nobody in this country got rich on his own.  You built a factory--good.
But you moved your goods on roads we all paid for.  You hired workers we
all paid to educate. So keep a big hunk of the money from your factory.
But take a hunk and pay it forward.  Elizabeth Warren (paraphrased)

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 20:00                       ` Randy Brukardt
@ 2014-06-17 20:16                         ` Jeffrey Carter
  2014-06-17 21:30                         ` Simon Wright
  1 sibling, 0 replies; 25+ messages in thread
From: Jeffrey Carter @ 2014-06-17 20:16 UTC (permalink / raw)


On 06/17/2014 01:00 PM, Randy Brukardt wrote:
>
> Actually, there is a way, at least if you want to ensure that the program
> cleans itself up properly. We invented it for Claw, and I put it into the
> ACATS so it's pretty certain that all compilers support it.
>
> The trick is to use Ada.Task_Identification to find out whether the
> environment task is trying to exit.
>
>      if not Is_Callable(Environment_Task) then
>          return; -- Exit this task.
>     end if;

That's good to know. I'll try to add it to my bag of tricks (though of course 
not needing any tricks is better).

-- 
Jeff Carter
"Whatever it is, I'm against it."
Horse Feathers
46


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 20:00                       ` Randy Brukardt
  2014-06-17 20:16                         ` Jeffrey Carter
@ 2014-06-17 21:30                         ` Simon Wright
  1 sibling, 0 replies; 25+ messages in thread
From: Simon Wright @ 2014-06-17 21:30 UTC (permalink / raw)


"Randy Brukardt" <randy@rrsoftware.com> writes:

> But that would fail ACATS test CXC7004 in modern compilers, so it's
> unlikely that many get this wrong.

Interestingly, FSF GCC is stuck on ACATS 2.4 or thereabouts, and has no
CXC tests at all. One hopes that AdaCore are more up-to-date
in-house. Perhaps getting current/next ACATS into the GCC testsuite
would be a worthwhile exercise for me ...

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Termination of periodic tasks
  2014-06-17 20:14     ` Charles H. Sampson
@ 2014-06-18  7:32       ` Dmitry A. Kazakov
  0 siblings, 0 replies; 25+ messages in thread
From: Dmitry A. Kazakov @ 2014-06-18  7:32 UTC (permalink / raw)


On Tue, 17 Jun 2014 13:14:23 -0700, Charles H. Sampson wrote:

> Natasha Kerensikova <lithiumcat@instinctive.eu> wrote:
> 
>> Hello,
>> 
>> On 2014-06-15, J-P. Rosen <rosen@adalog.fr> wrote:
>>> Le 15/06/2014 12:10, Natasha Kerensikova a écrit :
>>>> And same as before, the task should be able to terminate quickly when
>>>> the program terminates.
>>> Why not have a "stop" entry called by the main program when it
>>> terminates (possibly through an exported subprogram if you don't want to
>>> have the task public)?
>> 
>> Mostly because I believe this to be too heavy for a burden for the
>> client, and somewhat of an abstraction leak.
> 
>      I'm having trouble understanding how this is too heavy. In most
> programs I've written, there's a possibility of stopping. Usually the
> need to stop is detected by or, most commonly, propagated to the main
> program. The main program then signals all of its library packages to do
> whatever is necessary for stopping. An exported Stop subprogram seems a
> quite natural way to do that.

That is true. I am writing a lot of code with tasking and was unaware of
the issue with library level tasks.

>      I've even used implementations that have exported Stop subprograms
> in all library packages, some of them null. That enforces the
> abstractions in that the main program doesn't need to know which library
> packages need to be wrapped up.

I prefer a stateless design of packages with explicit objects maintaining
the state, created by the client. That eliminates the problem of task
termination too.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de


^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2014-06-18  7:32 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-06-15 10:10 Termination of periodic tasks Natasha Kerensikova
2014-06-15 12:11 ` Dmitry A. Kazakov
2014-06-15 15:23 ` J-P. Rosen
2014-06-16 13:54   ` Natasha Kerensikova
2014-06-17 20:14     ` Charles H. Sampson
2014-06-18  7:32       ` Dmitry A. Kazakov
2014-06-15 16:54 ` Jeffrey Carter
2014-06-16 14:02   ` Natasha Kerensikova
2014-06-16 15:08     ` Dmitry A. Kazakov
2014-06-16 17:08     ` Jeffrey Carter
2014-06-17  6:57       ` Natasha Kerensikova
2014-06-17  7:37         ` Dmitry A. Kazakov
2014-06-17  7:47           ` Natasha Kerensikova
2014-06-17  8:45             ` Dmitry A. Kazakov
2014-06-17  9:00               ` Natasha Kerensikova
2014-06-17 12:55                 ` Dmitry A. Kazakov
2014-06-17 14:51                   ` J-P. Rosen
2014-06-17 16:44                     ` Dmitry A. Kazakov
2014-06-17 20:00                       ` Randy Brukardt
2014-06-17 20:16                         ` Jeffrey Carter
2014-06-17 21:30                         ` Simon Wright
2014-06-17 12:02         ` Jacob Sparre Andersen
2014-06-17 19:32           ` Natasha Kerensikova
2014-06-17 17:53         ` Jeffrey Carter
2014-06-17 20:03           ` Randy Brukardt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox