Proposal: replace readMVar with atomicReadMVar, breaking BC

GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters). atomicReadMVar behaves differently from readMVar. The key differences are: - An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now. - An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently: m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1 It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful. The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully. Thanks, Edward

Good! Are the new semantics clearly documented? Simon | -----Original Message----- | From: libraries-bounces@haskell.org [mailto:libraries- | bounces@haskell.org] On Behalf Of Edward Z. Yang | Sent: 10 July 2013 10:20 | To: libraries | Subject: Proposal: replace readMVar with atomicReadMVar, breaking BC | | GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you | to read MVars without first taking and then putting back (removing a | nasty race where readMVar only works properly when there are no waiting | putters). | | atomicReadMVar behaves differently from readMVar. The key | differences are: | | - An atomicReadMVar op never causes an MVar to become "empty" at any | point. | This means less programs deadlock now. | | - An blocked atomicReadMVar is always served by the *first* putMVar | to arrive (it cuts to the head of the queue), whereas readMVar | obeyed the FIFO ordering. This means this program behaves | differently: | | m <- newEmptyMVar | forkIO $ takeMVar m | threadDelay 100 | forkIO $ print =<< readMVar m {- atomicReadMVar m -} | threadDelay 100 | putMVar m 1 | putMVar m 2 | -- readMVar: outputs 2 | -- atomicReadMVar: outputs 1 | | It actually would not be too difficult to add support for | atomicReadMVar | which *does* respect the FIFO ordering but I don't have a sense | when | that would be useful. | | The general feeling Simon and I have is that everyone really wanted | to make believe readMVar was atomicReadMVar, and so maybe we should | break BC and make readMVar do the right thing. But it is probably | worth some discussion, and I entreat you to think about the second | point more carefully. | | Thanks, | Edward | | _______________________________________________ | Libraries mailing list | Libraries@haskell.org | http://www.haskell.org/mailman/listinfo/libraries

I only realized that the ordering semantics were different when I was writing this email. I'll go add them to the Haddock docs. Excerpts from Simon Peyton-Jones's message of Wed Jul 10 03:47:27 -0700 2013:
Good! Are the new semantics clearly documented?
Simon
| -----Original Message----- | From: libraries-bounces@haskell.org [mailto:libraries- | bounces@haskell.org] On Behalf Of Edward Z. Yang | Sent: 10 July 2013 10:20 | To: libraries | Subject: Proposal: replace readMVar with atomicReadMVar, breaking BC | | GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you | to read MVars without first taking and then putting back (removing a | nasty race where readMVar only works properly when there are no waiting | putters). | | atomicReadMVar behaves differently from readMVar. The key | differences are: | | - An atomicReadMVar op never causes an MVar to become "empty" at any | point. | This means less programs deadlock now. | | - An blocked atomicReadMVar is always served by the *first* putMVar | to arrive (it cuts to the head of the queue), whereas readMVar | obeyed the FIFO ordering. This means this program behaves | differently: | | m <- newEmptyMVar | forkIO $ takeMVar m | threadDelay 100 | forkIO $ print =<< readMVar m {- atomicReadMVar m -} | threadDelay 100 | putMVar m 1 | putMVar m 2 | -- readMVar: outputs 2 | -- atomicReadMVar: outputs 1 | | It actually would not be too difficult to add support for | atomicReadMVar | which *does* respect the FIFO ordering but I don't have a sense | when | that would be useful. | | The general feeling Simon and I have is that everyone really wanted | to make believe readMVar was atomicReadMVar, and so maybe we should | break BC and make readMVar do the right thing. But it is probably | worth some discussion, and I entreat you to think about the second | point more carefully. | | Thanks, | Edward | | _______________________________________________ | Libraries mailing list | Libraries@haskell.org | http://www.haskell.org/mailman/listinfo/libraries

Clever me, I already described this behavior: -- |Atomically read the contents of an 'MVar'. If the 'MVar' is -- currently empty, 'atomicReadMVar' will wait until its full. -- 'atomicReadMVar' is guaranteed to receive the next 'putMVar'. -- -- 'atomicReadMVar' is multiple-wakeup, so when multiple readers are -- blocked on an 'MVar', all of them are woken up at the same time. Emphasis on "guaranteed to receive the next 'putMVar' :) Edward Excerpts from Edward Z. Yang's message of Wed Jul 10 10:08:17 -0700 2013:
I only realized that the ordering semantics were different when I was writing this email. I'll go add them to the Haddock docs.
Excerpts from Simon Peyton-Jones's message of Wed Jul 10 03:47:27 -0700 2013:
Good! Are the new semantics clearly documented?
Simon
| -----Original Message----- | From: libraries-bounces@haskell.org [mailto:libraries- | bounces@haskell.org] On Behalf Of Edward Z. Yang | Sent: 10 July 2013 10:20 | To: libraries | Subject: Proposal: replace readMVar with atomicReadMVar, breaking BC | | GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you | to read MVars without first taking and then putting back (removing a | nasty race where readMVar only works properly when there are no waiting | putters). | | atomicReadMVar behaves differently from readMVar. The key | differences are: | | - An atomicReadMVar op never causes an MVar to become "empty" at any | point. | This means less programs deadlock now. | | - An blocked atomicReadMVar is always served by the *first* putMVar | to arrive (it cuts to the head of the queue), whereas readMVar | obeyed the FIFO ordering. This means this program behaves | differently: | | m <- newEmptyMVar | forkIO $ takeMVar m | threadDelay 100 | forkIO $ print =<< readMVar m {- atomicReadMVar m -} | threadDelay 100 | putMVar m 1 | putMVar m 2 | -- readMVar: outputs 2 | -- atomicReadMVar: outputs 1 | | It actually would not be too difficult to add support for | atomicReadMVar | which *does* respect the FIFO ordering but I don't have a sense | when | that would be useful. | | The general feeling Simon and I have is that everyone really wanted | to make believe readMVar was atomicReadMVar, and so maybe we should | break BC and make readMVar do the right thing. But it is probably | worth some discussion, and I entreat you to think about the second | point more carefully. | | Thanks, | Edward | | _______________________________________________ | Libraries mailing list | Libraries@haskell.org | http://www.haskell.org/mailman/listinfo/libraries

Is there a reason why as I programmer I should prefer the non-FIFO
semantics, or is it implemented that way for efficiency?
Tom
El Jul 10, 2013, a las 5:20, "Edward Z. Yang"
GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

Excerpts from Tom Murphy's message of Wed Jul 10 05:45:36 -0700 2013:
Is there a reason why as I programmer I should prefer the non-FIFO semantics, or is it implemented that way for efficiency?
Timely delivery of reads. Ordinary takeMVar is FIFO for fairness reasons: so long as an MVar is not held indefinitely, all takeMVars will be serviced. For reads, we can service them immediately without worrying about fairness, since they don't block anyone. It is literally trivial to do either FIFO and non-FIFO implementation wise. Edward

+1 definitely.
Tom
El Jul 10, 2013, a las 13:07, "Edward Z. Yang"
Excerpts from Tom Murphy's message of Wed Jul 10 05:45:36 -0700 2013:
Is there a reason why as I programmer I should prefer the non-FIFO semantics, or is it implemented that way for efficiency?
Timely delivery of reads. Ordinary takeMVar is FIFO for fairness reasons: so long as an MVar is not held indefinitely, all takeMVars will be serviced. For reads, we can service them immediately without worrying about fairness, since they don't block anyone.
It is literally trivial to do either FIFO and non-FIFO implementation wise.
Edward

+1 for changing readMVar to be atomic, but we should probably mention the change in the doc for readMVar, something like the following (perhaps in fewer words): -- | ... -- -- /Compatibility note:/ prior to base 4.7, 'readMVar' was a combination -- of 'takeMVar' and 'putMVar', resulting in two drawbacks: -- -- * 'readMVar' was not atomic in the presence of multiple producers. -- Between taking the value and putting it back, another thread -- calling 'putMVar' could win, causing 'readMVar' to block after -- retrieving a value. -- -- * 'readMVar' was not multiple wakeup, meaning each consumer had to -- wake up the next.

On 10/07/13 13:45, Tom Murphy wrote:
Is there a reason why as I programmer I should prefer the non-FIFO semantics, or is it implemented that way for efficiency?
You might prefer the FIFO semantics if you want to take advantage of the fact that the RTS implements a double-ended queue of threads in such a way that threads can be removed from the middle in O(1) time if they receive an exception from throwTo. However, the only person I know that does this sort of thing is Chris Kuklewicz, and I can never understand his code. :-) I expect the readMVar-jumps-to-the-front-of-the-queue semantics will be more useful in most cases. We could have both, but I doubt it's worth it. Cheers, Simon
Tom
El Jul 10, 2013, a las 5:20, "Edward Z. Yang"
escribió: GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

Edward Z. Yang wrote:
GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back
Great, thanks a lot!
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing.
I agree completely. In my experience, the current non-atomic behaviour of readMVar has only lead to confusion; I don't see any use for this behaviour. Besides, if anybody really wants that behaviour they should probably invoke takeMVar and putMVar separately and add a comment or two. I also agree with the reasoning behind processing atomicReadMVar eagerly. Best regards, Bertram

After reading some of the background and seeing this, +1 (Frankly, I
didn't even know before this that readMVar had the race condition you
described, and from my point of view, it should simply be fixed!)
Also: If you add a small release note blurb for the changes `base`,
that would be wonderful too. :)
On Wed, Jul 10, 2013 at 4:20 AM, Edward Z. Yang
GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries
-- Regards, Austin - PGP: 4096R/0x91384671

Where do these release notes live? Excerpts from Austin Seipp's message of Wed Jul 10 12:28:48 -0700 2013:
After reading some of the background and seeing this, +1 (Frankly, I didn't even know before this that readMVar had the race condition you described, and from my point of view, it should simply be fixed!)
Also: If you add a small release note blurb for the changes `base`, that would be wonderful too. :)
On Wed, Jul 10, 2013 at 4:20 AM, Edward Z. Yang
wrote: GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

Oh, found it. Excerpts from Austin Seipp's message of Wed Jul 10 12:28:48 -0700 2013:
After reading some of the background and seeing this, +1 (Frankly, I didn't even know before this that readMVar had the race condition you described, and from my point of view, it should simply be fixed!)
Also: If you add a small release note blurb for the changes `base`, that would be wonderful too. :)
On Wed, Jul 10, 2013 at 4:20 AM, Edward Z. Yang
wrote: GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

+1 from me. I don't think anyone really wants the current readMVar
semantics anyway, and if somebody really does they can define their own
non-atomic readMVar. Thanks for working on this!
On Thu, Jul 11, 2013 at 4:38 AM, Edward Z. Yang
Oh, found it.
Excerpts from Austin Seipp's message of Wed Jul 10 12:28:48 -0700 2013:
After reading some of the background and seeing this, +1 (Frankly, I didn't even know before this that readMVar had the race condition you described, and from my point of view, it should simply be fixed!)
Also: If you add a small release note blurb for the changes `base`, that would be wonderful too. :)
On Wed, Jul 10, 2013 at 4:20 AM, Edward Z. Yang
wrote: GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

-----Original message----- From: "Edward Z. Yang"
Sent: 10 Jul 2013, 02:20 GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
That is great, I have been missing such a primitive.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
+1 from me. Even though it is a BC and a change in semantics, it is a change to the behaviour people expect or hope for when using readMVar. Cheers, Milan

On Wed, Jul 10, 2013 at 02:20:14AM -0700, Edward Z. Yang wrote:
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing.
I don't think this breaks backwards compatibility. The haddocks say: Fairness No thread can be blocked indefinitely on an MVar unless another thread holds that MVar indefinitely. One usual implementation of this fairness guarantee is that threads blocked on an MVar are served in a first-in-first-out fashion, but this is not guaranteed in the semantics. Thanks Ian -- Ian Lynagh, Haskell Consultant Well-Typed LLP, http://www.well-typed.com/

+1
I agree that changing this is more likely to give users the functionality
that they expected from the API in the first place.
On Wed, Jul 10, 2013 at 4:20 AM, Edward Z. Yang
GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

On 2013-07-10 11:20, Edward Z. Yang wrote:
GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
+1 I avoided readMVar for that reason in the past, effectively doing the take/put manually. I agree with others that the atomic operation is the desired behavior. David

OK, it looks like the consensus is to change the old readMVar. I will go and implement this. Edward Excerpts from Edward Z. Yang's message of Wed Jul 10 02:20:14 -0700 2013:
GHC HEAD recently got a new primitive: atomicReadMVar#, which allows you to read MVars without first taking and then putting back (removing a nasty race where readMVar only works properly when there are no waiting putters).
atomicReadMVar behaves differently from readMVar. The key differences are:
- An atomicReadMVar op never causes an MVar to become "empty" at any point. This means less programs deadlock now.
- An blocked atomicReadMVar is always served by the *first* putMVar to arrive (it cuts to the head of the queue), whereas readMVar obeyed the FIFO ordering. This means this program behaves differently:
m <- newEmptyMVar forkIO $ takeMVar m threadDelay 100 forkIO $ print =<< readMVar m {- atomicReadMVar m -} threadDelay 100 putMVar m 1 putMVar m 2 -- readMVar: outputs 2 -- atomicReadMVar: outputs 1
It actually would not be too difficult to add support for atomicReadMVar which *does* respect the FIFO ordering but I don't have a sense when that would be useful.
The general feeling Simon and I have is that everyone really wanted to make believe readMVar was atomicReadMVar, and so maybe we should break BC and make readMVar do the right thing. But it is probably worth some discussion, and I entreat you to think about the second point more carefully.
Thanks, Edward
participants (13)
-
amindfv@gmail.com
-
Austin Seipp
-
Bertram Felgenhauer
-
David Luposchainsky
-
Edward Kmett
-
Edward Z. Yang
-
Ian Lynagh
-
Joey Adams
-
John Lato
-
Milan Straka
-
Simon Marlow
-
Simon Peyton-Jones
-
Tom Murphy