Prev: Using GtkAda in Ubuntu
Next: gnat on opensuse 11.1
From: Maciej Sobczak on 21 Sep 2008 13:30 On 19 Wrz, 19:02, "Dmitry A. Kazakov" <mail...(a)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
From: Maciej Sobczak on 21 Sep 2008 13:37 On 20 Wrz, 01:01, a...(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
From: Dmitry A. Kazakov on 21 Sep 2008 15:24 On Sun, 21 Sep 2008 10:30:37 -0700 (PDT), Maciej Sobczak wrote: > On 19 Wrz, 19:02, "Dmitry A. Kazakov" <mail...(a)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
From: Maciej Sobczak on 21 Sep 2008 17:27 On 21 Wrz, 21:24, "Dmitry A. Kazakov" <mail...(a)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
From: anon on 21 Sep 2008 22:32
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(a)d1g2000hsg.googlegroups.com>, Maciej Sobczak <see.my.homepage(a)gmail.com> writes: >On 20 Wrz, 01:01, a...(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 |