comp.lang.ada
 help / color / mirror / Atom feed
* Structure of the multitasking server
@ 2008-09-19 12:21 Maciej Sobczak
  2008-09-19 13:34 ` Jean-Pierre Rosen
  2008-09-19 23:01 ` anon
  0 siblings, 2 replies; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-19 12:21 UTC (permalink / raw)


Hi all,

Imagine a server with fixed number of worker tasks. There is no queue
of jobs and jobs are immediately passed to one of the tasks that is
currently idle. There is a separate task (or just the main one) that
provides jobs for worker tasks.

I am concerned with the proper structure of objects - I mean in the
sense of recommended Ada practice.
Obviously there is a need for some shared resource where the
requesting task will put the job and from where the worker task will
pick it up.

This is more or less what I came up with, where the "channel" is a
single processing pipeline:

   type Worker_State is (Idle, Ready, Working);

   protected type Channel_State is
      procedure Post (J : in Job_Type);
      entry Get_Job (J : out Job_Type);
      function Busy return Boolean;
   private
      State : Worker_State := Idle;
      Job_To_Do : Job_Type;
   end Channel_State;

   protected body Channel_State is

      procedure Post (J : in Job_Type) is
      begin
         if State /= Idle then
            raise Program_Error;
         end if;

         Job_To_Do := J;
         State := Ready;
      end Post;

      entry Get_Job (J : out Job_Type) when State = Ready is
      begin
         J := Job_To_Do;
         State := Working;
      end Get_Job;

      function Busy return Boolean is
      begin
         return State /= Idle;
      end Busy;

   end Channel_State;

   type Channel;
   task type Worker_Task (Ch : access Channel);

   type Channel is record
      State : Channel_State;
      Worker : Worker_Task (Channel'Access);
   end record;

   task body Worker_Task is
      Job : Job_Type;
   begin
      loop
         Ch.all.Get_Job (Job);

         --  do the job ...

      end loop;
   end Worker_Task;

   Max_Channels : constant := 5;

   Channels : array (1 .. Max_Channels) of Channel;

My question is whether this is what a seasoned Ada programmer would
do.
Initially I tried to have two separate arrays, one for jobs and one
for worker tasks, but I found it difficult to link workers with their
respective jobs. After bundling them together in a single record that
is referenced from the task it worked and I actually find it
structured better.

The main task after constructing a job object finds some channel where
the worker task is not busy and posts the job to its shared state
component:

   loop
      Job := ...

      Found_Worker := False;
      for I in Channels'Range loop
         if not Channels (I).State.Busy then
            Channels (I).State.Post (Job);
            Found_Worker := True;
            exit;
         end if;
      end loop;

      if not Found_Worker then
         --  all pipelines are busy,
         --  but the overflow handling is not shown...
      end if;
   end loop;

All this works fine, but my question considers the choice of language
constructs and idioms.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-19 12:21 Structure of the multitasking server Maciej Sobczak
@ 2008-09-19 13:34 ` Jean-Pierre Rosen
  2008-09-19 17:02   ` Dmitry A. Kazakov
                     ` (2 more replies)
  2008-09-19 23:01 ` anon
  1 sibling, 3 replies; 32+ messages in thread
From: Jean-Pierre Rosen @ 2008-09-19 13:34 UTC (permalink / raw)


Maciej Sobczak a �crit :
> Hi all,
> 
> Imagine a server with fixed number of worker tasks. There is no queue
> of jobs and jobs are immediately passed to one of the tasks that is
> currently idle. There is a separate task (or just the main one) that
> provides jobs for worker tasks.
[...]

Why not simply use a rendezvous?
Each worker has an entry Get_Job:

     task body Worker_Task is
        Job : Job_Type;
     begin
        loop
           Get_Job (Job);

           --  do the job ...

        end loop;
     end Worker_Task;

and the server is simply (assuming Servers is an array of Worker_Task):

     loop
        Job := ...

        Found_Worker := False;
        for I in Servers'Range loop
           select
              Server (I).Get_Job (Job);
              Found_Worker := True;
              exit;
           else
              -- This server is busy
              null;
           end select;
        end loop;

        if not Found_Worker then
           --  all pipelines are busy,
           --  but the overflow handling is not shown...
        end if;
     end loop;
-- 
---------------------------------------------------------
            J-P. Rosen (rosen@adalog.fr)
Visit Adalog's web site at http://www.adalog.fr



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

* Re: Structure of the multitasking server
  2008-09-19 13:34 ` Jean-Pierre Rosen
@ 2008-09-19 17:02   ` Dmitry A. Kazakov
  2008-09-21 17:30     ` Maciej Sobczak
  2008-09-21 17:23   ` Maciej Sobczak
  2015-03-12 16:07   ` gautier_niouzes
  2 siblings, 1 reply; 32+ messages in thread
From: Dmitry A. Kazakov @ 2008-09-19 17:02 UTC (permalink / raw)


On Fri, 19 Sep 2008 15:34:00 +0200, Jean-Pierre Rosen wrote:

> Maciej Sobczak a �crit :
>> 
>> Imagine a server with fixed number of worker tasks. There is no queue
>> of jobs and jobs are immediately passed to one of the tasks that is
>> currently idle. There is a separate task (or just the main one) that
>> provides jobs for worker tasks.
> [...]
> 
> Why not simply use a rendezvous?
> Each worker has an entry Get_Job:
> 
>      task body Worker_Task is
>         Job : Job_Type;
>      begin
>         loop
>            Get_Job (Job);

You mean:

accept Get_Job (Requested : Job_Type) do
   Job := Requested;
end Get_Job;

>         end loop;
>      end Worker_Task;
> 
> and the server is simply (assuming Servers is an array of Worker_Task):
> 
>      loop
>         Job := ...
> 
>         Found_Worker := False;
>         for I in Servers'Range loop
>            select
>               Server (I).Get_Job (Job);
>               Found_Worker := True;
>               exit;
>            else
>               -- This server is busy
>               null;
>            end select;
>         end loop;
> 
>         if not Found_Worker then
>            --  all pipelines are busy,
>            --  but the overflow handling is not shown...
>         end if;
>      end loop;

This scheme requires some additional efforts in order to maintain the list
of idle workers.

I would use an inverse one, which is much simpler:

with Ada.Text_IO;  use Ada.Text_IO;

   type Job_Type is (Quit, Run);
   type Job (Kind : Job_Type := Quit) is null record;
   task type Worker;
   task Server is
      entry Get (Work : out Job);
   end Server;

   task body Server is
      Request : Job;
      Workers : array (1..5) of Worker;
   begin
      loop
            -- Get a job to do
         Request := (Kind => Run);
            -- Wait for a worker to come
         accept Get (Work : out Job) do
            Work := Request;
         end Get;
      end loop;

      -- Terminating workers
      for Index in Workers'Range loop
         Request := (Kind => Quit);
         accept Get (Work : out Job) do
            Work := Request;
         end Get;
      end loop;
   end Server;

   task body Worker is
      Work : Job;
   begin
      loop
         Server.Get (Work);
         case Work.Kind is
            when Quit => exit;
            when Run  => Put_Line ("Doing things");
         end case;
      end loop;
      Put_Line ("I am done");
   end Worker;

Note that task termination is usually a difficult problem in Ada. You
should pay an attention to this early. (The "terminate" alternative is
unusable in 80% cases.) In the solution above a special job type is used to
kill the worker.

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



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

* Re: Structure of the multitasking server
  2008-09-19 12:21 Structure of the multitasking server Maciej Sobczak
  2008-09-19 13:34 ` Jean-Pierre Rosen
@ 2008-09-19 23:01 ` anon
  2008-09-21 17:37   ` Maciej Sobczak
  1 sibling, 1 reply; 32+ messages in thread
From: anon @ 2008-09-19 23:01 UTC (permalink / raw)


Since you are using Channels, I am assuming that your talking about TCP/IP 
servers. In this case you should look into using the Check_Selector with 
the use of Signalling_Fds and Socket_Sets instead of using arrays.

Now the way GNAT has written the Selector function and the way the C_Select
is written the maximum number of server per Check_Selector is 27.  That is, 
each C_Select function can only monitor 32 sockets, but 
GNAT.Sockets.Create_Selector uses two socket and the TCP/IP sub-system 
uses or predefines 3, so you are left with 27 user define servers.

Found this clent/server on the net.  The following client/server shows how to 
use the Check_Selector routine. They were both were written in all caps and 
use Ada-95 spec so,  you may need to adjust the Signalling_Fds routine 
calls for Ada-2005 (GNAT-2008), but they do compile and excute under 
GNAT GPL 2007 using Linux. 

Note: 1) I alter for spacing and cap format. To make them more readable.
      2) The procedure "Set_Socket_Option" may need to be commented 
          out.  Some TCP/IP system do not like GNAT version of that 
          routine.
      3) As for Windows, I did not test!


--------------------------------------------------------------
--
--  Pool_Server
--
with GNAT.Sockets ; 
use GNAT.Sockets ;
with Ada.Text_IO ;

procedure Pool_Server is

    MaxTasks : constant Positive := 5 ;  -- buffer size
    type Index is mod MaxTasks ;

    function Rev ( S : String ) return String is
        Res : String ( S'Range ) ;
        J : Integer := S'First ;
      begin
        for I in reverse S'Range loop
         Res ( J ) := S ( I ) ;
         J := J + 1 ;
        end loop ;
        return Res ;
      end Rev ;

    protected Aborted is
      procedure Set ;
      function Check return Boolean ;
    private
      Done : Boolean := False ;
    end Aborted ;

    protected body Aborted is
      procedure Set is
        begin
          Done := True ;
        end Set ;

      function Check return Boolean is
        begin
          return Done ;
        end Check ;
    end Aborted ;


   type Echo ;
   type Echo_Access is access Echo ;

    task type Echo is
      entry Start ( N_Sock : IN Socket_Type ;
                    Self : IN Echo_Access   ) ;
      entry ReStart ( N_Sock : IN Socket_Type ) ;
    end Echo ;

    type Task_Array is array ( Index ) of Echo_Access ;

    protected Buffer is
      entry Deposit ( X : in Echo_Access ) ;
      entry Extract ( X : out Echo_Access ) ;
      function NumWaiting return Natural ;
    private
      Buf : Task_Array ;
      I, J : Index := 0 ;
      Count : Natural range 0 .. MaxTasks := 0 ;
    end Buffer ;


    task body Echo is
        Sock : Socket_Type ;
        S : Stream_Access ;
        Me : Echo_Access ;
        Input_Selector : Selector_Type ;
        Input_Set : Socket_Set_Type ;
        WSet : Socket_Set_Type ;
        Input_Status : Selector_Status ;
      begin
      --set up selector
      Create_Selector ( Input_Selector ) ;

      --Initialise socket sets
      --WSet is always empty as we are not interested in output events
      -- RSet only ever contains one socket namely Sock
      Empty ( Input_Set ) ;
      Empty ( WSet ) ;

      ACCEPT Start ( N_Sock : IN Socket_Type ;
                     Self : IN Echo_Access   ) DO
        Sock := N_Sock ;
        Me := Self ;
      end Start ;

      loop
        begin   -- block for exception handling
          S := Stream ( Sock ) ;    -- set up stream on socket
          Boolean'Write ( S, True ) ;  -- acknowledge connection

          loop
            -- check for input on Sock socket
            Set ( Input_Set, Sock ) ;

            -- time-out on check if no input within 0.5 second
            Check_Selector ( Input_Selector,
                             Input_Set,
                             WSet,
                             Input_Status,
                             0.5 ) ;
            if Input_Status = Completed then
              -- we have input, so process it
              declare
                  Str : String := String'Input ( S ) ;
              begin
                exit when Str = "quit" ;
                String'Output ( S, Rev ( Str ) ) ;
              end ;
            end if ;
            if Aborted.Check then
              String'Output ( S, "Server aborted" ) ;
              exit ;
            end if ;
          end loop ;

          Ada.Text_IO.New_Line ;
          Ada.Text_IO.Put_Line ( "Slave Closing Connection" ) ;
          ShutDown_Socket ( Sock, Shut_Read_Write ) ;
          Buffer.Deposit ( Me ) ;

          exception
            -- The mostly likely exception is if client quits unexpectedly
            -- close the socket and deposit ourselves in the buffer
          when others =>
               Ada.Text_IO.New_Line ;
               Ada.Text_IO.Put_Line ( "Connection closed unexpectedly" ) ;
               Close_Socket ( Sock ) ;
               Buffer.Deposit ( Me ) ;
        end ;

        select
          ACCEPT ReStart ( N_Sock : IN Socket_Type ) DO
            Sock := N_Sock ;
          end ReStart ;
        or
          -- terminate if all slaves are queued here and
          -- if the main server task has finished
          terminate ;
        end select ;

      end loop ;
    end Echo ;

    protected body Buffer is
      entry Deposit ( X : IN Echo_Access ) when Count < MaxTasks is
        begin
          Buf ( I ) := X ;
          I := I + 1 ;
          Count := Count + 1 ;
        end Deposit ;

      entry Extract ( X : OUT Echo_Access ) when Count > 0 is
        begin
          X := Buf ( J ) ;
          J := J + 1 ;
          Count := Count - 1 ;
        end Extract ;

      function NumWaiting return Natural is
      begin
         return Count ;
      end NumWaiting ;
   end Buffer ;

    Server          : Socket_Type ;
    New_Sock        : Socket_Type ;
    Slave           : Echo_Access ;
    Addr            : Sock_Addr_Type ( Family_Inet ) ;
    Peer_Addr       : Sock_Addr_Type ( Family_Inet ) ;
    Avail           : Boolean := False ;
    Ch              : Character ;
    TotalTasks      : Natural := 0 ;
    Accept_Selector : Selector_Type ;
    Accept_Set      : Socket_Set_Type ;
    WSet            : Socket_Set_Type ;
    Accept_Status   : Selector_Status ;

  begin  --  main server task
    Ada.Text_IO.Put_Line ( "WARNING server loops for ever." ) ;
    Ada.Text_IO.Put ( "Press A to terminate server and all " ) ;
    Ada.Text_IO.Put_Line ( "tasks immediately or press Q to ") ;
    Ada.Text_IO.Put ( "accept no further connections and " ) ;
    Ada.Text_IO.Put ( "terminate gracefully when all clients " ) ;
    Ada.Text_IO.Put ( "are fully when all clients are through." ) ;
    Ada.Text_IO.New_Line ;
    Initialize ;
    Create_Socket ( Server) ;
    Addr := ( Family_Inet,
              Addresses ( Get_Host_By_Name ( Host_Name ), 1 ),
              50000 ) ;
    --  allow server address to be reused for multiple connections
    Set_Socket_Option ( Server, 
                        Socket_Level,
                        ( Reuse_Address, True ) ) ;

    Bind_Socket ( Server, Addr ) ;
    Listen_Socket ( Server, 4 ) ;

    --  set up selector
    Create_Selector ( Accept_Selector ) ;

    --  Initialise socket sets
    --  WSet is always empty as we are not interested in output 
    --  events Accept_Set only ever contains one socket namely 
    --  Server
    Empty ( Accept_Set ) ;
    Empty ( WSet ) ;
    loop
      Ada.Text_IO.Get_Immediate ( Ch, Avail ) ;
      if Avail and then
         ( Ch = 'q' or Ch = 'Q' or Ch = 'a' or Ch = 'A' ) then
        exit ;
      end if ;

      --  check for input (connection requests) on Server socket
      Set ( Accept_Set, Server ) ;
      --  time-out on check if no request within 1 second
      Check_Selector ( Accept_Selector, 
                       Accept_Set,
                       WSet, 
                       Accept_Status, 
                       1.0 ) ;

      if Accept_Status = Completed then
        --  must be an event on Server socket as it is the only 
        --  one that we are checking.
        --  Hence the Accept_Socket call should not block.

        Accept_Socket ( Server, New_Sock, Peer_Addr ) ;
        Ada.Text_IO.New_Line ;
        Ada.Text_IO.Put_Line 
                        ( "Connection accepted -- allocating slave" ) ;


      if Buffer.NumWaiting = 0  and TotalTasks < MaxTasks then
          Slave := NEW Echo ;              -- start new task
          TotalTasks := TotalTasks + 1 ;
          Ada.Text_IO.Put_Line ( "New slave task started" ) ;
          --  call entry Start to activate task
          Slave.Start ( New_Sock, Slave ) ;
        else
          Ada.Text_IO.Put_Line ( "Waiting for an idle slave task" ) ;
          Buffer.Extract ( Slave ) ;
          --  call entry Start to re-activate task
          Slave.ReStart ( New_Sock ) ;
          Ada.Text_IO.Put_Line ( " Idle slave task reactivated" ) ;
        end if ;
      end if ;
    end loop ;

    if Ch = 'a' or Ch = 'A' then
      --  signal slave tasks to terminate
      Aborted.Set ;
    end if ;

   --  tidy up
    Close_Selector ( Accept_Selector ) ;
    Empty ( Accept_Set ) ;

    Close_Socket ( Server ) ;
    Ada.Text_IO.New_Line ;
    Ada.Text_IO.Put_Line ( "Main server task exiting ..." ) ;
    Finalize ;
end Pool_Server ;


--------------------------------------------------------------
--
--  Pool_Client
--
with Gnat.Sockets ;
use  Gnat.Sockets ;
with Ada.Command_Line ; 
use  Ada.Command_Line ;
with Ada.Text_IO ;
use  Ada.Text_IO ;

procedure Pool_Client is
    Sock : Socket_Type ;
    S : Stream_Access ;
    Addr : Sock_Addr_Type ( Family_Inet ) ;
    Msg : String ( 1 .. 80 ) ;
    Last : Natural ;
    B : Boolean ;
    Read_Selector : Selector_Type ;
    Read_Set, WSet : Socket_Set_Type ;
    Read_Status : Selector_Status ;
  begin
    Initialize ;
    Create_Socket ( Sock ) ;
    Addr := ( Family_Inet,
              Addresses ( Get_Host_By_Name ( Argument ( 1 ) ), 1 ),
              50000 ) ;
    Create_Selector ( Read_Selector ) ;
    Empty ( Read_Set ) ;
    Empty ( WSet ) ;


    Connect_Socket ( Sock, Addr ) ;
    S := Stream ( Sock ) ;
    Boolean'Read ( S, B ) ;
    -- wait for connection to be accepted

    loop
      Set ( Read_Set, Sock ) ;

      -- check for input on socket  (server may be aborting)
      -- time-out immediately if no input pending
      -- We seem to need a small delay here (using zero seems to block
      -- forever)
      -- Is this a GNAT bug or AB misreading Check_Selector docs?

      Check_Selector ( Read_Selector, 
                       Read_Set,
                       WSet, 
                       Read_Status,
                       0.005 ) ;
      if Read_Status = Expired then
        Ada.Text_IO.Put ( "Message> " ) ;  -- prompt user for message
        Ada.Text_IO.Get_Line ( Msg, Last ) ;

        -- send message to socket unless server is aborting
        String'Output ( S, Msg ( 1 .. Last ) ) ;
        exit when Msg ( 1 .. Last ) = "quit" ;
      end if ;

      declare 
          -- receive message
          Str : String := String'Input ( S ) ;
      begin      Ada.Text_IO.Put_Line ( Str ) ;
        exit when Str = "Server aborted" ;
      end ;
    end loop ;

    Ada.Text_IO.Put_Line ( "Client quitting ..." ) ;
    ShutDown_Socket ( Sock ) ;
    Close_Selector ( Read_Selector ) ;
    Finalize ;
  exception 
    when others =>
        Ada.Text_IO.Put_Line ("Exception: Client quitting ..." ) ;
        Close_Socket ( Sock ) ;
        Close_Selector( Read_Selector ) ;
        Finalize ;
  end Pool_Client ;


In <8b4d1170-22e6-40d3-8ed1-096dc0163491@m36g2000hse.googlegroups.com>, Maciej Sobczak <see.my.homepage@gmail.com> writes:
>Hi all,
>
>Imagine a server with fixed number of worker tasks. There is no queue
>of jobs and jobs are immediately passed to one of the tasks that is
>currently idle. There is a separate task (or just the main one) that
>provides jobs for worker tasks.
>
>I am concerned with the proper structure of objects - I mean in the
>sense of recommended Ada practice.
>Obviously there is a need for some shared resource where the
>requesting task will put the job and from where the worker task will
>pick it up.
>
>This is more or less what I came up with, where the "channel" is a
>single processing pipeline:
>
>   type Worker_State is (Idle, Ready, Working);
>
>   protected type Channel_State is
>      procedure Post (J : in Job_Type);
>      entry Get_Job (J : out Job_Type);
>      function Busy return Boolean;
>   private
>      State : Worker_State := Idle;
>      Job_To_Do : Job_Type;
>   end Channel_State;
>
>   protected body Channel_State is
>
>      procedure Post (J : in Job_Type) is
>      begin
>         if State /= Idle then
>            raise Program_Error;
>         end if;
>
>         Job_To_Do := J;
>         State := Ready;
>      end Post;
>
>      entry Get_Job (J : out Job_Type) when State = Ready is
>      begin
>         J := Job_To_Do;
>         State := Working;
>      end Get_Job;
>
>      function Busy return Boolean is
>      begin
>         return State /= Idle;
>      end Busy;
>
>   end Channel_State;
>
>   type Channel;
>   task type Worker_Task (Ch : access Channel);
>
>   type Channel is record
>      State : Channel_State;
>      Worker : Worker_Task (Channel'Access);
>   end record;
>
>   task body Worker_Task is
>      Job : Job_Type;
>   begin
>      loop
>         Ch.all.Get_Job (Job);
>
>         --  do the job ...
>
>      end loop;
>   end Worker_Task;
>
>   Max_Channels : constant := 5;
>
>   Channels : array (1 .. Max_Channels) of Channel;
>
>My question is whether this is what a seasoned Ada programmer would
>do.
>Initially I tried to have two separate arrays, one for jobs and one
>for worker tasks, but I found it difficult to link workers with their
>respective jobs. After bundling them together in a single record that
>is referenced from the task it worked and I actually find it
>structured better.
>
>The main task after constructing a job object finds some channel where
>the worker task is not busy and posts the job to its shared state
>component:
>
>   loop
>      Job := ...
>
>      Found_Worker := False;
>      for I in Channels'Range loop
>         if not Channels (I).State.Busy then
>            Channels (I).State.Post (Job);
>            Found_Worker := True;
>            exit;
>         end if;
>      end loop;
>
>      if not Found_Worker then
>         --  all pipelines are busy,
>         --  but the overflow handling is not shown...
>      end if;
>   end loop;
>
>All this works fine, but my question considers the choice of language
>constructs and idioms.
>
>--
>Maciej Sobczak * www.msobczak.com * www.inspirel.com
>
>Database Access Library for Ada: www.inspirel.com/soci-ada




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

* Re: Structure of the multitasking server
  2008-09-19 13:34 ` Jean-Pierre Rosen
  2008-09-19 17:02   ` Dmitry A. Kazakov
@ 2008-09-21 17:23   ` Maciej Sobczak
  2008-09-22  8:23     ` Jean-Pierre Rosen
  2015-03-12 16:07   ` gautier_niouzes
  2 siblings, 1 reply; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-21 17:23 UTC (permalink / raw)


On 19 Wrz, 15:34, Jean-Pierre Rosen <ro...@adalog.fr> wrote:

> Why not simply use a rendezvous?

This is something that I deliberately wanted to avoid.
I don't like the idea of tasks interacting with each other directly
and I prefer to have the communication part extracted away (Ravenscar
got that right for a reason, I think?).

In theory, I could also change the queuing strategy without modifying
the worker task.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-19 17:02   ` Dmitry A. Kazakov
@ 2008-09-21 17:30     ` Maciej Sobczak
  2008-09-21 19:24       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-21 17:30 UTC (permalink / raw)


On 19 Wrz, 19:02, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> Note that task termination is usually a difficult problem in Ada. You
> should pay an attention to this early.

No problem with that. With separate protected object (instead of
rendezvous) it is enough to do this:

   protected type Channel_State is
      procedure Post (J : in Job_Type);
      procedure Finish;
      entry Get_Job (J : out Job_Type; Finished : out Boolean);
      function Busy return Boolean;
   private
      State : Worker_State := Idle;
      Job_To_Do : Job_Type;
      Should_Finish : Boolean := False;
   end Channel_State;

and later in the worker task:

      loop
         Ch.all.Get_Job (Job, Finished);
         exit when Finished;

         --  do the job ...

      end loop;


There is no need to introduce any special type of job ("poison pill").
Above, the job space is not polluted with task lifetime management
concepts - these should be kept separate.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-19 23:01 ` anon
@ 2008-09-21 17:37   ` Maciej Sobczak
  2008-09-22  2:32     ` anon
  0 siblings, 1 reply; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-21 17:37 UTC (permalink / raw)


On 20 Wrz, 01:01, a...@anon.org (anon) wrote:

> Since you are using Channels, I am assuming that your talking about TCP/IP
> servers.

Not necessarily, although in this case it is indeed true.

> In this case you should look into using the Check_Selector

Why? If I want to keep the number of pipelines relatively small, then
fixed pool of tasks seems to be quite clean.

> Now the way GNAT has written the Selector function and the way the C_Select
> is written the maximum number of server per Check_Selector is 27.

Is it documented somewhere?
The only "documentation" I have found is the .ads file for
GNAT.Sockets, and this detail is not mentioned there.

Also, on my system the default limit on the number of file descriptors
used with select(2) is 1024.

> That is,
> each C_Select function can only monitor 32 sockets

What is C_Select? Why would it be more limited than select(2) (the
system-level one)?

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-21 17:30     ` Maciej Sobczak
@ 2008-09-21 19:24       ` Dmitry A. Kazakov
  2008-09-21 21:27         ` Maciej Sobczak
  0 siblings, 1 reply; 32+ messages in thread
From: Dmitry A. Kazakov @ 2008-09-21 19:24 UTC (permalink / raw)


On Sun, 21 Sep 2008 10:30:37 -0700 (PDT), Maciej Sobczak wrote:

> On 19 Wrz, 19:02, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>> Note that task termination is usually a difficult problem in Ada. You
>> should pay an attention to this early.
> 
> No problem with that. With separate protected object (instead of
> rendezvous) it is enough to do this:

Of course there is a problem because conditional and timed entry calls may
not contain a "terminate" alternative. This is another reason why a
protected object might be a poor choice. But the way you used protected
object looks like a design noise. Technically you just replaced a
rendezvous queue with a protected entry queue. Instead of just server and
workers, you have server, workers, channels and management stuff. Very
OO-ish in the negative sense of this word.

The second problem which adds complexity is server to worker 1-n
communication. Should be n-1 worker to server, simpler (classic
client-server) and more efficient too.

Due to complexity you have overlooked to make a channel idle again after a
job is done. You also have overlooked to add a "Shut_Down" state in order
to finalize workers: four channel states instead of just none. And note,
that the type Channel cannot call to the procedure Finish to set Shut_Down
from its Finalize (if it were controlled). That is because Worker is a
component of. The thing will hang up on channel destruction. 

> There is no need to introduce any special type of job ("poison pill").

Job carries the parameters of a worker. You used it too, under the name
Job_Type.

> Above, the job space is not polluted with task lifetime management
> concepts - these should be kept separate.

You have polluted it with the procedure Finish, that has to be called
outside the worker tasks.

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



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

* Re: Structure of the multitasking server
  2008-09-21 19:24       ` Dmitry A. Kazakov
@ 2008-09-21 21:27         ` Maciej Sobczak
  2008-09-22  8:12           ` Dmitry A. Kazakov
  0 siblings, 1 reply; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-21 21:27 UTC (permalink / raw)


On 21 Wrz, 21:24, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:
> On Sun, 21 Sep 2008 10:30:37 -0700 (PDT), Maciej Sobczak wrote:

> > No problem with that. With separate protected object (instead of
> > rendezvous) it is enough to do this:
>
> Of course there is a problem because conditional and timed entry calls may
> not contain a "terminate" alternative.

They don't have to. Again:

      entry Get_Job (J : out Job_Type; Finished : out Boolean);

They have *no choice* but to get the Finished flag. Well, they don't
have to react to it, but that seems to be very easy to spot in the
code review or during the data flow analysis.

> But the way you used protected
> object looks like a design noise. Technically you just replaced a
> rendezvous queue with a protected entry queue.

The fact that this replacement is so obvious is exactly the reason why
rendezvous as a language-level mechanism is nothing but a syntax
sugar.

> Instead of just server and
> workers

There is never "just server and workers". There are also jobs, queues
(even if implicit) and lifetime management (your "poison pills").

> you have server, workers, channels and management stuff.

Because it *is* there anyway.
Trying to hide it? What about the following: in my version the main
task (the one that invents jobs) can give the job to the worker task
when it is still working on the previous one, so that the main task
need not be blocked and can go on inventing more jobs. In other words,
main task and worker tasks need not wait for each other. This is not
obvious from the state transitions that I've shown, but can be
trivially implemented this way, without any massive modification of
the tasks themselves:

   protected type Channel_State is
      procedure Post (J : in Job_Type);
      procedure Finish;
      entry Get_Job (J : out Job_Type; Finished : out Boolean);
      function Can_Accept_New_Job return Boolean;
   private
      Job_Pending : Boolean := False;
      Should_Finish : Boolean := False;
      Job_To_Do : Job_Type;
   end Channel_State;

This code makes it easy to go to real queue with capacity > 1.

This is not so easy in your version with rendezvous, where the main
task has to always *wait* until some of the worker will be willing to
pick up the new job (that's the whole point of rendezvous). Obviously,
there are fewer opportunities for concurrency in your version.
What would you have to do to have this increased concurrency in your
version? Would you still claim it to be simpler?

> The second problem which adds complexity is server to worker 1-n
> communication. Should be n-1 worker to server, simpler (classic
> client-server) and more efficient too.

I'd prefer producer + queue + N consumers. Still a protected object in
the middle.

> Due to complexity you have overlooked to make a channel idle again after a
> job is done.

Yes, I have overlooked it, but now I would not make it this way. I
would make it as describe above, to benefit from the increased
concurrency.

> And note,
> that the type Channel cannot call to the procedure Finish to set Shut_Down
> from its Finalize (if it were controlled).

That's right and this is why I asked here about different design
options.

> > There is no need to introduce any special type of job ("poison pill").
>
> Job carries the parameters of a worker. You used it too, under the name
> Job_Type.

But in my case the Job_Type need not contain the special "poison"
value. Let's say that the Job_Type is a matrix that has to be
reversed. There is no place for the "poison" value in the matrix
itself, so the Job_Type would need to be extended (a record with
additional flag, perhaps?) to cover it, but then the pollution of the
type would be very explicit.

> > Above, the job space is not polluted with task lifetime management
> > concepts - these should be kept separate.
>
> You have polluted it with the procedure Finish, that has to be called
> outside the worker tasks.

I did not pollute the Job_Type (the matrix to be reversed), only the
Channel_State - which is there exactly for everything that is related
to managing the pipeline.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-21 17:37   ` Maciej Sobczak
@ 2008-09-22  2:32     ` anon
  2008-09-22 13:05       ` Maciej Sobczak
  0 siblings, 1 reply; 32+ messages in thread
From: anon @ 2008-09-22  2:32 UTC (permalink / raw)



  Why use "GNAT.Sockets.Check_Selection"?

Well, in TCP/IP system the designers of TCP/IP created a routine called
TCP/IP "Select", that is designed to allow the use of multi-servers or 
multitasking type of servers within one environment. There are reasons 
that goes beyond Ada and other languages that states why a multi server 
design should use the TCP/IP "Select" routine instead of multi tasks where 
each task uses a TCP/IP "Accept" statements to block the server routine 
until a call or a connection is made, basically it has to do with limited 
system resources.  

Note: TCP/IP "Accept" can only monitor one connection port while the 
TCP/IP "Select" allows monitoring up to 32 at one time in one routine.
which makes the TCP/IP "Select" a better utilization of system resources.

Now, in GNAT the TCP/IP "Select" routine is ported to Ada by the function 
"GNAT.Sockets.Thin.C_Select" which is the low-level C and non-portable 
version of the routine. The non-portable TCP/IP "Select" is wrapped into 
a procedure called "GNAT.Sockets.Check_Selection" that is more portable.

Why only 27 servers per TCP/IP "Select" routine  Well, the designers created 
the TCP/IP "Select" to use 3 32-bit word to detect or flag, up to 32 servers 
(bitmapped). Now, the TCP/IP system uses the first 3 file descriptors mapped 
onto the first 3 sockets positions and are defined as the standard system I/O 
(Stdin (0), Stdout (1), Stderr (2)) which is hard coded in the TCP/IP "Select" 
routine.  And the designer of the "GNAT.Sockets" uses two additional random 
assigned sockets or file descriptors (depends on port/version of GNAT) to build 
the Selector type for interfacing with the TCP/IP "Select" routine.  So, 

  27 (User Allowed sockets) :=
           32 (total sockets) - 3 (System defined) - 2 (GNAT RTL used) 


Note: For Full documentation on why 32 sockets you will need to check 
with the history of TCP/IP for the full story, but I would guess it 
was because of limited resources back then. Most servers back then only 
handle a few services, and that has not changed too much in todays world. 
Today, only, the volume of clients has increased.

Plus, some system may have 1024 file descriptors, but the OS normally 
limit the number of assigned/open file descriptors to 32 or less. And 
that includes the 3 Standard I/O files.

--  TCP/IP Server Designed (GNAT)

Now, the best and "Tried and True" design for a multiple service server 
is for a single batch designed monitor task with multi-service server task.  
The monitor task setup and monitor 2 to 27 sockets.  Then activate the 
service server task, afterwards quickly reset and monitors/idles until 
the next request is made. Simple and straight foreword design.

Why Best design: Uses less resources including CPU cycles.  Which allow 
the server tasks and other programs a timely access to their resource needs.

--
-- A simple outline for a multiple services type of TCP/IP server task
--
Server_Controller_Task: 

    Server_Number : Natural ; -- Set to maximum number of server task
                              -- value ranges 5..32 (27) ;

     -- Initialize Controller variables 

    for Server_task_count in 5..Server_Number loop
       idle until called ( Ada "Accept" statement ) ( socket )
       map socket to the Signalling_Fds (read/write/exception)
    end loop


    -- Monitoring Routine: idle until a server is needed.

    loop
      setup or reset Selector variables  -- required for every call
      call Check_Selector function       --  Idle until connected
      Using returned Signalling_Fds, find and call the server task to handle job
    end loop

    exception
        shutdown server in case of TCP/IP system exception. Or
        Storage_Error, Program_Error, etc.

--
-- Each "Server_Service_Task" handles one type of service.
--
Server_Service_Task: 5..MAX(32):

    -- Initialize: Inform controller task that server exist
    --             and is ready to handle request.

    Create Socket 
    Bind Socket to port and address

           --  This call assign and map Socket to Signalling_Fds value
           --  which allows the controller server to monitor the 
           --  connection request and active this server when needed.
    Call controller_task  ( Socket )


    -- idle task until called. Then handle request. 

    loop 
        Set server resources to idle
        idle until called to handle server job. ( Ada "Accept" statement )
        Open Channel
        Handle server service job 
        Close Channel
        exception
          handle lost of connection.  Reset for next call
    end loop
    exception
        handle Socket_Error in case of TCP/IP Socket exception. 

        shutdown server in case of TCP/IP system exception. Or
        Storage_Error, Program_Error, etc.




In <2cfc647a-c9cb-4e0c-9909-7923575fd1ec@d1g2000hsg.googlegroups.com>, Maciej Sobczak <see.my.homepage@gmail.com> writes:
>On 20 Wrz, 01:01, a...@anon.org (anon) wrote:
>
>> Since you are using Channels, I am assuming that your talking about TCP/I=
>P
>> servers.
>
>Not necessarily, although in this case it is indeed true.
>
>> In this case you should look into using the Check_Selector
>
>Why? If I want to keep the number of pipelines relatively small, then
>fixed pool of tasks seems to be quite clean.
>
>> Now the way GNAT has written the Selector function and the way the C_Sele=
>ct
>> is written the maximum number of server per Check_Selector is 27.
>
>Is it documented somewhere?
>The only "documentation" I have found is the .ads file for
>GNAT.Sockets, and this detail is not mentioned there.
>
>Also, on my system the default limit on the number of file descriptors
>used with select(2) is 1024.
>
>>=A0That is,
>> each C_Select function can only monitor 32 sockets
>
>What is C_Select? Why would it be more limited than select(2) (the
>system-level one)?
>
>--
>Maciej Sobczak * www.msobczak.com * www.inspirel.com
>
>Database Access Library for Ada: www.inspirel.com/soci-ada




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

* Re: Structure of the multitasking server
  2008-09-21 21:27         ` Maciej Sobczak
@ 2008-09-22  8:12           ` Dmitry A. Kazakov
  2008-09-22 12:47             ` Maciej Sobczak
  0 siblings, 1 reply; 32+ messages in thread
From: Dmitry A. Kazakov @ 2008-09-22  8:12 UTC (permalink / raw)


On Sun, 21 Sep 2008 14:27:13 -0700 (PDT), Maciej Sobczak wrote:

> On 21 Wrz, 21:24, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
>> On Sun, 21 Sep 2008 10:30:37 -0700 (PDT), Maciej Sobczak wrote:
> 
>>> No problem with that. With separate protected object (instead of
>>> rendezvous) it is enough to do this:
>>
>> Of course there is a problem because conditional and timed entry calls may
>> not contain a "terminate" alternative.
> 
> They don't have to. Again:
> 
>       entry Get_Job (J : out Job_Type; Finished : out Boolean);

Which is a bad pattern because it propagates task termination requests to
an entry point of some object (or another task) which usually have little
or nothing to do with the task termination issue.

I don't like an extra output parameter, it is error-prone. Therefore I used
a special value of Job_Type. There also exists a third variant with an
exception propagation. (Note that an exception from an rendezvous
propagates on both sides)

This all is a matter of taste, because the (poor) pattern is same: the
knowledge about caller's termination is moved to the callee.

>> you have server, workers, channels and management stuff.
> 
> Because it *is* there anyway.
> Trying to hide it? What about the following: in my version the main
> task (the one that invents jobs) can give the job to the worker task
> when it is still working on the previous one, so that the main task
> need not be blocked and can go on inventing more jobs.

This is a very different task. You didn't say that you wanted to queue
jobs. Anyway there is little sense to queue jobs to busy workers. A better
design would be:

   Server --> Scheduler <-- Worker

The jobs are queued to the scheduler. Workers ask it for jobs to do.
Scheduler could be a protected object or a task. Depending on the nature of
the jobs source, Server and Scheduler can be merged (see below).

> This is not so easy in your version with rendezvous, where the main
> task has to always *wait* until some of the worker will be willing to
> pick up the new job (that's the whole point of rendezvous).

You *have* to wait, or else you must drop jobs.

Queueing jobs postpones the problem but that by no means solves it. If
workers are incapable to process the jobs, the server must wait for them.
The only reason why job queueing might become necessary is a need to make
the server asynchronously working. (Remember our recent discussion?
Buffering = wasting resources, unless decouples.)

Anyway it is no problem in my design. Use selective accept (RM 9.7.1):

   select
      when not Empty (Queue)  => accept Get (Work : out Job) do
         ... -- A worker is here to get a job from the FIFO
      end Get;
   or when not Full (Queue) => accept Put (Work : out Job) do
         ... -- A new job is to queue into the FIFO
      end Put;
   or when Full (Queue) => delay Time_Out;
      ... -- Go to hell with your jobs!
   end select;

> Obviously,
> there are fewer opportunities for concurrency in your version.
> What would you have to do to have this increased concurrency in your
> version? Would you still claim it to be simpler?

Certainly yes. If I wished to collect jobs at the server I would just add a
FIFO of jobs there, like above.

>> The second problem which adds complexity is server to worker 1-n
>> communication. Should be n-1 worker to server, simpler (classic
>> client-server) and more efficient too.
> 
> I'd prefer producer + queue + N consumers. Still a protected object in
> the middle.

What you describe is my design (the queue is held by the producer).Your
design was:

   producer + N queues of size 1 + N consumers

It is not a problem to me to split the producer into server and scheduler.
I object mainly to moving *parts* of the scheduler (as protected objects)
into the consumers. It makes no sense.

>>> There is no need to introduce any special type of job ("poison pill").
>>
>> Job carries the parameters of a worker. You used it too, under the name
>> Job_Type.
> 
> But in my case the Job_Type need not contain the special "poison"
> value.

See above. You can use either of three variants with my design. This is an
independent issue.

> Let's say that the Job_Type is a matrix that has to be
> reversed. There is no place for the "poison" value in the matrix
> itself, so the Job_Type would need to be extended (a record with
> additional flag, perhaps?) to cover it, but then the pollution of the
> type would be very explicit.

If it were large objects, I would not use copying anyway. So it would
become a referential object moving around. These would have enough place
for "poison". Further, don't forget that Ada has variant records.

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



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

* Re: Structure of the multitasking server
  2008-09-21 17:23   ` Maciej Sobczak
@ 2008-09-22  8:23     ` Jean-Pierre Rosen
  0 siblings, 0 replies; 32+ messages in thread
From: Jean-Pierre Rosen @ 2008-09-22  8:23 UTC (permalink / raw)


Maciej Sobczak a �crit :
> On 19 Wrz, 15:34, Jean-Pierre Rosen <ro...@adalog.fr> wrote:
> 
>> Why not simply use a rendezvous?
> 
> This is something that I deliberately wanted to avoid.
> I don't like the idea of tasks interacting with each other directly
> and I prefer to have the communication part extracted away (Ravenscar
> got that right for a reason, I think?).
You said "I prefer", so this can be a matter of taste ;-)

Personnally, I view protected types as a great tool for simple, 
low-level communications, and rendezvous for higher level 
communications. I think rendezvous are closer to real-life, and thus 
easier to manage. YMMV of course.

The issue for Ravenscar is different: it is a matter of provability.
There are many examples of things that are simpler for the human being, 
but less easy for proofs systems...

-- 
---------------------------------------------------------
            J-P. Rosen (rosen@adalog.fr)
Visit Adalog's web site at http://www.adalog.fr



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

* Re: Structure of the multitasking server
  2008-09-22  8:12           ` Dmitry A. Kazakov
@ 2008-09-22 12:47             ` Maciej Sobczak
  2008-09-22 14:11               ` Dmitry A. Kazakov
  0 siblings, 1 reply; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-22 12:47 UTC (permalink / raw)


On 22 Wrz, 10:12, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> >       entry Get_Job (J : out Job_Type; Finished : out Boolean);
>
> Which is a bad pattern because it propagates task termination requests to
> an entry point of some object (or another task) which usually have little
> or nothing to do with the task termination issue.

How is the special job any different in this aspect? It has exactly
the same properties, just implicitly.

> I don't like an extra output parameter, it is error-prone. Therefore I used
> a special value of Job_Type.

In what way is a special value less error-prone than a separate flag?

> There also exists a third variant with an
> exception propagation.

This assumes rendezvous.

> This all is a matter of taste, because the (poor) pattern is same: the
> knowledge about caller's termination is moved to the callee.

In the version with protected objects the entity that triggers
termination might be different from the one that invents job.
In your version these different responsibilities are mixed.

> > What about the following: in my version the main
> > task (the one that invents jobs) can give the job to the worker task
> > when it is still working on the previous one, so that the main task
> > need not be blocked and can go on inventing more jobs.
>
> This is a very different task. You didn't say that you wanted to queue
> jobs.

And this is exactly the advantage of separate protected object - I
didn't have to state all aspects and I was free to invent the part
with queues later on. This flexibility is not provided by rendezvous,
which would then require massive rework.

> Anyway there is little sense to queue jobs to busy workers.

There is a lot of sense. In particular, in my version the task that
invents jobs is never blocked on interactions with workers. This is a
big advantage, because such a blocking would disturb its own
interactions with the environment and reduce its responsiveness.
In your version with rendezvous the task that invents jobs has to
block and during that time it cannot react to the events that drive it
(keyboard input, network transmission, etc.).

> A better
> design would be:
>
>    Server --> Scheduler <-- Worker
>
> The jobs are queued to the scheduler. Workers ask it for jobs to do.
> Scheduler could be a protected object or a task.

Isn't it what I have proposed?

> You *have* to wait, or else you must drop jobs.

Which might be a perfectly legal handling policy.
Or the main task could promptly reject requests so that clients can
back off or try to find another, less busy server.

> Anyway it is no problem in my design. Use selective accept (RM 9.7.1):
>
>    select
>       when not Empty (Queue)  => accept Get (Work : out Job) do
>          ... -- A worker is here to get a job from the FIFO
>       end Get;
>    or when not Full (Queue) => accept Put (Work : out Job) do
>          ... -- A new job is to queue into the FIFO
>       end Put;
>    or when Full (Queue) => delay Time_Out;
>       ... -- Go to hell with your jobs!
>    end select;

But this looks like part of a separate task and the third branch still
causes someone else to block.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-22  2:32     ` anon
@ 2008-09-22 13:05       ` Maciej Sobczak
  2008-09-23  9:25         ` anon
  0 siblings, 1 reply; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-22 13:05 UTC (permalink / raw)


On 22 Wrz, 04:32, a...@anon.org (anon) wrote:

>   Why use "GNAT.Sockets.Check_Selection"?

> [...] basically it has to do with limited
> system resources.

Of course, but as I've said, the intended (fixed) number of pipelines
is relatively small and the system resources do not seem to be any
limitation.

> Note: TCP/IP "Accept" can only monitor one connection port while the
> TCP/IP "Select" allows monitoring up to 32 at one time in one routine.
> which makes the TCP/IP "Select" a better utilization of system resources.

I don't understand this assertion. You cannot *replace* accept with
select, they serve completely different purposes, so you cannot state
that one is better than another.

> Why only 27 servers per TCP/IP "Select" routine  Well, the designers created
> the TCP/IP "Select" to use 3 32-bit word to detect or flag

This is an implementation detail that does not seem to be true any
longer. As I've already explained, on my system the default limit for
select(2) is 1024 descriptors and with a simple compiler option can be
actually unlimited.

The limit of 32 seems to be very impractical today. It is not uncommon
to have hundreds of clients connected at the same time to a single
server. They can be all handled without any partitioning.

> Plus, some system may have 1024 file descriptors, but the OS normally
> limit the number of assigned/open file descriptors to 32 or less.

Again, this is not true on any of the systems that I have access to.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-22 12:47             ` Maciej Sobczak
@ 2008-09-22 14:11               ` Dmitry A. Kazakov
  2008-09-23  8:07                 ` Maciej Sobczak
  0 siblings, 1 reply; 32+ messages in thread
From: Dmitry A. Kazakov @ 2008-09-22 14:11 UTC (permalink / raw)


On Mon, 22 Sep 2008 05:47:13 -0700 (PDT), Maciej Sobczak wrote:

> On 22 Wrz, 10:12, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
> 
>>> � � � entry Get_Job (J : out Job_Type; Finished : out Boolean);
>>
>> Which is a bad pattern because it propagates task termination requests to
>> an entry point of some object (or another task) which usually have little
>> or nothing to do with the task termination issue.
> 
> How is the special job any different in this aspect? It has exactly
> the same properties, just implicitly.

It is not, as I said the pattern is same.

>> I don't like an extra output parameter, it is error-prone. Therefore I used
>> a special value of Job_Type.
> 
> In what way is a special value less error-prone than a separate flag?

Not a flag, it is a special value. I have used a discriminant, which would
then control a variant record.

>> There also exists a third variant with an
>> exception propagation.
> 
> This assumes rendezvous.

Nope. An exception can be propagated from the entry body or rendezvous.
Here is my example with exceptions (a complete program):

with Ada.Text_IO;  use Ada.Text_IO;

procedure Test_Server is
   Time_To_Die : exception;
   type Job is null record;
   task type Worker;
   task Server is
      entry Get (Work : out Job);
   end Server;

   task body Server is
      Request : Job;
      Workers : array (1..5) of Worker;
   begin
      for Times in 1..7 loop
         -- Get a job to do

         -- Wait for a worker to come
         accept Get (Work : out Job) do
            Work := Request;
         end Get;
      end loop;
      -- Terminating workers
      for Index in Workers'Range loop
         begin
            accept Get (Work : out Job) do
               raise Time_To_Die;
            end Get;
         exception
            when Time_To_Die => null;
         end;
      end loop;
   end Server;

   task body Worker is
      Work : Job;
   begin
      loop
         Server.Get (Work);
         Put_Line ("Doing things");
      end loop;
   exception
      when Time_To_Die => Put_Line ("I am done");
   end Worker;
begin
   null;
end Test_Server;

>> This all is a matter of taste, because the (poor) pattern is same: the
>> knowledge about caller's termination is moved to the callee.
> 
> In the version with protected objects the entity that triggers
> termination might be different from the one that invents job.
> In your version these different responsibilities are mixed.

It does not work. You cannot selectively call to multiple entries of a
protected object anyway. Then again, termination should be triggered by
task finalization, this the only "right way." All others are more or less
nasty workarounds. This is the point. So long it is not either a
"terminate" alternative or else a "free" decision of the task to exit, all
designs are equally bad.

> There is a lot of sense. In particular, in my version the task that
> invents jobs is never blocked on interactions with workers. This is a
> big advantage, because such a blocking would disturb its own
> interactions with the environment and reduce its responsiveness.

This is a design fault. Under certain conditions it must either block or
fail.

> In your version with rendezvous the task that invents jobs has to
> block and during that time it cannot react to the events that drive it
> (keyboard input, network transmission, etc.).

Wrong, see 9.7.1(16).

>> A better design would be:
>>
>> � �Server --> Scheduler <-- Worker
>>
>> The jobs are queued to the scheduler. Workers ask it for jobs to do.
>> Scheduler could be a protected object or a task.
> 
> Isn't it what I have proposed?

No. Decisive is who knows whom. Your design is when a multicasting
publisher "knows" its peers. This is a bad idea, obviously.

> Or the main task could promptly reject requests so that clients can
> back off or try to find another, less busy server.

>> Anyway it is no problem in my design. Use selective accept (RM 9.7.1):
>>
>> � �select
>> � � � when not Empty (Queue) �=> accept Get (Work : out Job) do
>> � � � � �... -- A worker is here to get a job from the FIFO
>> � � � end Get;
>> � �or when not Full (Queue) => accept Put (Work : out Job) do
>> � � � � �... -- A new job is to queue into the FIFO
>> � � � end Put;
>> � �or when Full (Queue) => delay Time_Out;
>> � � � ... -- Go to hell with your jobs!
>> � �end select;
> 
> But this looks like part of a separate task and the third branch still
> causes someone else to block.

No problem with that. There is "else part" of selective accept for this
9.7.1(19):
 
 � select
 �  � when not Empty (Queue) �=> accept Get ...
 � or when not Full (Queue) => accept Put ...
 � else
      ... -- Go to hell at once!
� �end select;

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



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

* Re: Structure of the multitasking server
  2008-09-22 14:11               ` Dmitry A. Kazakov
@ 2008-09-23  8:07                 ` Maciej Sobczak
  2008-09-23  9:37                   ` Dmitry A. Kazakov
  2008-09-23 10:47                   ` Jean-Pierre Rosen
  0 siblings, 2 replies; 32+ messages in thread
From: Maciej Sobczak @ 2008-09-23  8:07 UTC (permalink / raw)


On 22 Wrz, 16:11, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> >> There also exists a third variant with an
> >> exception propagation.
>
> > This assumes rendezvous.
>
> Nope.

> Here is my example with exceptions (a complete program):
[...]

This example uses rendezvous.
I don't like it, because it misuses exceptions for something that is a
perfectly normal control flow.
Yes, this is very subjective - for the record, I also don't like the
End_Error exception.

BTW - the fact that you raise an exception only to shut it up in the
same scope indicates that this is a severe misuse of some language
feature.

> > In the version with protected objects the entity that triggers
> > termination might be different from the one that invents job.
> > In your version these different responsibilities are mixed.
>
> It does not work. You cannot selectively call to multiple entries of a
> protected object anyway.

I don't have to. What I said is that the entities that provide jobs
and that trigger termination can be different, not that the worker can
get them from separate channels.

> Then again, termination should be triggered by
> task finalization

Why? Which task?
The termination of workers can be triggered by the load that is below
some threshold. This is a valid strategy for dynamic task pool and is
not necessarily related to the finalization of the main task.

> > In particular, in my version the task that
> > invents jobs is never blocked on interactions with workers. This is a
> > big advantage, because such a blocking would disturb its own
> > interactions with the environment and reduce its responsiveness.
>
> This is a design fault. Under certain conditions it must either block or
> fail.

Where is the design fault? The immediate failure (as opposed to
blocking) can be a valid overload handling policy.
If I go to some office and cannot be handled because of the clerks
being too busy, I prefer to know it *immediately*, so that I can go to
the pub.

> > In your version with rendezvous the task that invents jobs has to
> > block and during that time it cannot react to the events that drive it
> > (keyboard input, network transmission, etc.).
>
> Wrong, see 9.7.1(16).

Again: keyboard input, network transmission, etc. Select statement is
not flexible enough.
In addition, there is no need to create even more tasks only to fit to
select's limitations.

> Your design is when a multicasting
> publisher "knows" its peers. This is a bad idea, obviously.

Why it is a bad idea? What is bad in the manager having knowledge
about workers in his team?
Is your boss aware of you or do you need to constantly ask him for new
tasks?

BTW - there is no multicasting in my design.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



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

* Re: Structure of the multitasking server
  2008-09-22 13:05       ` Maciej Sobczak
@ 2008-09-23  9:25         ` anon
  0 siblings, 0 replies; 32+ messages in thread
From: anon @ 2008-09-23  9:25 UTC (permalink / raw)


--
--  A Server and a Client that shows how to use 
--  GNAT.Sockets.Check_Selection as a controller for
--  multiple port server. And can be alter to allow multiple 
--  services type of server.
--
--  In C,  they call this type of server a Super-Server Class 
--  of servers.
--

-- ------------------------------------------------------------ --
-- Multi-Tasking Server that allows Multi-Service to be handled --
-- ------------------------------------------------------------ --

with Ada.Text_IO ;
with GNAT.Sockets ;
with System ;

use  Ada.Text_IO ;
use  GNAT.Sockets ;

procedure testserver is 


  -- ------------------- --
  --  Server Task Types  --
  -- ------------------- --

    -- 
    --  TCP Task
    -- 

    task tcp_hello is
        entry Initialize ;
        entry Acknowledge ;
      end tcp_hello ;


    -- 
    --  UDP Task
    -- 

    task udp_hello is
        entry Initialize ;
        entry Acknowledge ;
      end udp_hello ;


    Server_Error  : exception ;

    tcp_Socket    : Socket_Type       ;
    udp_Socket    : Socket_Type       ;


-- ------------------------------------------------------------------------ --
----                        TCP/IP Controller Task                        ----
----                                                                      ----
----  dispatches server task to handle tcp/ip services.                   ----
----                                                                      ----
-- ------------------------------------------------------------------------ --

    task Controller is
        entry Initialize_Controller ;
      end Controller  ;


    task body Controller is

        Selector      : Selector_Type   ;
        Server_Status : Selector_Status ;

        --  Exception_Fds is not use in Ada 95
        --  and is optional Ada 2005 GNAT.Sockets

        Read_Fds      : Socket_Set_Type ;
        Write_Fds     : Socket_Set_Type ;


      begin -- Controller 
          --
          -- Set up controller variables
          --
          Create_Selector ( Selector ) ;
          Empty ( Read_Fds ) ;
          Empty ( Write_Fds ) ;


          Accept Initialize_Controller ;

          --
          -- Set up and active Server
          --
          Put_Line ( "Controller: Server Dispatching Loop" ) ;
          loop
            -- Insure Fds value, 
            Empty ( Read_Fds ) ;
            Empty ( Write_Fds ) ;

            Set ( Read_Fds, tcp_Socket ) ;
            Set ( Read_Fds, udp_Socket ) ;

            --
            --  Excute the TCP/IP "Select" routine.  This routine is blocked
            --  until a connecion is made. In this part it is identical to 
            --  TCP/IP "Accept".
            --
            --  Also, Exception_Fds is not use in Ada 95 and is optional 
            --  Ada 2005 GNAT.Sockets
            --
            Check_Selector ( Selector, 
                             Read_Fds, 
                             Write_Fds, 
                             Server_Status ) ;

            -- call a server to handle job using Signalling Fds

            if Is_Set ( Read_Fds, tcp_Socket ) then
                tcp_hello.Acknowledge ;
            elsif Is_Set ( Read_Fds, udp_Socket ) then
                udp_hello.Acknowledge ;
            else
              raise Socket_Error ;
            end if ;
          end loop ;
       exception
         when Socket_Error => 
             -- need to signal servers to shutdown because 
             -- dispatcher has stopped
             raise ;            
      end Controller  ;




-- ------------------------------------------------------------------------ --
----                         TCP/IP Server Tasks                          ----
----                                                                      ----
----  Both tasks are similar.  They both send a message to a client       ----
----  Differences:                                                        ----
----        1) Transport protocols: One uses TCP, the other uses UDP      ----
----        2) Message and message length                                 ----
----                                                                      ----
----  Excution of these two servers will cause the "netstat" program      ----
----  to add the following info to the "netstat" socket report            ----
----                                                                      ----
----      IP Address        protocol    port                              ----
----      127.0.0.1         tcp         54321                             ----
----      127.0.0.1         udp         54321                             ----
----                                                                      ----
-- ------------------------------------------------------------------------ --



    task body tcp_hello is

        Port         : constant Port_Type := 54321 ;

        Address      : Sock_Addr_Type    ;
        Channel      : Stream_Access     ;
        Client       : Socket_Type       ; 

        hello_string : String ( 1..25 ) := "TCP Server: Hello, World!" ; 

      begin -- Tcp_hello

        Create_Socket ( tcp_Socket, Family_Inet, Socket_Stream ) ;
        --
--        Address.Addr := Any_Inet_Addr ;
        Address.Addr := Inet_Addr ( "127.0.0.1" ) ; -- Limited access
        Address.Port := Port ;
        --
        Bind_Socket ( tcp_Socket, Address ) ;

        --
        Listen_Socket ( tcp_Socket, 4 ) ;  

        Accept Initialize ;
        --
        --  Main Server processing routine 
        --
        Put_Line ( "TCP: Active the Server loop" ) ;
        loop 
          accept Acknowledge ;

          -- ------------------------ --
          --  Do server services job  --
          -- ------------------------ --

          --
          --  Because of Check_Selector, no wait for Accept_Socket
          --  used to obtain connected socket ( Client ) address 
          --  for the tcp protocol.
          --
          Accept_Socket ( tcp_Socket, Client, Address ) ;

          Channel := Stream ( Client ) ;

          String'Write ( Channel, hello_string ) ;

          Close_Socket ( Client ) ;

        end loop ;

        --
        -- for security, close socket if exception occured
        --
        exception
          when others =>
              Put_Line ( "TCP: Exception" ) ;
              Close_Socket ( tcp_Socket ) ;
              raise ;
      end tcp_hello ;



    -- 
    --  UDP Task
    -- 

    task body udp_hello is

        Port           : constant Port_Type := 54321 ;

        Address        : Sock_Addr_Type    ;
        Channel_Input  : Stream_Access     ;
        Channel_Output : Stream_Access     ;

        Temp           : Character         ;
        hello_string : String := "UDP Server: Repeat Hello, World!" ; 

      begin -- udp_hello

        Create_Socket ( udp_Socket, Family_Inet, Socket_Datagram ) ;
        --
--        Address.Addr := Any_Inet_Addr ;
        Address.Addr := Inet_Addr ( "127.0.0.1" ) ;
        Address.Port := Port ;
        --
        Bind_Socket ( udp_Socket, Address ) ;


        Accept Initialize ;
        --
        --  Main Server processing routine 
        --
        Put_Line ( "UDP: Active the Server loop" ) ;
        loop 
          accept Acknowledge ;

          -- ------------------------ --
          --  Do server services job  --
          -- ------------------------ --
                                           -- Accept from any Address and Port
          Address.Addr := Any_Inet_Addr ;
          Address.Port := Any_Port ;

          Channel_Input := Stream ( udp_Socket, Address ) ;  
      
          Character'Read ( Channel_Input, Temp ) ;

                                           -- Open an output channel
          Address := Get_Address ( Channel_Input ) ;
          Channel_Output := Stream ( udp_Socket, Address ) ;

          String'Write ( Channel_Output, hello_string ) ;
        end loop ;

        --
        -- for security, close socket if exception occured
        --
        exception
          when others =>
              Put_Line ( "UDP: Exception" ) ;
              Close_Socket ( udp_Socket ) ;
              raise ;
      end udp_hello ;



                      -- ------------------------ --
                      ----   Server Initiaizer  ----
                      -- ------------------------ --



  begin

    -- Initialize each server service task

    tcp_hello.Initialize ;
    udp_hello.Initialize ;

    --  Startup network controller

    Controller.Initialize_Controller ;

  --
  -- Handle all exceptions
  --
  exception 
    when Socket_Error =>
      raise ;
    when others =>
      raise ;
  end testserver ;





-- ----------------------------------------------------- --
-- Non-Tasking Client that test the Multi-Service Server --
-- ----------------------------------------------------- --

with Ada.Command_Line ;
with Ada.Text_IO ;
with Ada.Unchecked_Conversion ;
with GNAT.Sockets ;

use  Ada.Command_Line ;
use  Ada.Text_IO ;
use  GNAT.Sockets ;

procedure testclient is 

    localhost : constant Inet_Addr_Type := Inet_Addr ( "127.0.0.1" ) ;
    localport : constant Port_Type := 54321 ;
    --
    Address : Sock_Addr_Type    ;
    Channel : Stream_Access     ;
    Socket  : Socket_Type       ;
    --
    tcp_Buffer  : String ( 1..25 )  ;
    udp_Buffer  : String ( 1..32 )  ;

  begin -- Daytime0

    --
    Address.Addr := localhost ;
    Address.Port := localport ;

    --
    if Argument ( 1 ) = "-T" then
      Put_Line ( "Protocol: TCP" ) ;
      --
      Create_Socket ( Socket, Family_Inet, Socket_Stream ) ;
      Connect_Socket ( Socket, Address ) ;
      --
      Channel := Stream ( Socket ) ;
      String'Read ( Channel, tcp_Buffer ) ;
      --
      Close_Socket ( Socket ) ;
      --
      Put ( "Server Data => " ) ;
      Put ( tcp_Buffer ) ;
      Put ( " <= " ) ;
      New_Line ;

    elsif Argument ( 1 ) = "-U" then
      Put_Line ( "Protocol: UDP" ) ;
      --
      Create_Socket ( Socket, Family_Inet, Socket_Datagram ) ;
      Channel := Stream ( Socket, Address ) ;

      -- Allows server to obtain client address by send a dummy character 

      Character'Write ( Channel, Ascii.nul ) ; 
      --
      Channel := Stream ( Socket, Address ) ;
      String'Read ( Channel, udp_Buffer ) ;
      --
      Close_Socket ( Socket ) ;
      --
      Put ( "Server Data => " ) ;
      Put ( udp_Buffer ) ;
      Put ( " <= " ) ;
      New_Line ;

     else
      Put_Line ( Standard_Error, "usage: testclient [-DT]" ) ;
      Put_Line ( Standard_Error, "-T: tcp" ) ;
      Put_Line ( Standard_Error, "-U: udp" ) ;
      New_Line ;
    end if ;
  end testclient ;



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

* Re: Structure of the multitasking server
  2008-09-23  8:07                 ` Maciej Sobczak
@ 2008-09-23  9:37                   ` Dmitry A. Kazakov
  2008-09-23 10:47                   ` Jean-Pierre Rosen
  1 sibling, 0 replies; 32+ messages in thread
From: Dmitry A. Kazakov @ 2008-09-23  9:37 UTC (permalink / raw)


On Tue, 23 Sep 2008 01:07:38 -0700 (PDT), Maciej Sobczak wrote:

> I don't like it, because it misuses exceptions for something that is a
> perfectly normal control flow.

I don't know how a control flow can be abnormal. If "normality" is
attributed to the meaning of program's artefacts, then that is at the
programmer's discretion. Is it normal not to have a job? For a worker that
might look abnormal, for a welfare receiver it maybe not...

> Yes, this is very subjective - for the record, I also don't like the
> End_Error exception.

But you certainly enjoy Constraint_Error... It is not subjective, it is
unrealistic.
 
> BTW - the fact that you raise an exception only to shut it up in the
> same scope indicates that this is a severe misuse of some language
> feature.

I use this language feature in order to express "not a value" in a
type-safe way. The same feature is used when an integer overflows.

You didn't want "no job" become a job, so I used exceptions instead. (A
return flag with a rubbish value is not an Ada way.)

>>> In the version with protected objects the entity that triggers
>>> termination might be different from the one that invents job.
>>> In your version these different responsibilities are mixed.
>>
>> It does not work. You cannot selectively call to multiple entries of a
>> protected object anyway.
> 
> I don't have to. What I said is that the entities that provide jobs
> and that trigger termination can be different, not that the worker can
> get them from separate channels.

If entries are different then the worker cannot selectively call to them.
If they are never called by it, then how is this related to the worker? In
your code there was only one entry called by the worker.

>> Then again, termination should be triggered by
>> task finalization
> 
> Why? Which task?

The worker task.

>>> In particular, in my version the task that
>>> invents jobs is never blocked on interactions with workers. This is a
>>> big advantage, because such a blocking would disturb its own
>>> interactions with the environment and reduce its responsiveness.
>>
>> This is a design fault. Under certain conditions it must either block or
>> fail.
> 
> Where is the design fault? The immediate failure (as opposed to
> blocking) can be a valid overload handling policy.

First you said that it neither blocks nor fail. Now you consider it to fail
immediately. Fine, let's take this. Why then to queue jobs, which are known
to fail later? (It is difficult to pursue a moving target...)

>>> In your version with rendezvous the task that invents jobs has to
>>> block and during that time it cannot react to the events that drive it
>>> (keyboard input, network transmission, etc.).
>>
>> Wrong, see 9.7.1(16).
> 
> Again: keyboard input, network transmission, etc. Select statement is
> not flexible enough.

1. The selective accept statement is flexible enough. And in any case it is
far more flexible than entry call statements. This is one of the reasons
why rendezvous might be better in one to many relationships.

The problem with the things you mentioned is that their interfaces are not
conform to Ada tasking. There are many reasons for this, unrelated to the
discussion.

2. Not all interfaces are like those.

3. In this particular case the server can block on job reception, because
this is the only event to react to.

>> Your design is when a multicasting
>> publisher "knows" its peers. This is a bad idea, obviously.
> 
> Why it is a bad idea? What is bad in the manager having knowledge
> about workers in his team?

Yes, obviously. This requires a team and maintenance of. Such managers are
expensive and exposed to various risks. It might appear the only way of
design when you deal with humans, but fortunately, with bits and bytes
there are much better options.

> Is your boss aware of you or do you need to constantly ask him for new
> tasks?

This is a model widely used when you don't want to employ staff
permanently. If you have plenty of free workers, you just sit and wait them
to come. Fee is paid per work made. See freelances, etc.

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



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

* Re: Structure of the multitasking server
  2008-09-23  8:07                 ` Maciej Sobczak
  2008-09-23  9:37                   ` Dmitry A. Kazakov
@ 2008-09-23 10:47                   ` Jean-Pierre Rosen
  1 sibling, 0 replies; 32+ messages in thread
From: Jean-Pierre Rosen @ 2008-09-23 10:47 UTC (permalink / raw)


Maciej Sobczak a �crit :
> This example uses rendezvous.
> I don't like it, because it misuses exceptions for something that is a
> perfectly normal control flow.
> Yes, this is very subjective - for the record, I also don't like the
> End_Error exception.
> 
> BTW - the fact that you raise an exception only to shut it up in the
> same scope indicates that this is a severe misuse of some language
> feature.
> 
This is a very restrictive (although widely shared) view of exceptions, 
where you consider that they are only for signaling failures.

Exceptions are a programming tool for handling situations that are 
"exceptions" to the regular processing. This is the case for errors of 
course, but end of processing is perfectly appropriate.

Moreover, using exceptions in this case has the benefit that the worker 
cannot ignore the signal (as would be the case with an out parameter). 
If the exception is not handled, the worker will crash, but not block 
the system.

-- 
---------------------------------------------------------
            J-P. Rosen (rosen@adalog.fr)
Visit Adalog's web site at http://www.adalog.fr



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

* Re: Structure of the multitasking server
  2008-09-19 13:34 ` Jean-Pierre Rosen
  2008-09-19 17:02   ` Dmitry A. Kazakov
  2008-09-21 17:23   ` Maciej Sobczak
@ 2015-03-12 16:07   ` gautier_niouzes
  2015-03-12 21:38     ` Jacob Sparre Andersen
  2 siblings, 1 reply; 32+ messages in thread
From: gautier_niouzes @ 2015-03-12 16:07 UTC (permalink / raw)


Thanks Jean-Pierre Rosen for this elegant solution!
For a very simple simulation framework I need, here is a even simplified variant that is working perfectly. In this case the parameters passed to the task entries are just a job number and the worker's number, for information.

  procedure Process_parallel is
    workers: array(1..8) of Worker_type;
  begin
    for d in is_driver'Range loop  
      Looking_for_idle:
      loop
        for w in workers'range loop
          select
            workers(w).Run(d, w);
            exit Looking_for_idle;
          else
            null; -- Worker is busy
          end select;
        end loop;
      end loop Looking_for_idle;
    end loop;
  end Process_parallel;
_________________________ 
Gautier's Ada programming 
http://gautiersblog.blogspot.com/search/label/Ada 
NB: follow the above link for a valid e-mail address 


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

* Re: Structure of the multitasking server
  2015-03-12 16:07   ` gautier_niouzes
@ 2015-03-12 21:38     ` Jacob Sparre Andersen
  2015-03-12 22:39       ` gautier_niouzes
  0 siblings, 1 reply; 32+ messages in thread
From: Jacob Sparre Andersen @ 2015-03-12 21:38 UTC (permalink / raw)


gautier_niouzes@hotmail.com writes:

>       Looking_for_idle:
>       loop
>         for w in workers'range loop
>           select
>             workers(w).Run(d, w);
>             exit Looking_for_idle;
>           else
>             null; -- Worker is busy
>           end select;
>         end loop;
>       end loop Looking_for_idle;

This looks like a busy-wait to me.  Is it?  If not, why not?

Greetings,

Jacob
-- 
"Nobody writes jokes in base 13."
 Douglas Adams

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

* Re: Structure of the multitasking server
  2015-03-12 21:38     ` Jacob Sparre Andersen
@ 2015-03-12 22:39       ` gautier_niouzes
  2015-03-13  8:15         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 32+ messages in thread
From: gautier_niouzes @ 2015-03-12 22:39 UTC (permalink / raw)


Le jeudi 12 mars 2015 22:38:50 UTC+1, Jacob Sparre Andersen a écrit :

> This looks like a busy-wait to me.  Is it?  If not, why not?

Seems so. Is it bad? Well it may eat one core or CPU's performance.
Is it better with a small delay (see below) to cool down the boss ?
Anyway the system is working fine.
BTW, I had forgotten a Stop entry: when all the work was done, the workers were still waiting for more :-).

  procedure Process_parallel is
    workers: array(1..8) of Worker_type;
  begin
    All_drivers_numbers:
    for d in is_driver'Range loop  
      Looking_for_idle:
      loop
        for w in workers'range loop
          select
            workers(w).Run(d, w);
            exit Looking_for_idle;
          else
            null; -- workers(w) is busy
          end select;
        end loop;
        delay 0.01;
      end loop Looking_for_idle;
    end loop All_drivers_numbers;
    for w in workers'range loop
      workers(w).Stop(w);
    end loop;    
  end Process_parallel;
_________________________ 
Gautier's Ada programming 
http://www.openhub.net/accounts/gautier_bd

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

* Re: Structure of the multitasking server
  2015-03-12 22:39       ` gautier_niouzes
@ 2015-03-13  8:15         ` Dmitry A. Kazakov
  2015-03-13 20:16           ` gautier_niouzes
  0 siblings, 1 reply; 32+ messages in thread
From: Dmitry A. Kazakov @ 2015-03-13  8:15 UTC (permalink / raw)


On Thu, 12 Mar 2015 15:39:02 -0700 (PDT), gautier_niouzes@hotmail.com
wrote:

> Le jeudi 12 mars 2015 22:38:50 UTC+1, Jacob Sparre Andersen a écrit :
> 
>> This looks like a busy-wait to me.  Is it?  If not, why not?
> 
> Seems so. Is it bad?

Yes.

> Well it may eat one core or CPU's performance.
> Is it better with a small delay (see below) to cool down the boss ?

You could add a wakeup event to prevent busy waiting. An alternative would
be a waitable jobs queue.

BTW, you seem have another problem. The drivers/workers are serviced always
in the same order which would systematically choke ones near to the list
end. A usual solution of this problem is list walking entered at the point
where it was left.

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

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

* Re: Structure of the multitasking server
  2015-03-13  8:15         ` Dmitry A. Kazakov
@ 2015-03-13 20:16           ` gautier_niouzes
  2015-03-13 20:47             ` Dmitry A. Kazakov
  2015-03-13 23:04             ` Randy Brukardt
  0 siblings, 2 replies; 32+ messages in thread
From: gautier_niouzes @ 2015-03-13 20:16 UTC (permalink / raw)


Le vendredi 13 mars 2015 09:14:52 UTC+1, Dmitry A. Kazakov a écrit :

> You could add a wakeup event to prevent busy waiting. An alternative would
> be a waitable jobs queue.

Wouldn't a delay instruction help ?

> BTW, you seem have another problem. The drivers/workers are serviced always
> in the same order which would systematically choke ones near to the list
> end. A usual solution of this problem is list walking entered at the point
> where it was left.

Do you mean worker #1 would tend to be overworked compared to the others ?
In the program I run - perhaps because the jobs are long: a few seconds number crunching - it doesn't seem to be the case:

Worker 1	362	jobs
Worker 2	540	jobs
Worker 3	652	jobs
Worker 4	359	jobs
Worker 5	366	jobs
Worker 6	592	jobs
Worker 7	371	jobs
Worker 8	370	jobs

G.


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

* Re: Structure of the multitasking server
  2015-03-13 20:16           ` gautier_niouzes
@ 2015-03-13 20:47             ` Dmitry A. Kazakov
  2015-03-15  7:43               ` gautier_niouzes
  2015-03-13 23:04             ` Randy Brukardt
  1 sibling, 1 reply; 32+ messages in thread
From: Dmitry A. Kazakov @ 2015-03-13 20:47 UTC (permalink / raw)


On Fri, 13 Mar 2015 13:16:29 -0700 (PDT), gautier_niouzes@hotmail.com
wrote:

> Le vendredi 13 mars 2015 09:14:52 UTC+1, Dmitry A. Kazakov a écrit :
> 
>> You could add a wakeup event to prevent busy waiting. An alternative would
>> be a waitable jobs queue.
> 
> Wouldn't a delay instruction help ?

It helps switching to another task before the scheduler preemts the task
Process, which would normally happen after a timer interrupt. If Process is
through *before* the time quant is expired, delay 0.0 cause Process giving
up the core.

[I don't understand what are you going to do]

>> BTW, you seem have another problem. The drivers/workers are serviced always
>> in the same order which would systematically choke ones near to the list
>> end. A usual solution of this problem is list walking entered at the point
>> where it was left.
> 
> Do you mean worker #1 would tend to be overworked compared to the others ?
> In the program I run - perhaps because the jobs are long: a few seconds
> number crunching - it doesn't seem to be the case:
> 
> Worker 1	362	jobs
> Worker 2	540	jobs
> Worker 3	652	jobs
> Worker 4	359	jobs
> Worker 5	366	jobs
> Worker 6	592	jobs
> Worker 7	371	jobs
> Worker 8	370	jobs

The problem should first appear under 100% load. It would not appear if you
never exit the loop. But it seems that you do. (Not that I really
understand what are you going to achieve.)

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


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

* Re: Structure of the multitasking server
  2015-03-13 20:16           ` gautier_niouzes
  2015-03-13 20:47             ` Dmitry A. Kazakov
@ 2015-03-13 23:04             ` Randy Brukardt
  2015-03-14  8:22               ` Simon Wright
  1 sibling, 1 reply; 32+ messages in thread
From: Randy Brukardt @ 2015-03-13 23:04 UTC (permalink / raw)


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 828 bytes --]

<gautier_niouzes@hotmail.com> wrote in message 
news:c943b1fd-8c0c-4c22-ba8c-fb72fbd52805@googlegroups.com...
Le vendredi 13 mars 2015 09:14:52 UTC+1, Dmitry A. Kazakov a écrit :

>> You could add a wakeup event to prevent busy waiting. An alternative 
>> would
>> be a waitable jobs queue.

>Wouldn't a delay instruction help ?

Of course. A "slow busy wait" is good enough in almost all circumstances 
(typically, the machine isn't doing anything important anyway during that 
the minimal time used). It certainly beats distorting one's design.

Of course, if there is a queue of jobs anyway, it makes sense to arrange 
that to be blocking so you end up waiting on that. But then of course you 
need a "stop" job else the supervisor ends up waiting forever.

                                                         Randy.




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

* Re: Structure of the multitasking server
  2015-03-13 23:04             ` Randy Brukardt
@ 2015-03-14  8:22               ` Simon Wright
  0 siblings, 0 replies; 32+ messages in thread
From: Simon Wright @ 2015-03-14  8:22 UTC (permalink / raw)


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

> Of course, if there is a queue of jobs anyway, it makes sense to
> arrange that to be blocking so you end up waiting on that. But then of
> course you need a "stop" job else the supervisor ends up waiting
> forever.

Having the supervisor wait forever is OK in some circumstances
(particularly in embedded systems). Though I had to introduce one to
support offline tests (you need to be able to go on to the next test!)


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

* Re: Structure of the multitasking server
  2015-03-13 20:47             ` Dmitry A. Kazakov
@ 2015-03-15  7:43               ` gautier_niouzes
  2015-03-15  8:35                 ` Simon Wright
  2015-03-15  8:52                 ` J-P. Rosen
  0 siblings, 2 replies; 32+ messages in thread
From: gautier_niouzes @ 2015-03-15  7:43 UTC (permalink / raw)


There is 100% load in my case. 8 worker tasks, 8 CPUs, all busy with the number crunching. What I'm doing is running computation jobs in parallel on different tasks and ensuring all tasks are busy. Each time a worker is done, it gets quickly a new job and I'm happy.

...
    << Worker 7       done with driver 3369
>>     Worker 7   starting with driver 3380
    << Worker 8       done with driver 3372
>>     Worker 8   starting with driver 3381
    << Worker 6       done with driver 3374
>>     Worker 6   starting with driver 3382
    << Worker 4       done with driver 3378
>>     Worker 4   starting with driver 3383
    << Worker 3       done with driver 3377
>>     Worker 3   starting with driver 3384
    << Worker 5       done with driver 3375
>>     Worker 5   starting with driver 3385
    << Worker 7       done with driver 3380
>>     Worker 7   starting with driver 3386
...
_________________________ 
Gautier's Ada programming 
http://sf.net/users/gdemont/


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

* Re: Structure of the multitasking server
  2015-03-15  7:43               ` gautier_niouzes
@ 2015-03-15  8:35                 ` Simon Wright
  2015-03-15  8:52                 ` J-P. Rosen
  1 sibling, 0 replies; 32+ messages in thread
From: Simon Wright @ 2015-03-15  8:35 UTC (permalink / raw)


gautier_niouzes@hotmail.com writes:

> There is 100% load in my case. 8 worker tasks, 8 CPUs, all busy with
> the number crunching. What I'm doing is running computation jobs in
> parallel on different tasks and ensuring all tasks are busy. Each time
> a worker is done, it gets quickly a new job and I'm happy.

I know this isn't closely related, but make has the flag -j (--jobs), which
specifies the number of jobs to run simultaneously. --jobs=4 was OK
(this is a Macbook Pro with an Intel Core i5), but building GCC with
just --jobs (run as many jobs simultaneously as possible) froze the
machine to the point where a forced power cycle was the only option. I
wasn't happy.

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

* Re: Structure of the multitasking server
  2015-03-15  7:43               ` gautier_niouzes
  2015-03-15  8:35                 ` Simon Wright
@ 2015-03-15  8:52                 ` J-P. Rosen
  2015-03-15  9:21                   ` Jacob Sparre Andersen
  1 sibling, 1 reply; 32+ messages in thread
From: J-P. Rosen @ 2015-03-15  8:52 UTC (permalink / raw)


Le 15/03/2015 08:43, gautier_niouzes@hotmail.com a écrit :
> There is 100% load in my case. 8 worker tasks, 8 CPUs, all busy with
> the number crunching. What I'm doing is running computation jobs in
> parallel on different tasks and ensuring all tasks are busy. Each
> time a worker is done, it gets quickly a new job and I'm happy.
> 
> ...

If you give the producer a lower priority than the workers, it will be
activated only when workers are done, and busy waiting is no more a problem.

-- 
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] 32+ messages in thread

* Re: Structure of the multitasking server
  2015-03-15  8:52                 ` J-P. Rosen
@ 2015-03-15  9:21                   ` Jacob Sparre Andersen
  2015-03-15 16:04                     ` Brad Moore
  0 siblings, 1 reply; 32+ messages in thread
From: Jacob Sparre Andersen @ 2015-03-15  9:21 UTC (permalink / raw)


J-P. Rosen wrote:

> If you give the producer a lower priority than the workers, it will be
> activated only when workers are done, and busy waiting is no more a
> problem.

Nice solution. :-)

Greetings,

Jacob
-- 
"Any, sufficiently complicated, experiment is indistinguishable from magic."

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

* Re: Structure of the multitasking server
  2015-03-15  9:21                   ` Jacob Sparre Andersen
@ 2015-03-15 16:04                     ` Brad Moore
  0 siblings, 0 replies; 32+ messages in thread
From: Brad Moore @ 2015-03-15 16:04 UTC (permalink / raw)


On 15-03-15 03:21 AM, Jacob Sparre Andersen wrote:
> J-P. Rosen wrote:
>
>> If you give the producer a lower priority than the workers, it will be
>> activated only when workers are done, and busy waiting is no more a
>> problem.

I use this approach in the Ravenscar Paraffin libraries.

Note though, that this approach only works on real-time systems, which implement the priority model.

Linux (and Windows) for example by default doesn't do this, so the spinning of the lower priority task can interfere
with the higher priority tasks running on the same core. If you are using a flavor of Linux that has been configured for
real-time then you should be able to rely on this behaviour.

   See
     https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO#About_the_RT-Preempt_Patch

Otherwise, this approach doesn't really work (it works but inefficient because CPU is wasted on spinning),
so you should use blocking instead of spin waiting.
In Paraffin, I provide two versions of Ravenscar libraries (one blocking, and one spinning), so that you can use
whichever version best suits your OS.

Brad

>
> Nice solution. :-)
>
> Greetings,
>
> Jacob
>



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

end of thread, other threads:[~2015-03-15 16:04 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-09-19 12:21 Structure of the multitasking server Maciej Sobczak
2008-09-19 13:34 ` Jean-Pierre Rosen
2008-09-19 17:02   ` Dmitry A. Kazakov
2008-09-21 17:30     ` Maciej Sobczak
2008-09-21 19:24       ` Dmitry A. Kazakov
2008-09-21 21:27         ` Maciej Sobczak
2008-09-22  8:12           ` Dmitry A. Kazakov
2008-09-22 12:47             ` Maciej Sobczak
2008-09-22 14:11               ` Dmitry A. Kazakov
2008-09-23  8:07                 ` Maciej Sobczak
2008-09-23  9:37                   ` Dmitry A. Kazakov
2008-09-23 10:47                   ` Jean-Pierre Rosen
2008-09-21 17:23   ` Maciej Sobczak
2008-09-22  8:23     ` Jean-Pierre Rosen
2015-03-12 16:07   ` gautier_niouzes
2015-03-12 21:38     ` Jacob Sparre Andersen
2015-03-12 22:39       ` gautier_niouzes
2015-03-13  8:15         ` Dmitry A. Kazakov
2015-03-13 20:16           ` gautier_niouzes
2015-03-13 20:47             ` Dmitry A. Kazakov
2015-03-15  7:43               ` gautier_niouzes
2015-03-15  8:35                 ` Simon Wright
2015-03-15  8:52                 ` J-P. Rosen
2015-03-15  9:21                   ` Jacob Sparre Andersen
2015-03-15 16:04                     ` Brad Moore
2015-03-13 23:04             ` Randy Brukardt
2015-03-14  8:22               ` Simon Wright
2008-09-19 23:01 ` anon
2008-09-21 17:37   ` Maciej Sobczak
2008-09-22  2:32     ` anon
2008-09-22 13:05       ` Maciej Sobczak
2008-09-23  9:25         ` anon

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