
Am Freitag, 6. Juli 2007 01:24 schrieb Lukas Mai:
Hello, cafe!
I have the following code (paraphrased):
... forkIO spin ... spin = do (t, _) <- accept s -- (*) forkIO $ dealWith t -- (**) spin
My problem is that I want to stop spin from another thread. The "obvious" solution would be to throw it an exception. However, that leaks a socket (t) if the exception arrives between (*) and (**). I could wrap the whole thing in block, but from looking at the source of Network.Socket it seems that accept itself is not exception safe; so no matter what I do, I can't use asynchronous exceptions to make spin exit.
(Is this actually true? Should accept be fixed (along with a lot of other library functions)?)
Answering myself: I now think that the above isn't true. :-) Quoting Control.Exception:
Some operations are interruptible, which means that they can receive asynchronous exceptions even in the scope of a block. Any function which may itself block is defined as interruptible; this includes takeMVar (but not tryTakeMVar), and most operations which perform some I/O with the outside world. The reason for having interruptible operations is so that we can write things like
block ( a <- takeMVar m catch (unblock (...)) (\e -> ...) )
if the takeMVar was not interruptible, then this particular combination could lead to deadlock, because the thread itself would be blocked in a state where it can't receive any asynchronous exceptions. With takeMVar interruptible, however, we can be safe in the knowledge that the thread can receive exceptions right up until the point when the takeMVar succeeds. Similar arguments apply for other interruptible operations like openFile.
If I understand this correctly, spin should be written as: spin = do block $ do (t, _) <- accept s unblock (forkIO $ doStuff t) `finally` sClose t spin Now t can't leak from spin because it's protected by block, while the underlying accept() syscall is still interruptible by asynchronous exceptions. Lukas