* How to avoid unreferenced objects (mutexes etc) @ 2002-01-11 13:48 Dmitry A. Kazakov 2002-01-11 13:52 ` Lutz Donnerhacke ` (4 more replies) 0 siblings, 5 replies; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-11 13:48 UTC (permalink / raw) Hi all! I have a question. Let I implement mutex and locking objects as follows: protected type Mutex is entry Seize; procedure Release; private Owned : Boolean := False; end Mutex; type Lock (Resource : access Mutex) is new Ada.Finalization.Limited_Controlled with null record; procedure Initialize (Object : in out Lock) is begin Object.Resource.Seize; end Initialize; procedure Finalize (Object : in out Lock) is begin Object.Resource.Release; end Finalize; The idea is to write critical sections as follows: Temp : Lock (Mutex_of_a_resource'Access); begin ... -- Safe access to the resource end; -- Mutex is released even if an exception propagates The problem is that the object Temp is never referenced. The compiler complains of that, but it is a minor problem. The questions are 1. Has the compiler right to optimize out Temp? 2. Is there a better solution? Thanks, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 13:48 How to avoid unreferenced objects (mutexes etc) Dmitry A. Kazakov @ 2002-01-11 13:52 ` Lutz Donnerhacke 2002-01-11 14:47 ` Robert A Duff ` (3 subsequent siblings) 4 siblings, 0 replies; 41+ messages in thread From: Lutz Donnerhacke @ 2002-01-11 13:52 UTC (permalink / raw) * Dmitry A. Kazakov wrote: >The problem is that the object Temp is never referenced. >The compiler complains of that, but it is a minor problem. You can use a compiler specific pragma to supress this warning. >The questions are > 1. Has the compiler right to optimize out Temp? No. There are initialisation and finalization actions which has to be taken. Other example "t : task_access := new task_type;" is also often "unused". > 2. Is there a better solution? That's a wonderful canoncical pattern. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 13:48 How to avoid unreferenced objects (mutexes etc) Dmitry A. Kazakov 2002-01-11 13:52 ` Lutz Donnerhacke @ 2002-01-11 14:47 ` Robert A Duff 2002-01-11 18:02 ` Jeffrey Carter ` (2 subsequent siblings) 4 siblings, 0 replies; 41+ messages in thread From: Robert A Duff @ 2002-01-11 14:47 UTC (permalink / raw) dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) writes: > Temp : Lock (Mutex_of_a_resource'Access); > The problem is that the object Temp is never referenced. The compiler > complains of that, but it is a minor problem. The questions are > > 1. Has the compiler right to optimize out Temp? No. There are some extra permissions to "optimize" away pseudo-dead variables of a non-limited type, even when init/fin have side effects. (there's an AI on that.) But your type is limited, and therefore immune to such meddling. > 2. Is there a better solution? Your solution is fine. - Bob ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 13:48 How to avoid unreferenced objects (mutexes etc) Dmitry A. Kazakov 2002-01-11 13:52 ` Lutz Donnerhacke 2002-01-11 14:47 ` Robert A Duff @ 2002-01-11 18:02 ` Jeffrey Carter 2002-01-11 19:40 ` Robert Dewar 2002-01-12 1:11 ` Nick Roberts 4 siblings, 0 replies; 41+ messages in thread From: Jeffrey Carter @ 2002-01-11 18:02 UTC (permalink / raw) "Dmitry A. Kazakov" wrote: > > The idea is to write critical sections as follows: > > Temp : Lock (Mutex_of_a_resource'Access); > begin > ... -- Safe access to the resource > end; -- Mutex is released even if an exception propagates > > 2. Is there a better solution? Use a dedicated monitor to implement the critical sections. -- Jeffrey Carter ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 13:48 How to avoid unreferenced objects (mutexes etc) Dmitry A. Kazakov ` (2 preceding siblings ...) 2002-01-11 18:02 ` Jeffrey Carter @ 2002-01-11 19:40 ` Robert Dewar 2002-01-12 10:18 ` Martin Dowie 2002-01-14 8:54 ` Dmitry A. Kazakov 2002-01-12 1:11 ` Nick Roberts 4 siblings, 2 replies; 41+ messages in thread From: Robert Dewar @ 2002-01-11 19:40 UTC (permalink / raw) dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote in message news:<3c3ee8c8.105408250@News.CIS.DFN.DE>... > The problem is that the object Temp is never referenced. > The compiler > complains of that, but it is a minor problem. The > questions are In GNAT, we just introduced pragma Unreferenced (entity-name, entity-name, ....); specifically to deal with entities that are deliberately unreferenced. This pragma supresses unreferenced warnings for the entity, and gives an error if the entity is actually referenced. It provides useful documentation that the entity is really really intended to be there, and serves a purpose even though it is not specifically referenced. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 19:40 ` Robert Dewar @ 2002-01-12 10:18 ` Martin Dowie 2002-01-14 8:54 ` Dmitry A. Kazakov 1 sibling, 0 replies; 41+ messages in thread From: Martin Dowie @ 2002-01-12 10:18 UTC (permalink / raw) "Robert Dewar" <dewar@gnat.com> wrote in message news:5ee5b646.0201111140.48bf30e2@posting.google.com... > dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote in message news:<3c3ee8c8.105408250@News.CIS.DFN.DE>... > In GNAT, we just introduced > > pragma Unreferenced (entity-name, entity-name, ....); > > specifically to deal with entities that are deliberately > unreferenced. This pragma supresses unreferenced warnings > for the entity, and gives an error if the entity is actually referenced. > > It provides useful documentation that the entity is really > really intended to be there, and serves a purpose even > though it is not specifically referenced. This sounds unbelievable useful, is it going to be proposed for Ada200X? ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 19:40 ` Robert Dewar 2002-01-12 10:18 ` Martin Dowie @ 2002-01-14 8:54 ` Dmitry A. Kazakov 1 sibling, 0 replies; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-14 8:54 UTC (permalink / raw) On 11 Jan 2002 11:40:37 -0800, dewar@gnat.com (Robert Dewar) wrote: >dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote in message news:<3c3ee8c8.105408250@News.CIS.DFN.DE>... >> The problem is that the object Temp is never referenced. >> The compiler >> complains of that, but it is a minor problem. The >> questions are > > >In GNAT, we just introduced > > pragma Unreferenced (entity-name, entity-name, ....); > >specifically to deal with entities that are deliberately >unreferenced. This pragma supresses unreferenced warnings >for the entity, and gives an error if the entity is actually referenced. > >It provides useful documentation that the entity is really >really intended to be there, and serves a purpose even >though it is not specifically referenced. Very nice. In which GNAT version it appears? Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-11 13:48 How to avoid unreferenced objects (mutexes etc) Dmitry A. Kazakov ` (3 preceding siblings ...) 2002-01-11 19:40 ` Robert Dewar @ 2002-01-12 1:11 ` Nick Roberts 2002-01-12 22:04 ` Matthew Heaney 2002-01-14 9:42 ` Dmitry A. Kazakov 4 siblings, 2 replies; 41+ messages in thread From: Nick Roberts @ 2002-01-12 1:11 UTC (permalink / raw) "Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c3ee8c8.105408250@News.CIS.DFN.DE... > protected type Mutex is > entry Seize; > procedure Release; > private > Owned : Boolean := False; > end Mutex; > > type Lock (Resource : access Mutex) is new > Ada.Finalization.Limited_Controlled with null record; > procedure Initialize (Object : in out Lock) is > begin > Object.Resource.Seize; > end Initialize; > procedure Finalize (Object : in out Lock) is > begin > Object.Resource.Release; > end Finalize; > > The idea is to write critical sections as follows: > > Temp : Lock (Mutex_of_a_resource'Access); > begin > ... -- Safe access to the resource > end; -- Mutex is released even if an exception propagates This may well be simply my own taste, but I would much prefer the critical section to be written thus: type Indirect_Mutex is access all Mutex; The_Lock: aliased Mutex; ... Lock: [constant] Indirect_Mutex := The_Lock'Access; begin -- here we can do any non-critical pre-processing Lock.Seize; begin -- critical code goes here exception when others => -- reset resource state to something stable end; Lock.Release; -- here we can do any non-critical post-processing end; This way, no mucking about with finalisation or unreferenced objects is required, and you have natural places to put pre- and post-processing code. More importantly, this scheme ensures that if some critical processing (presumably messing about with the state of the resource) does go wrong, the resource is 'cleaned up' before any other innocent task can try using it. However, really, a much better design is like this: protected type My_Resource_Type is procedure Do_Something_Critical; private ... -- resource state end; ... protected body My_Resource_Type is procedure Do_Something_Critical is begin -- critical code here exception when others => -- reset resource state to something stable end; end My_Resource_Type; ... The_Resource: My_Resource_Type; ... begin -- pre-processing The_Resource.Do_Something_Critical; -- post-processing end; The general idea is that a mutex is a low-level synchronisation construct, and Ada provides a higher-level construct (protected objects) that takes away much of the pain and danger of using mutexes directly. -- Best wishes, Nick Roberts ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-12 1:11 ` Nick Roberts @ 2002-01-12 22:04 ` Matthew Heaney 2002-01-13 5:45 ` Nick Roberts 2002-01-14 8:31 ` Jean-Pierre Rosen 2002-01-14 9:42 ` Dmitry A. Kazakov 1 sibling, 2 replies; 41+ messages in thread From: Matthew Heaney @ 2002-01-12 22:04 UTC (permalink / raw) "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> wrote in message news:a1o2h1$sfe3f$5@ID-25716.news.dfncis.de... > However, really, a much better design is like this: No, because protected operations can't make blocking calls. Note that all predefined I/O packages are "potentially blocking," which means, for example, that you can't call Text_IO from inside a protected object. So you still need a semaphore-style type (as in the original post), to handle the situation in which blocking calls are made inside a critical section. The technique shown using a Limited_Controlled type to automatically seize and release the semaphore is a standard Ada95 idiom. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-12 22:04 ` Matthew Heaney @ 2002-01-13 5:45 ` Nick Roberts 2002-01-13 8:21 ` tmoran ` (3 more replies) 2002-01-14 8:31 ` Jean-Pierre Rosen 1 sibling, 4 replies; 41+ messages in thread From: Nick Roberts @ 2002-01-13 5:45 UTC (permalink / raw) "Matthew Heaney" <mheaney@on2.com> wrote in message news:u41cg9jaqnrs56@corp.supernews.com... > "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> wrote in message > news:a1o2h1$sfe3f$5@ID-25716.news.dfncis.de... > > However, really, a much better design is like this: > > No, because protected operations can't make blocking calls. > > Note that all predefined I/O packages are "potentially blocking," which > means, for example, that you can't call Text_IO from inside a protected > object. Well, I had no idea that Dmitry's critical section contained blocking calls. My comments were made on the very reasonable assumption that it didn't. I would suggest that it is unusual for a critical section to need to make blocking calls, and generally desirable to remove blocking calls from critical sections wherever possible. Moreover, it is generally desirable to reduce the execution time (both average and maximum, if possible) of a critical section to a reasonable minimum. > So you still need a semaphore-style type (as in the original post), to > handle the situation in which blocking calls are made inside a critical > section. No you don't. If your critical section must perform a blocking action, put it in a task. E.g.: task type My_Resource_Type is entry Do_Something_Critical; end; ... task body My_Resource_Type is ... -- resource state begin loop -- internal pre-processing select accept Do_Something_Critical do -- critical code here (blocking operations possible) exception when others => -- reset resource state to something stable end; -- internal (checking and) post-processing or terminate; end select; end loop; end My_Resource_Type; ... The_Resource: My_Resource_Type; ... begin -- pre-processing specific to this point in execution The_Resource.Do_Something_Critical; -- post-processing specific to this point in execution end; Of course, this is likely to be implemented by the compiler using mutexes/semaphores. The point is, it's generally better to use a higher-level construct like this, because it generally gives the compiler more opportunity to catch bugs. > The technique shown using a Limited_Controlled type to > automatically seize and release the semaphore is a standard Ada95 idiom. It may be a standard idiom, to some people, but that doesn't necessarily make it the best approach. When the COBOL ALTER verb was invented, it was standard idiom at the time for programs to modify their own code dynamically. Nowadays, it is recognised that anyone using ALTER faces the danger of self-lobotomy*. Or, to put it another way, the standard idiom in this case doesn't seem to address the problem of an exception leaving the resource in question in an unstable state. Nor does it do anything to mitigate the effects of accidentally omitting (or wrongly positioning) one of the seize or release calls. -- Best wishes, Nick Roberts *Eight months, nine hundred and fifty man hours, twenty three bottles of painkillers, ninety five bottles of whiskey, two divorces, one and a half garagefuls of listings, seventy severe arguments with the boss, and five therapists later. Or so ex CA Associates employees tell me, anyway. :-o ;-) ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-13 5:45 ` Nick Roberts @ 2002-01-13 8:21 ` tmoran 2002-01-13 16:12 ` Nick Roberts 2002-01-13 15:08 ` Simon Wright ` (2 subsequent siblings) 3 siblings, 1 reply; 41+ messages in thread From: tmoran @ 2002-01-13 8:21 UTC (permalink / raw) > and generally desirable to remove blocking calls from > critical sections wherever possible. Sometimes the "critical section" quite reasonably could last a long time - when it's doing an IO operation using an IO channel, for instance. That can be long, lots of other things can legitimately run, but there better not be anyone else trying to use that same IO channel at the same time. Claw.Sockets.Read, for instance, uses this idiom, and a Read could block a particular socket for quite a while. >address the problem of an exception leaving the resource in question in an >unstable state. Nor does it do anything to mitigate the effects of >accidentally omitting (or wrongly positioning) one of the seize or release >calls. The original question was about: > Temp : Lock (Mutex_of_a_resource'Access); >begin > ... -- Safe access to the resource >end; -- Mutex is released even if an exception propagates and clearly there is no danger of omitting or wrongly positioning anything, or of problems with an exception. In fact Temp : Lock (Mutex_of_a_resource'Access); Something : Positive := Some_Value; would even be safe if Some_Value was negative, which is not a case a local (after the "begin") exception handler would catch. Once Initialize(Temp) has been called a matching Finalize(Temp) call is guaranteed. > > The technique shown using a Limited_Controlled type to > > automatically seize and release the semaphore is a standard Ada95 idiom. And a good idiom, IMHO. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-13 8:21 ` tmoran @ 2002-01-13 16:12 ` Nick Roberts 0 siblings, 0 replies; 41+ messages in thread From: Nick Roberts @ 2002-01-13 16:12 UTC (permalink / raw) <tmoran@acm.org> wrote in message news:msb08.31576$%d4.3960804474@newssvr14.news.prodigy.com... > > and generally desirable to remove blocking calls from > > critical sections wherever possible. > Sometimes the "critical section" quite reasonably could last a > long time - when it's doing an IO operation using an IO channel, > for instance. That can be long, lots of other things can legitimately > run, but there better not be anyone else trying to use that same IO > channel at the same time. Claw.Sockets.Read, for instance, uses > this idiom, and a Read could block a particular socket for quite a while. Yes, but this isn't really a point at issue, is it? The situation you describe is a classic example of (the need for) the use of a buffer (is the terminology 'concurrent queue' these days?) to smooth out the timing differences between producer and consumer. If the target is a multi-processor machine, it can be particularly important (from the point of view of efficiency) that a task is used to do the buffering. > >address the problem of an exception leaving the resource in question in an > >unstable state. Nor does it do anything to mitigate the effects of > >accidentally omitting (or wrongly positioning) one of the seize or release > >calls. > The original question was about: > > Temp : Lock (Mutex_of_a_resource'Access); > >begin > > ... -- Safe access to the resource > >end; -- Mutex is released even if an exception propagates > and clearly there is no danger of omitting or wrongly positioning > anything, Absolutely true. My mistake. > or of problems with an exception. But there *is* a problem with exception handling: whilst the mutex will be released okay, the resource could be left in a bad state, and nothing (in Dmitry's example) is done to attempt to repair the damage. In fact, I note the possibility of building into the Finalize checks for the integrity of the resource's state (upon failure of which the resource could be reset). This technique certainly could mitigate the problems of leaving the resource in an unstable state, but it may be impractical or too inefficient. It could also be obfuscatory, in placing the clean-up code away from the code it cleans up. The advantage of having the clean-up code at the site of each (call to a) critical section (as per my examples) is that the clean-up code can then be specific to the code which causes the damage, and this is likely to be the more intelligible arrangement too. > In fact > > Temp : Lock (Mutex_of_a_resource'Access); > Something : Positive := Some_Value; > > would even be safe if Some_Value was negative, > ... > Once Initialize(Temp) has been called a matching Finalize(Temp) call is > guaranteed. Correct (apart from the possibility of the resource being left in a bad state, as just mentioned). > which is not a case a > local (after the "begin") exception handler would catch. Correct, but irrelevant. In my first example (with explicit seize and release), an exception propagated during elaboration of the relevant declarative region (where Lock is) would prevent the mutex ever being seized. In my second example (with the protected object holding the resource state), an exception propagated during elaboration of the declarative region of the protected procedure Do_Something_Critical would be propagated out of the procedure before it could possibly start altering the state of the resource (and the protected object is internally unlocked when the exception is propagated out). In my third example (with the task type), a call on the entry Do_Something_Critical does not cause the elaboration of a declarative region (but if it did, it too would be placed outside the actual critical code). Thus, these are all three perfectly safe from something going wrong during the elaboration of declarations. > > > The technique shown using a Limited_Controlled type to > > > automatically seize and release the semaphore is a standard Ada95 idiom. > And a good idiom, IMHO. I continue to disagree, for the reasons I have stated. I recognise that there will sometimes be occasions when the lower-level approach cannot be avoided (as with other situations where there is a choice between higher and lower levels of solution), but I hold that such occasions will be rare. (I also recognise that my O is rarely H ;-) -- Best wishes, Nick Roberts ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-13 5:45 ` Nick Roberts 2002-01-13 8:21 ` tmoran @ 2002-01-13 15:08 ` Simon Wright 2002-01-15 17:53 ` Nick Roberts 2002-01-13 16:51 ` Jeffrey Carter 2002-01-14 23:32 ` Matthew Heaney 3 siblings, 1 reply; 41+ messages in thread From: Simon Wright @ 2002-01-13 15:08 UTC (permalink / raw) "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> writes: > Or, to put it another way, the standard idiom in this case doesn't > seem to address the problem of an exception leaving the resource in > question in an unstable state. Nor does it do anything to mitigate > the effects of accidentally omitting (or wrongly positioning) one of > the seize or release calls. ? critical : declare l : Lock; begin critical stuff end critical; * No seize/release calls * Finalization on exit from critical region for whatever reason (including exceptions) ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-13 15:08 ` Simon Wright @ 2002-01-15 17:53 ` Nick Roberts 0 siblings, 0 replies; 41+ messages in thread From: Nick Roberts @ 2002-01-15 17:53 UTC (permalink / raw) "Simon Wright" <simon@pushface.org> wrote in message news:x7v7kqmxp8j.fsf@smaug.pushface.org... > ... > * No seize/release calls > > * Finalization on exit from critical region for whatever reason > (including exceptions) Yes, quite right, I slipped. See my other reply. -- Best wishes, Nick Roberts ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-13 5:45 ` Nick Roberts 2002-01-13 8:21 ` tmoran 2002-01-13 15:08 ` Simon Wright @ 2002-01-13 16:51 ` Jeffrey Carter 2002-01-14 23:32 ` Matthew Heaney 3 siblings, 0 replies; 41+ messages in thread From: Jeffrey Carter @ 2002-01-13 16:51 UTC (permalink / raw) Nick Roberts wrote: > [referring to using a task to implement a critical section] > Of course, this is likely to be implemented by the compiler using > mutexes/semaphores. This appears to have been the expectation of the Ada-83 designers, but few if any compilers did so automatically (some had pragmata to allow the developer to indicate that a task was really a monitor), which is why protected objects were added to Ada 95. Nevertheless, I would still prefer the use of a task to a semaphore when mutual exclusion is needed for processing that cannot be in a protected object. -- Jeff Carter "Perfidious English mouse-dropping hoarders." Monty Python & the Holy Grail ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-13 5:45 ` Nick Roberts ` (2 preceding siblings ...) 2002-01-13 16:51 ` Jeffrey Carter @ 2002-01-14 23:32 ` Matthew Heaney 2002-01-15 8:53 ` Dmitry A. Kazakov 3 siblings, 1 reply; 41+ messages in thread From: Matthew Heaney @ 2002-01-14 23:32 UTC (permalink / raw) "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> wrote in message news:a1r6tr$slh9n$1@ID-25716.news.dfncis.de... > Well, I had no idea that Dmitry's critical section contained blocking calls. You seem to be objecting to the very idea of a semaphore type, claiming that all your synchronization needs can be done using a monitor-style protected object. That would be false, because a monitor implemented as a protected object is not allowed to make blocking calls. > My comments were made on the very reasonable assumption that it didn't. I > would suggest that it is unusual for a critical section to need to make > blocking calls, No, it is very common to have to make blocking calls in critical region. For example, I am writing a server in which client messages are logged to a file. However, multiple threads can be handling client requests, so the manipulation of the file object (to write the message) must by protected by a critical region. The critical region cannot be implemented as a monitor-style protected object, because the action is blocking. Hence the need for a semaphore-style protected object. > and generally desirable to remove blocking calls from critical sections wherever possible. I don't make blocking calls inside a critical region for my own amusement. I make a blocking call inside a critical region because I need to serialize access to a resource, and manipulation of the resource blocks. > Moreover, it is generally desirable to > reduce the execution time (both average and maximum, if possible) of a > critical section to a reasonable minimum. Motherhood and apple pie: no kidding. > No you don't. If your critical section must perform a blocking action, put > it in a task. E.g.: But the whole point is to use tasks as a unit of concurrency, not as a synchronization mechanism. That's why protected objects were invented. > The point is, it's generally better to use a > higher-level construct like this, because it generally gives the compiler > more opportunity to catch bugs. Ever hear the story about the king who asked both a computer programmer and an electrical engineer to design a toaster? http://www.shartwell.freeserve.co.uk/humor-site/elecvscomp.htm To use a high-level construct like a task to do a job that only requires a low-level primitive is an example of "abstraction inversion." Yes, a semaphore all by itself is dangerous, because you can forget to release it. But when used in conjuction with a controlled object, the release is guaranteed to happen, so there is no danger. > It may be a standard idiom, to some people, but that doesn't necessarily > make it the best approach. All you're saying here is that you don't like it. > Or, to put it another way, the standard idiom in this case doesn't seem to > address the problem of an exception leaving the resource in question in an > unstable state. Nor was it intended to. The idiom exists to guarantee that the Release operation of a semaphore is called, not matter what happens. If the exception leaves the resource in an "unknown state," that is a separate issue, quite orthogonal to the issue of synchronization. > Nor does it do anything to mitigate the effects of > accidentally omitting (or wrongly positioning) one of the seize or release > calls. Huh? When you use the controlled-object idiom, the seize and release calls are made for you by the controlled-object: Semphore : aliased Semaphore_Type; Critical_Section: declare Control : Control_Type (Semaphore'Access); begin <manipulate resource> end Critical_Section; The real problem is that Ada doesn't have a termination clause, like a finally clause in Java or in Win32 SEH. That's why these idioms are necessary: to work around that omission. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-14 23:32 ` Matthew Heaney @ 2002-01-15 8:53 ` Dmitry A. Kazakov 0 siblings, 0 replies; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-15 8:53 UTC (permalink / raw) On Mon, 14 Jan 2002 18:32:35 -0500, "Matthew Heaney" <mheaney@on2.com> wrote: >Yes, a semaphore all by itself is dangerous, because you can forget to >release it. But when used in conjuction with a controlled object, the >release is guaranteed to happen, so there is no danger. IMO, when it is said that semaphore is a low-level mechanism, it means approximately the following. Tasks and protected objects may have entries implementing *something* [useful (:-))]. In contrary to this, semaphores just implement themselves. A semaphore is useless without the resource it blocks. But then why is it separated from the resource? Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-12 22:04 ` Matthew Heaney 2002-01-13 5:45 ` Nick Roberts @ 2002-01-14 8:31 ` Jean-Pierre Rosen 1 sibling, 0 replies; 41+ messages in thread From: Jean-Pierre Rosen @ 2002-01-14 8:31 UTC (permalink / raw) [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain, Size: 1096 bytes --] "Matthew Heaney" <mheaney@on2.com> a �crit dans le message news: u41cg9jaqnrs56@corp.supernews.com... > > "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> wrote in message > news:a1o2h1$sfe3f$5@ID-25716.news.dfncis.de... > > However, really, a much better design is like this: > > No, because protected operations can't make blocking calls. > > Note that all predefined I/O packages are "potentially blocking," which > means, for example, that you can't call Text_IO from inside a protected > object. > > So you still need a semaphore-style type (as in the original post), to > handle the situation in which blocking calls are made inside a critical > section. The technique shown using a Limited_Controlled type to > automatically seize and release the semaphore is a standard Ada95 idiom. > Have a look at package Protection from Adalog's components page (http://www.adalog.fr/compo2.htm) for a component that automates this idiom. -- --------------------------------------------------------- J-P. Rosen (rosen@adalog.fr) Visit Adalog's web site at http://www.adalog.fr ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-12 1:11 ` Nick Roberts 2002-01-12 22:04 ` Matthew Heaney @ 2002-01-14 9:42 ` Dmitry A. Kazakov 2002-01-15 15:41 ` Matthew Heaney 2002-01-15 18:59 ` Nick Roberts 1 sibling, 2 replies; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-14 9:42 UTC (permalink / raw) On Sat, 12 Jan 2002 01:11:43 -0000, "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> wrote: [...] >The general idea is that a mutex is a low-level synchronisation construct, >and Ada provides a higher-level construct (protected objects) that takes >away much of the pain and danger of using mutexes directly. Absolutely. So my suspicion goes. Or else, there must be something fundamentally wrong [in design] if unused objects are *really* required. It clearly indicates a *problem*. I would prefer a monitor based solution as Jeffrey Carter suggested, but I just failed to design it [properly]. Maybe I should describe the problem. There is a communication channel implemented as a tagged type. The base type provides low-level I/O operations. It has read and write tasks serving as monitors. The problem is with the derived types [potentially an arbitrary number of them]. They extend the base type and provide layered high-level operations. Some of them must lock the channel [either read or write one]. A "natural" solution would be to extend monitors, but alas tasks are not tagged. An untagged solution requires an increasing number of wrapping subroutines [thus run-time penalty]. A genericity-based solution produces a combinatoric explosion of packages used as the parameters for other packages etc. They must be instantiated at the library level. In other words it is a mess. So I chose the lesser evil = mutexes with all that nasy problems of error recovery, you have pointed. I really do not like it. Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-14 9:42 ` Dmitry A. Kazakov @ 2002-01-15 15:41 ` Matthew Heaney 2002-01-15 16:18 ` Hyman Rosen ` (2 more replies) 2002-01-15 18:59 ` Nick Roberts 1 sibling, 3 replies; 41+ messages in thread From: Matthew Heaney @ 2002-01-15 15:41 UTC (permalink / raw) "Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c429d1c.2624281@News.CIS.DFN.DE... > Absolutely. So my suspicion goes. Or else, there must be something > fundamentally wrong [in design] if unused objects are *really* > required. It clearly indicates a *problem*. The problem is that the language doesn't have a termination handler for blocks, a la a "finally" clause in Java or Win32 Structured Exception Handling. So you have to create "unused" objects as a work-around. > A "natural" solution would be to extend monitors, but alas tasks > are not tagged. Can't you parameterize a task with an access discriminant that binds to an object whose type is class-wide, and then call (dispatching) operations on that object? type T is tagged limited null record; procedure Op (O : access T); task Task_Object (O : access T'Class) is .. task body Task_Object is begin ... Op (O); --dispatching call to object viewed thru access discrim ... end; Now you're free to derive types from T, and bind derived type instances to a task. Using this idiom obviates the need for extensible task types. > So I chose the lesser evil = mutexes with all that nasty problems of > error recovery, you have pointed. I really do not like it. The language gives you primitives so that you can use to build in the safety. There is no problem using semaphores in Ada95, if you follow certain idioms. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-15 15:41 ` Matthew Heaney @ 2002-01-15 16:18 ` Hyman Rosen 2002-01-15 16:57 ` Darren New 2002-01-16 15:18 ` Dmitry A. Kazakov 2 siblings, 0 replies; 41+ messages in thread From: Hyman Rosen @ 2002-01-15 16:18 UTC (permalink / raw) Matthew Heaney wrote: > The problem is that the language doesn't have a termination handler for > blocks, a la a "finally" clause in Java or Win32 Structured Exception > Handling. So you have to create "unused" objects as a work-around. In C++, this is considered a desirable design paradigm, not a problem. If you happen to be controlling several resources, the "finally" way marches off the right side of the page - try { // acquire resorce 1 try { // acquire resource 2 try { // acquire resource 3 // now do something } finally { // release resource 3 } } finally { // release resource 2 } } finally { // release resource 1 } As opposed to Res1Holder r1; // acquire resource 1 Res2Holder r2; // acquire resource 2 Res3Holder r3; // acquire resource 3 // do something ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-15 15:41 ` Matthew Heaney 2002-01-15 16:18 ` Hyman Rosen @ 2002-01-15 16:57 ` Darren New 2002-01-15 18:57 ` Matthew Heaney 2002-01-16 15:18 ` Dmitry A. Kazakov 2 siblings, 1 reply; 41+ messages in thread From: Darren New @ 2002-01-15 16:57 UTC (permalink / raw) Matthew Heaney wrote: > Now you're free to derive types from T, and bind derived type instances to > a task. Using this idiom obviates the need for extensible task types. Not really, because there are various operations that may only be applied to entries, and various operations that may only appear in task bodies and hence your T and its derived types cannot affect it. As an example, if your task is coded without any guards on the entries, there's no way for a child of T to add a guard. You can do a fair amount with this idiom, but I wouldn't say it *obviates* the need for extensible tasks. -- Darren New San Diego, CA, USA (PST). Cryptokeys on demand. The opposite of always is sometimes. The opposite of never is sometimes. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-15 16:57 ` Darren New @ 2002-01-15 18:57 ` Matthew Heaney 2002-01-16 0:57 ` Darren New 0 siblings, 1 reply; 41+ messages in thread From: Matthew Heaney @ 2002-01-15 18:57 UTC (permalink / raw) "Darren New" <dnew@san.rr.com> wrote in message news:3C445F34.44697AEF@san.rr.com... > Matthew Heaney wrote: > > Now you're free to derive types from T, and bind derived type instances to > > a task. Using this idiom obviates the need for extensible task types. > > Not really, because there are various operations that may only be > applied to entries, and various operations that may only appear in task > bodies and hence your T and its derived types cannot affect it. But won't this work: package P is type T is abstract tagged limited private; procedure Op (O : in out T) is abstract; private task type T_Task_Type (O : access T) is entry E; end T_Task_Type; type T is abstract tagged limited record T_Task : T_Task_Type (T'Access); end record; end P; Now each instance of T gets its own thread. Types in T'Class have visibility to the task object (assuming they are declared in children of P). > As an example, if your task is coded without any guards on the entries, > there's no way for a child of T to add a guard. But couldn't a derived type NT extend T to include a protected object (that implements a semaphore, say), that could be used by types in NT'Class to do the same thing? > You can do a fair amount with this idiom, but I wouldn't say it > *obviates* the need for extensible tasks. I haven't found a need for extensible tasks. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-15 18:57 ` Matthew Heaney @ 2002-01-16 0:57 ` Darren New 2002-01-16 16:35 ` Stephen Leake 2002-01-16 23:04 ` Matthew Heaney 0 siblings, 2 replies; 41+ messages in thread From: Darren New @ 2002-01-16 0:57 UTC (permalink / raw) Matthew Heaney wrote: > > "Darren New" <dnew@san.rr.com> wrote in message > news:3C445F34.44697AEF@san.rr.com... > > Matthew Heaney wrote: > > > Now you're free to derive types from T, and bind derived type instances > to > > > a task. Using this idiom obviates the need for extensible task types. > > > > Not really, because there are various operations that may only be > > applied to entries, and various operations that may only appear in task > > bodies and hence your T and its derived types cannot affect it. > > But won't this work: > > package P is > type T is abstract tagged limited private; > procedure Op (O : in out T) is abstract; > private > task type T_Task_Type (O : access T) is > entry E; > end T_Task_Type; > > type T is abstract tagged limited record > T_Task : T_Task_Type (T'Access); > end record; > > end P; > > Now each instance of T gets its own thread. Types in T'Class have > visibility to the task object (assuming they are declared in children of P). Sure. Now Op can't act as an entry. The basic problem is that if I have something that says x.boop and boop is an entry, it can only call one specific piece of code. That one specific piece of code has to be the one that determines whether the code blocks. > > As an example, if your task is coded without any guards on the entries, > > there's no way for a child of T to add a guard. > > But couldn't a derived type NT extend T to include a protected object (that > implements a semaphore, say), that could be used by types in NT'Class to do > the same thing? That's not guarding an entry. If T.op dispatches, then it can't be an entry. If it's an entry, I can't override it. I can't do select T.op or delay 10.0 ; yadda end select; and have T.op be one of two different pieces of code, for example. The problem I had was trying to build a library wherein the framework instantiates a number of tasks. Each task is provided by the user of the framework. (Think, for example, of someone writing multiple types of windows in a GUI framework.) Each task would need to respond to different events at different times (meaning selective accepts in the tasks) and the framework wanted to not have to know what all these different task types are (meaning dispatching). Couldn't figure out how to do it properly. Wound up building a kludge that basically involved manually marshalling all the messages and queueing them, and generally reimplementing the entry queue mechanism in Ada. > I haven't found a need for extensible tasks. I have, but then that's the kind of software I write. -- Darren New San Diego, CA, USA (PST). Cryptokeys on demand. The opposite of always is sometimes. The opposite of never is sometimes. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 0:57 ` Darren New @ 2002-01-16 16:35 ` Stephen Leake 2002-01-16 18:07 ` Darren New 2002-01-16 23:04 ` Matthew Heaney 1 sibling, 1 reply; 41+ messages in thread From: Stephen Leake @ 2002-01-16 16:35 UTC (permalink / raw) Darren New <dnew@san.rr.com> writes: > > > As an example, if your task is coded without any guards on the entries, > > > there's no way for a child of T to add a guard. > > > > But couldn't a derived type NT extend T to include a protected object (that > > implements a semaphore, say), that could be used by types in NT'Class to do > > the same thing? > > That's not guarding an entry. If T.op dispatches, then it can't be an > entry. If it's an entry, I can't override it. Hmm. Entries with guards only occur in protected objects, not tasks (unless I'm really confused). So I'll assume we are talking about protected objects here. It's true that an entry (in either a task or a protected object) can be dispatching. But you can provide a guard in the parent task, that uses a function that is dispatching: entry Foo (...) when T.Bar; This appears to be legal when T.Bar is dispatching, but I've never tried it. > I can't do select T.op or delay 10.0 ; yadda end select; and have > T.op be one of two different pieces of code, for example. True. > The problem I had was trying to build a library wherein the > framework instantiates a number of tasks. Each task is provided by > the user of the framework. (Think, for example, of someone writing > multiple types of windows in a GUI framework.) Each task would need > to respond to different events at different times (meaning selective > accepts in the tasks) and the framework wanted to not have to know > what all these different task types are (meaning dispatching). > Couldn't figure out how to do it properly. Wound up building a > kludge that basically involved manually marshalling all the messages > and queueing them, and generally reimplementing the entry queue > mechanism in Ada. Maybe you can invert this; define the tasks in the framework code, with a standard set of entries, but have each entry call a function via dispatching, and allow the user to override those functions. This is the structure used by Borland's Object Windows Library. > > I haven't found a need for extensible tasks. > > I have, but then that's the kind of software I write. To be precise, you have a need for dispatching in a tasking context, and tried a design approach that doesn't work in Ada. There are other approaches; if one of them works, you have not found a need for extensible tasks :). -- -- Stephe ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 16:35 ` Stephen Leake @ 2002-01-16 18:07 ` Darren New 2002-01-16 23:18 ` Matthew Heaney 0 siblings, 1 reply; 41+ messages in thread From: Darren New @ 2002-01-16 18:07 UTC (permalink / raw) Stephen Leake wrote: > Hmm. Entries with guards only occur in protected objects, not tasks > (unless I'm really confused). So I'll assume we are talking about > protected objects here. Errr, no. I'm pretty sure a selective accept works in tasks. > It's true that an entry (in either a task or a protected object) can > be dispatching. But you can provide a guard in the parent task, that > uses a function that is dispatching: > > entry Foo (...) when T.Bar; > > This appears to be legal when T.Bar is dispatching, but I've never > tried it. Yes. But the problem is in the callee you want a selective accept, and in the caller you want a conditional entry, and you don't want those two tied together at compile time. In that case, you're screwed. If you could derive a task type from another task type, I'd be fine; that is, if I could have multiple different task bodies for the same task type, I'd not have had even conceptual problems. > Maybe you can invert this; define the tasks in the framework code, > with a standard set of entries, but have each entry call a function > via dispatching, and allow the user to override those functions. This > is the structure used by Borland's Object Windows Library. Basically, what I wound up doing was saying "I'll dispatch to your objects, and you must not block. If you block, you will wedge up every task trying to do I/O. Caveat emptor." Then I provided a library of ways of getting called without blocking when you're busy, such as marshalling up the arguments and queueing them, with a task that loops and dequeues the arguments. In other words, I reimplemented Ada's entry queues. > > > I haven't found a need for extensible tasks. > > > > I have, but then that's the kind of software I write. > > To be precise, you have a need for dispatching in a tasking context, > and tried a design approach that doesn't work in Ada. There are other > approaches; if one of them works, you have not found a need for > extensible tasks :). Well, sure, I could write a Java JVM in Ada and then I'd not need any extensions to Ada. That's not to say it's an appropriate solution. Personally, I don't think reimplementing the entry concept in Ada source code should be necessary. -- Darren New San Diego, CA, USA (PST). Cryptokeys on demand. The opposite of always is sometimes. The opposite of never is sometimes. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 18:07 ` Darren New @ 2002-01-16 23:18 ` Matthew Heaney 0 siblings, 0 replies; 41+ messages in thread From: Matthew Heaney @ 2002-01-16 23:18 UTC (permalink / raw) "Darren New" <dnew@san.rr.com> wrote in message news:3C45C12D.A229F569@san.rr.com... > Errr, no. I'm pretty sure a selective accept works in tasks. Yes, it does, but you shouldn't bother using it. Have the task wait on a guarded entry of a protected object, and communicate with the task by calling a protected procedure of the protected object. > Yes. But the problem is in the callee you want a selective accept, and > in the caller you want a conditional entry, and you don't want those two > tied together at compile time. Fine. So don't tie them together at compile time. The task calls a dispatching operation, and that dispatching operation is implemented in a derived type by calling a guarded entry of a protected object. The callee doesn't call an entry directly. He calls a primitive operation that is implemented by calling an entry (of a protected object). I showed this in my last post. > In that case, you're screwed. If you > could derive a task type from another task type, I'd be fine; that is, > if I could have multiple different task bodies for the same task type, > I'd not have had even conceptual problems. The behavior that varies is encapsulated by calling private primitive operations that dispatch: package P is type T is abstract tagged limited private; procedure Op (O : in out T) is abstract; private task type T_Task_Type (O : access T'Class); --type is class-wide type T is abstract tagged limited record T_Task : T_Task_Type (T'Access); end record; procedure Wait (O : access T); --primitive end P; package body P is procedure Wait (O : access T) is begin null; pragma Assert (False); --must override end; type T_Task_Type is begin loop Wait (O); -- dispatches according to tag of discriminant ... end loop; end T_Task_Type; end P; Now you can implement a derived type by extending T with a protected object, and then overidding Wait so that it calls an entry of the protected object. > In other words, I reimplemented Ada's entry queues. I don't think this is necessary. Find a way to turn tasks into callers of protected entries, so you can get rid of the select-accept statements. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 0:57 ` Darren New 2002-01-16 16:35 ` Stephen Leake @ 2002-01-16 23:04 ` Matthew Heaney 2002-01-17 0:21 ` Darren New 1 sibling, 1 reply; 41+ messages in thread From: Matthew Heaney @ 2002-01-16 23:04 UTC (permalink / raw) "Darren New" <dnew@san.rr.com> wrote in message news:3C44CFBD.BC1ED52F@san.rr.com... > Sure. Now Op can't act as an entry. I don't understand this comment. Op is a procedure, so by definition it isn't an entry. > The basic problem is that if I have something that says > x.boop > and boop is an entry, I don't understand this comment. Is x a task object, protected object, or something else? How can boop be anything other than an entry? > it can only call one specific piece of code. That > one specific piece of code has to be the one that determines whether the > code blocks. Can you say Boop (X), and then implement operation Boop to have the behavior you want? > That's not guarding an entry. If T.op dispatches, then it can't be an > entry. If it's an entry, I can't override it. I don't understand this comment. If you use the syntax T.op then T must be a task (or protected object). And in that case it's an entry, and so of course it doesn't dispatch. If you had said: "If Op (T) dispatches" then I would understand your question about whether it dispatches. But you didn't, so I'm confused. What's wrong with saying Op (T), and implementing Op as a primitive operation of T? > I can't do > select T.op or delay 10.0 ; yadda end select; > and have T.op be one of two different pieces of code, for example. This is a selective entry call. So wrap it in a dispatching operation: function Op (O : in out T; Timeout : Duration) return Boolean is begin select O.T_Task.Op; return False; or delay Timeout; return True; end select; end Op; Now if you say: Timed_Out := Op (O, 10.0); then you get blocking and dispatching. > The problem I had was trying to build a library wherein the framework > instantiates a number of tasks. Each task is provided by the user of the > framework. (Think, for example, of someone writing multiple types of > windows in a GUI framework.) Each task would need to respond to > different events at different times (meaning selective accepts in the > tasks) Don't use selective accepts in tasks. This is old-fashioned. Rewrite your tasks to make blocking calls to an internal protected object. Have operations communicate with the task by calling the protected object, which wakes up the waiting task. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 23:04 ` Matthew Heaney @ 2002-01-17 0:21 ` Darren New 0 siblings, 0 replies; 41+ messages in thread From: Darren New @ 2002-01-17 0:21 UTC (permalink / raw) Matthew Heaney wrote: > Yes, it does, but you shouldn't bother using it. Have the task wait on a > guarded entry of a protected object, and communicate with the task by > calling a protected procedure of the protected object. I must admit that I'm not 100% clear on how this is better than a selective accept, given that a selective accept allows me to time out and to pick whichever entry is available first. I don't think I can pick whichever protected object guard opens first, can I? > Don't use selective accepts in tasks. This is old-fashioned. Rewrite your > tasks to make blocking calls to an internal protected object. Have > operations communicate with the task by calling the protected object, which > wakes up the waiting task. Which is basically what I did. Except there were about 20 or 30 entry points, with various combinations or acceptabilities - i.e., only some operations made sense in some states. There was also a requirement to wait for events from other sources (e.g., events from the UI or events from the I/O framework). So I wound up with protected entries that waited for this or that or the other, then told you which, which you then called.... anyway, it was an awful mess compared to what it could have been had I been able to have multiple task bodies defined for a single task type. But thanks for the advice. If I ever go back to developing this, I'll rethink it again. -- Darren New San Diego, CA, USA (PST). Cryptokeys on demand. The opposite of always is sometimes. The opposite of never is sometimes. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-15 15:41 ` Matthew Heaney 2002-01-15 16:18 ` Hyman Rosen 2002-01-15 16:57 ` Darren New @ 2002-01-16 15:18 ` Dmitry A. Kazakov 2 siblings, 0 replies; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-16 15:18 UTC (permalink / raw) On Tue, 15 Jan 2002 10:41:58 -0500, "Matthew Heaney" <mheaney@on2.com> wrote: >> So I chose the lesser evil = mutexes with all that nasty problems of >> error recovery, you have pointed. I really do not like it. > >The language gives you primitives so that you can use to build in the >safety. There is no problem using semaphores in Ada95, if you follow >certain idioms. There is no harm in gotos if you know where you go. (:-)) I believe that like in case of gotos, use of semaphores should be minimized [if possible, of course]. Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-14 9:42 ` Dmitry A. Kazakov 2002-01-15 15:41 ` Matthew Heaney @ 2002-01-15 18:59 ` Nick Roberts 2002-01-16 15:05 ` Dmitry A. Kazakov 1 sibling, 1 reply; 41+ messages in thread From: Nick Roberts @ 2002-01-15 18:59 UTC (permalink / raw) "Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c429d1c.2624281@News.CIS.DFN.DE... > ... > Maybe I should describe the problem. There is a communication channel > implemented as a tagged type. The base type provides low-level I/O > operations. It has read and write tasks serving as monitors. The > problem is with the derived types [potentially an arbitrary number of > them]. They extend the base type and provide layered high-level > operations. I'm not too keen on this approach, because this way the higher levels do not 'hide away' the lower-level interface. Normally, it would be better for a distinctly higher-level interface to be based on a different type (not a derived one). > Some of them must lock the channel [either read or write > one]. A "natural" solution would be to extend monitors, but alas tasks > are not tagged. But the tagged type could contain a component of a task type. > An untagged solution requires an increasing number of > wrapping subroutines [thus run-time penalty]. I really don't understand why there should be a greater time penalty for this solution. I think possibly this is a misunderstanding that lies at the heart of your problems. > A genericity-based > solution produces a combinatoric explosion of packages used as the > parameters for other packages etc. They must be instantiated at the > library level. In other words it is a mess. I don't really see generics being directly relevant to the problem here. > So I chose the lesser evil = mutexes with all that nasy problems of > error recovery, you have pointed. I really do not like it. Please find below an outline skeleton of what a simple communications hierarchy might look like in Ada. It is merely a guess at what you need, and it even then is merely a suggestion of how to do it. ===== -- Abstract level: hardware devices in general with Ada.Finalization; package Devices is type Abstract_Device is abstract Limited_Controlled with private; procedure Reset (Device: access Abstract_Device) is abstract; ... private ... end Devices; ===== -- Abstract level: communications devices package Devices.Communications is type Byte_Array is ...; type Communication_Device is abstract new Abstract_Device with private; procedure Read (Transceiver: access Communication_Device; Data: out Byte_Array; Last: out Natural) is abstract; procedure Write (Transceiver: access Communication_Device; Data: in Byte_Array) is abstract; ... private ... end Devices.Communications; ===== -- Low concrete level: my particular communications hardware with Devices.Communications; with My_Stuff.Hardware; package My_Stuff.My_Comms is type My_Comms_Device_X is new Devices.Communications.Comuunications_Device with private; ... private task type Reception_Monitor is entry Read (Data: ...); entry Reset (Port: in My_Stuff.Hardware.Port_Number); entry Stop; end; task type Transmission_Monitor is entry Write (Data: ...); entry Reset (Port: in My_Stuff.Hardware.Port_Number); entry Stop; end; ... etc type Communications_Device is abstract new Abstract_Device with record R_Mon: Reception_Monitor; T_Mon: Transmission_Monitor; ... end record; procedure Reset (...); procedure Read (...); procedure Write (...); procedure Initialize (...); procedure Finalize (...); ... end My_Stuff.My_Comms; ===== -- Higher level: a communications facility for application programs with My_Stuff.My_Comms; with Ada.Streams.Stream_IO; package Utility.Intercom is type Socket_Type is limited private; procedure Open (Socket: in out Socket_Type; Name: in String; Form: in String := ""); procedure Reset (Socket: in out Socket_Type); procedure Close (Socket: in out Socket_Type); function Input_Stream (Socket: in Socket_Type) return Ada.Streams.Stream_IO.Stream_Access; function Output_Stream (Socket: in Socket_Type) return Ada.Streams.Stream_IO.Stream_Access; private ... end Utility.Intercom; ===== Note the embedding of two tasks, R_Mon and T_Mon, acting as input and output monitor respectively, as components within a tagged type. These tasks would be programmed to buffer the byte stream passing through them, so as to help 'smooth out the bumps' that you inevitably get as differences in the rate at which other tasks will produce or consume data. On a single-processor system, they will act like passive synchronisation mechanisms (but that's okay); on a multi-processor system they could actually help to increase the efficiency (speed) of the overall system, possibly quite dramatically, because they can be busily receiving and buffering data (R_Mon) and sending buffered data (T_Mon) while other tasks are busy doing other things. Also notice how I use successive derivations from a tagged type to evolve from an abstract level to a concrete one (where the theme is promises made and promises kept), but I use different types to evolve from a low level to a higher one (where the theme is the progressive hiding away of nasty nitty-gritty details). Hope this helps in some way. -- Best wishes, Nick Roberts ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-15 18:59 ` Nick Roberts @ 2002-01-16 15:05 ` Dmitry A. Kazakov 2002-01-16 18:30 ` Matthew Heaney ` (2 more replies) 0 siblings, 3 replies; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-16 15:05 UTC (permalink / raw) On Tue, 15 Jan 2002 18:59:16 -0000, "Nick Roberts" <nickroberts@adaos.worldonline.co.uk> wrote: >"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message >news:3c429d1c.2624281@News.CIS.DFN.DE... > >> ... >> Maybe I should describe the problem. There is a communication channel >> implemented as a tagged type. The base type provides low-level I/O >> operations. It has read and write tasks serving as monitors. The >> problem is with the derived types [potentially an arbitrary number of >> them]. They extend the base type and provide layered high-level >> operations. > >I'm not too keen on this approach, because this way the higher levels do not >'hide away' the lower-level interface. Normally, it would be better for a >distinctly higher-level interface to be based on a different type (not a >derived one). Well, you can derive from the base in the private part and expose operations you want to keep visible using wrappers implemented via renaming in the body. However, I always wished Ada having an ability to disallow primitive operations. Like: type Unordered is new Integer; function ">" (Left, Right : Unordered) is abstract; -- Disallow ">" >> Some of them must lock the channel [either read or write >> one]. A "natural" solution would be to extend monitors, but alas tasks >> are not tagged. > >But the tagged type could contain a component of a task type. Yes, and another problem as well. Finalize is called *after* all task components has been terminated. So when the object is being destroyed you cannot notify tasks about that. You must use pointers to tasks if you need a "prepare-to-die" notification. >> An untagged solution requires an increasing number of >> wrapping subroutines [thus run-time penalty]. > >I really don't understand why there should be a greater time penalty for >this solution. I think possibly this is a misunderstanding that lies at the >heart of your problems. Maybe the penalty is not very high, but writting dozens of wrappers is disgusting. >> A genericity-based >> solution produces a combinatoric explosion of packages used as the >> parameters for other packages etc. They must be instantiated at the >> library level. In other words it is a mess. > >I don't really see generics being directly relevant to the problem here. You can make package implementing some interface layer generic with a lower level package as the formal parameter. >> So I chose the lesser evil = mutexes with all that nasy problems of >> error recovery, you have pointed. I really do not like it. > >Please find below an outline skeleton of what a simple communications >hierarchy might look like in Ada. It is merely a guess at what you need, and >it even then is merely a suggestion of how to do it. [ driver example snipped ] The difference is that with a driver you always know the interface: Open/Read/Write/Close. The protocols we must implement are more complex. Additionally one should have an ability to replace any part of the hierarchy. For instance, low level: transport [like sockets vs. RS323], middle level: time synchronization protocol, high level client vs. server. Of course this ideal is reachless, so I am considering a which "local optimum" to take. (:-)) Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 15:05 ` Dmitry A. Kazakov @ 2002-01-16 18:30 ` Matthew Heaney 2002-01-17 8:58 ` Dmitry A. Kazakov 2002-01-16 20:28 ` Robert A Duff 2002-01-17 19:05 ` Nick Roberts 2 siblings, 1 reply; 41+ messages in thread From: Matthew Heaney @ 2002-01-16 18:30 UTC (permalink / raw) "Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c45865f.2709203@News.CIS.DFN.DE... > However, I always wished Ada having an ability to disallow primitive > operations. Like: > > type Unordered is new Integer; > function ">" (Left, Right : Unordered) is abstract; -- Disallow ">" I'm confused by your comment. Ada95 *can* do this. I do this to turn off predefined equality for a composite type whose component type is a generic formal non-limited type, so that I don't accidently use predefined array comparisons using the predefined equality for the formal type, which reemerges in the composite type, ie generic type T is private; package GP is type T_Array is array (Positive range <>) of T; function "=" (L, R : T_Array) return Boolean is abstract; end GP; Note that passing in an equality operator doesn't work here, because predefined equality reemerges *again* in the array declaration. So I just turn off array comparison, in order to prevent any accidents. > >But the tagged type could contain a component of a task type. > > Yes, and another problem as well. Finalize is called *after* all task > components has been terminated. So when the object is being destroyed > you cannot notify tasks about that. You must use pointers to tasks if > you need a "prepare-to-die" notification. But you can use a two-part termination: package P is type T (<>) is abstract tagged limited private; type T_Class_Access is access all T'Class; procedure Op (O : access T) is abstract; procedure Free (O : in out T_Class_Access); private type T_Task_Type (O : access T'Class) is entry E; end T_Task_Type; type T is abstract new Limited_Controlled with record T_Task : T_Task_Type (T'Access); end record; procedure Do_Free (O : access T); --private, primitive op end P; package body P is procedure Do_Free (O : access T) is begin null; end; procedure Free (O : in out T_Class_Access) is procedure Deallocate is new Ada.UD (T'Class, T_Class_Access); begin if O /= null then Do_Free (O); Deallocate (O); end if; end Free; ... end P; Each type in T'Class provides a factory function to construct instances. To destruct an instance, the client calls P.Free. Free is implemented by calling dispatching Do_Free, which notifies the object that it's about to be destroyed. It then calls deallocate, which triggers the Finalize call. Won't this work? ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 18:30 ` Matthew Heaney @ 2002-01-17 8:58 ` Dmitry A. Kazakov 2002-01-17 9:19 ` Lutz Donnerhacke 0 siblings, 1 reply; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-17 8:58 UTC (permalink / raw) On Wed, 16 Jan 2002 13:30:50 -0500, "Matthew Heaney" <mheaney@on2.com> wrote: >"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message >news:3c45865f.2709203@News.CIS.DFN.DE... >> However, I always wished Ada having an ability to disallow primitive >> operations. Like: >> >> type Unordered is new Integer; >> function ">" (Left, Right : Unordered) is abstract; -- Disallow ">" > >I'm confused by your comment. Ada95 *can* do this. > >I do this to turn off predefined equality for a composite type whose >component type is a generic formal non-limited type, so that I don't >accidently use predefined array comparisons using the predefined equality >for the formal type, which reemerges in the composite type, ie > >generic > type T is private; >package GP is > type T_Array is array (Positive range <>) of T; > function "=" (L, R : T_Array) return Boolean is abstract; >end GP; > >Note that passing in an equality operator doesn't work here, because >predefined equality reemerges *again* in the array declaration. So I just >turn off array comparison, in order to prevent any accidents. Yes it works (I have actually used that) for some cases. It does not work when the type is non-abstract and tagged. Sorry for my misleading comment. I believe it is worth to allow that for all types, i.e. to allow overriding a non-abstract subroutine with an abstract one. A dispatching call to a disallowed subprogram will then raise an exception [like when tags of arguments are different]. >> >But the tagged type could contain a component of a task type. >> >> Yes, and another problem as well. Finalize is called *after* all task >> components has been terminated. So when the object is being destroyed >> you cannot notify tasks about that. You must use pointers to tasks if >> you need a "prepare-to-die" notification. > >But you can use a two-part termination: > >package P is > type T (<>) is abstract tagged limited private; > type T_Class_Access is access all T'Class; > procedure Op (O : access T) is abstract; > procedure Free (O : in out T_Class_Access); >private > type T_Task_Type (O : access T'Class) is > entry E; > end T_Task_Type; > > type T is abstract new Limited_Controlled with record > T_Task : T_Task_Type (T'Access); > end record; > > procedure Do_Free (O : access T); --private, primitive op >end P; > > >package body P is > procedure Do_Free (O : access T) is > begin > null; > end; > > procedure Free (O : in out T_Class_Access) is > procedure Deallocate is new Ada.UD (T'Class, T_Class_Access); > begin > if O /= null then > Do_Free (O); > Deallocate (O); > end if; > end Free; >... >end P; > >Each type in T'Class provides a factory function to construct instances. To >destruct an instance, the client calls P.Free. Free is implemented by >calling dispatching Do_Free, which notifies the object that it's about to be >destroyed. It then calls deallocate, which triggers the Finalize call. > >Won't this work? Surely. But I prefer my solution because it hides nasty pointers in the implementation. Your variant exposes them to the user. Using C++ for a long time one becomes pointer-allergic. (:-)) In fact it is the *same* solution, i.e. pointers are inevitable. Would not it be better to add to Ada.Finalization procedure Postinitialize (..); -- All tasks are already running procedure Prefinalize (..); -- All tasks are still running ? Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-17 8:58 ` Dmitry A. Kazakov @ 2002-01-17 9:19 ` Lutz Donnerhacke 2002-01-17 10:42 ` Dmitry A. Kazakov 0 siblings, 1 reply; 41+ messages in thread From: Lutz Donnerhacke @ 2002-01-17 9:19 UTC (permalink / raw) * Dmitry A. Kazakov wrote: >I believe it is worth to allow that for all types, i.e. to allow >overriding a non-abstract subroutine with an abstract one. A >dispatching call to a disallowed subprogram will then raise an >exception [like when tags of arguments are different]. I strongly discourage from such a change. Class wide programming using abstract tagged types are used for the guarantee of a working interface. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-17 9:19 ` Lutz Donnerhacke @ 2002-01-17 10:42 ` Dmitry A. Kazakov 2002-01-17 10:55 ` Lutz Donnerhacke 0 siblings, 1 reply; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-17 10:42 UTC (permalink / raw) On Thu, 17 Jan 2002 09:19:59 +0000 (UTC), lutz@iks-jena.de (Lutz Donnerhacke) wrote: >* Dmitry A. Kazakov wrote: >>I believe it is worth to allow that for all types, i.e. to allow >>overriding a non-abstract subroutine with an abstract one. A >>dispatching call to a disallowed subprogram will then raise an >>exception [like when tags of arguments are different]. > >I strongly discourage from such a change. Class wide programming using >abstract tagged types are used for the guarantee of a working interface. There is no way to enforce LSP, if you aim at that. Just consider an overriding like: procedure Do_Something (...) is begin raise Program_Error; end Do_Something; And who said that derived types shall be LSP subtypes? What should we do with subtype Positive ...; then? Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-17 10:42 ` Dmitry A. Kazakov @ 2002-01-17 10:55 ` Lutz Donnerhacke 2002-01-17 15:30 ` Dmitry A. Kazakov 0 siblings, 1 reply; 41+ messages in thread From: Lutz Donnerhacke @ 2002-01-17 10:55 UTC (permalink / raw) * Dmitry A. Kazakov wrote: >* lutz@iks-jena.de (Lutz Donnerhacke) wrote: >>* Dmitry A. Kazakov wrote: >>>I believe it is worth to allow that for all types, i.e. to allow >>>overriding a non-abstract subroutine with an abstract one. A >>>dispatching call to a disallowed subprogram will then raise an >>>exception [like when tags of arguments are different]. >> >>I strongly discourage from such a change. Class wide programming using >>abstract tagged types are used for the guarantee of a working interface. > >There is no way to enforce LSP, if you aim at that. Just consider an >overriding like: > >procedure Do_Something (...) is >begin > raise Program_Error; >end Do_Something; Then the reason is on client site. Which is easy to detect. >And who said that derived types shall be LSP subtypes? The generic preamble requiring the correct instantiation. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-17 10:55 ` Lutz Donnerhacke @ 2002-01-17 15:30 ` Dmitry A. Kazakov 2002-01-17 16:29 ` Lutz Donnerhacke 0 siblings, 1 reply; 41+ messages in thread From: Dmitry A. Kazakov @ 2002-01-17 15:30 UTC (permalink / raw) On Thu, 17 Jan 2002 10:55:57 +0000 (UTC), lutz@iks-jena.de (Lutz Donnerhacke) wrote: >* Dmitry A. Kazakov wrote: >>* lutz@iks-jena.de (Lutz Donnerhacke) wrote: >>>* Dmitry A. Kazakov wrote: >>>>I believe it is worth to allow that for all types, i.e. to allow >>>>overriding a non-abstract subroutine with an abstract one. A >>>>dispatching call to a disallowed subprogram will then raise an >>>>exception [like when tags of arguments are different]. >>> >>>I strongly discourage from such a change. Class wide programming using >>>abstract tagged types are used for the guarantee of a working interface. >> >>There is no way to enforce LSP, if you aim at that. Just consider an >>overriding like: >> >>procedure Do_Something (...) is >>begin >> raise Program_Error; >>end Do_Something; > >Then the reason is on client site. Which is easy to detect. How it differentiates from: procedure Do_Something (..) is abstract; The later has an additional advantage that some calls to it can be detected at compile time. Consider disallowing methods as an additional constraint. Any constraint imposed by a derived type is incompatible with LSP. Should we then dismiss the idiom of constrained subtypes? >>And who said that derived types shall be LSP subtypes? > >The generic preamble requiring the correct instantiation. You must define the word correct. If correct = absolutely substitutable then of course LSP is required. Regards, Dmitry Kazakov ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-17 15:30 ` Dmitry A. Kazakov @ 2002-01-17 16:29 ` Lutz Donnerhacke 0 siblings, 0 replies; 41+ messages in thread From: Lutz Donnerhacke @ 2002-01-17 16:29 UTC (permalink / raw) * Dmitry A. Kazakov wrote: >How it differentiates from: >procedure Do_Something (..) is abstract; > >The later has an additional advantage that some calls to it can be >detected at compile time. You are right. I'm still feeling bad on this issue. ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 15:05 ` Dmitry A. Kazakov 2002-01-16 18:30 ` Matthew Heaney @ 2002-01-16 20:28 ` Robert A Duff 2002-01-17 19:05 ` Nick Roberts 2 siblings, 0 replies; 41+ messages in thread From: Robert A Duff @ 2002-01-16 20:28 UTC (permalink / raw) dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) writes: > However, I always wished Ada having an ability to disallow primitive > operations. Like: > > type Unordered is new Integer; > function ">" (Left, Right : Unordered) is abstract; -- Disallow ">" Umm, isn't that legal? And does it not disallow calls to ">"? Of course, it's a pain to list all the operations you *don't* want -- I'd rather it be the other way 'round. > The difference is that with a driver you always know the interface: > Open/Read/Write/Close. And ioctl, which is 37-to-50 different operations wrapped up in one, different for each driver. ;-) - Bob ^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: How to avoid unreferenced objects (mutexes etc) 2002-01-16 15:05 ` Dmitry A. Kazakov 2002-01-16 18:30 ` Matthew Heaney 2002-01-16 20:28 ` Robert A Duff @ 2002-01-17 19:05 ` Nick Roberts 2 siblings, 0 replies; 41+ messages in thread From: Nick Roberts @ 2002-01-17 19:05 UTC (permalink / raw) "Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c45865f.2709203@News.CIS.DFN.DE... > Well, you can derive from the base in the private part and expose > operations you want to keep visible using wrappers implemented via > renaming in the body. This suggests a less than ideal design, under normal circumstances (I gather yours are not). It sounds like each of your steps, from the lowest levels to the highest, is too small. Each step should, ideally, be a significant conceptual 'jump'. I do know that there exceptions to this idea (I have dealt with a few), but it is a good general rule. > However, I always wished Ada having an ability to disallow primitive > operations. Like: > > type Unordered is new Integer; > function ">" (Left, Right : Unordered) is abstract; -- Disallow ">" ... an advantage, of course, of using a completely new type (rather than a derived one). > Yes, and another problem as well. Finalize is called *after* all task > components has been terminated. So when the object is being destroyed > you cannot notify tasks about that. You must use pointers to tasks if > you need a "prepare-to-die" notification. I have never actually considered this problem, in practice, for the simple reason that I have always coded explicit Startup, Reset, and Shutdown operations into the interface, at every level. I simply don't - in my own mind - include this functionality in the duties of Finalize (but that's just my opinion, of course). Of course, my designs all stem from before 1995. > >> An untagged solution requires an increasing number of > >> wrapping subroutines [thus run-time penalty]. > > > >I really don't understand why there should be a greater time penalty for > >this solution. I think possibly this is a misunderstanding that lies at the > >heart of your problems. > > Maybe the penalty is not very high, but writting dozens of wrappers is > disgusting. I suggest you must 'bite the bullet' of re-implementing operations carried up from one level to the next. Each will tend to be of the form: procedure Flash_Big_Yellow_Light (Device: access Medium_Level_Device) is begin Flash_Big_Yellow_Light(Device.Low_Level_Part); end; This (3 lines) is instead of: procedure Flash_Big_Yellow_Light (Device: access Medium_Level_Device) renames Low_Level.Lights.Yellow.Flash; (1 or 2 lines), or inheritance (0 lines). I'm saying that, although 3 or 4 lines per subprogram is a bit of a pain (and I'm familiar with interfaces that have hundreds of operations), it's usually worth the effort in terms of getting a clean separation between the levels. This 'clean separation' typically pays dividends evetually, in terms of making things easier for the client (application) software, easing maintenance of the code at each level, and especially easing the exchanging of one implementation at a certain level with another (something important to you, do I gather?). From an efficiency point of view, heavy use of Inline is sometimes feasible. > >I don't really see generics being directly relevant to the problem here. > > You can make package implementing some interface layer generic with a > lower level package as the formal parameter. Yes, but how is this of significance to synchronisation mechanisms? Anyway, it does sound like you need a dynamic way of switching implementations (either by dispatching or using access-to-subprogram types). > [ driver example snipped ] > > The difference is that with a driver you always know the interface: > Open/Read/Write/Close. The protocols we must implement are more > complex. Additionally one should have an ability to replace any part > of the hierarchy. For instance, low level: transport [like sockets vs. > RS323], middle level: time synchronization protocol, high level client > vs. server. Of course this ideal is reachless, so I am considering a > which "local optimum" to take. (:-)) Yes, that does sound like a very unusual requirement. Best of luck! -- Best wishes, Nick Roberts ^ permalink raw reply [flat|nested] 41+ messages in thread
end of thread, other threads:[~2002-01-17 19:05 UTC | newest] Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2002-01-11 13:48 How to avoid unreferenced objects (mutexes etc) Dmitry A. Kazakov 2002-01-11 13:52 ` Lutz Donnerhacke 2002-01-11 14:47 ` Robert A Duff 2002-01-11 18:02 ` Jeffrey Carter 2002-01-11 19:40 ` Robert Dewar 2002-01-12 10:18 ` Martin Dowie 2002-01-14 8:54 ` Dmitry A. Kazakov 2002-01-12 1:11 ` Nick Roberts 2002-01-12 22:04 ` Matthew Heaney 2002-01-13 5:45 ` Nick Roberts 2002-01-13 8:21 ` tmoran 2002-01-13 16:12 ` Nick Roberts 2002-01-13 15:08 ` Simon Wright 2002-01-15 17:53 ` Nick Roberts 2002-01-13 16:51 ` Jeffrey Carter 2002-01-14 23:32 ` Matthew Heaney 2002-01-15 8:53 ` Dmitry A. Kazakov 2002-01-14 8:31 ` Jean-Pierre Rosen 2002-01-14 9:42 ` Dmitry A. Kazakov 2002-01-15 15:41 ` Matthew Heaney 2002-01-15 16:18 ` Hyman Rosen 2002-01-15 16:57 ` Darren New 2002-01-15 18:57 ` Matthew Heaney 2002-01-16 0:57 ` Darren New 2002-01-16 16:35 ` Stephen Leake 2002-01-16 18:07 ` Darren New 2002-01-16 23:18 ` Matthew Heaney 2002-01-16 23:04 ` Matthew Heaney 2002-01-17 0:21 ` Darren New 2002-01-16 15:18 ` Dmitry A. Kazakov 2002-01-15 18:59 ` Nick Roberts 2002-01-16 15:05 ` Dmitry A. Kazakov 2002-01-16 18:30 ` Matthew Heaney 2002-01-17 8:58 ` Dmitry A. Kazakov 2002-01-17 9:19 ` Lutz Donnerhacke 2002-01-17 10:42 ` Dmitry A. Kazakov 2002-01-17 10:55 ` Lutz Donnerhacke 2002-01-17 15:30 ` Dmitry A. Kazakov 2002-01-17 16:29 ` Lutz Donnerhacke 2002-01-16 20:28 ` Robert A Duff 2002-01-17 19:05 ` Nick Roberts
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox