
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)?) Another idea is to sClose s in another thread. This makes accept throw an IO exception, which breaks the loop. Yay! But it introduces another race condition: If a third thread creates a socket between sClose and accept, the underlying file descriptor of s may get reused, leading to silently wrong behavior because accept listens on the wrong socket now. My question is: How can I use accept in a loop so that I can safely stop it from another thread? Lukas

On 7/6/07, Lukas Mai
Hello, cafe!
Have you been reading my mind? See the other recent Cafe thread (um, titled something about System.Exit). Here's my solution: acceptLoop sok reqChan = do req <- Network.Socket.accept sok atomically (writeTChan reqChan req) acceptLoop sok reqChan mainLoop reqChan quitVar liveOpCountVar = do action <- atomically (quitNow `orElse` getReq) case action of Nothing -> return () Just (reqSok,reqAddr) -> do atomically $ do liveOpCount <- readTVar liveOpCountVar writeTVar liveOpCountVar (liveOpCount + 1) forkIO (doSession reqSok reqAddr quitVar liveOpCountVar) mainLoop reqChan quitVar liveOpCountVar where quitNow = do q <- readTVar quitVar case q of True -> return Nothing False -> retry getReq = do req <- readTChan reqChan return (Just req) doit sok = do reqChan <- atomically newTChan quitVar <- atomically (newTVar False) liveOpCountVar <- atomically (newTVar 0) forkIO (acceptLoop sok reqChan) mainLoop reqChan quitVar liveOpCountVar atomically $ do liveOpCount <- readTVar liveOpCountVar if liveOpCount > 0 then retry else return () Although doSession is not included, obviously when you want to quit, something in doSession should set quitVar to True. Also, as suggested elsewhere, doSession should involve a "finally" clauses to make sure the live op count gets decremented. T. -- Dr Thomas Conway drtomc@gmail.com Silence is the perfectest herald of joy: I were but little happy, if I could say how much.

drtomc:
On 7/6/07, Lukas Mai
wrote: Hello, cafe!
Have you been reading my mind? See the other recent Cafe thread (um, titled something about System.Exit).
Here's my solution:
... If you've got small examples illustrating how to use the various concurrency abstractions, feel free to add them to: http://haskell.org/haskellwiki/Concurrency_demos So we can build up a large body of examples for all the libs and tricks out there. -- don

Am Freitag, 6. Juli 2007 02:13 schrieb Thomas Conway:
Here's my solution:
acceptLoop sok reqChan = do req <- Network.Socket.accept sok atomically (writeTChan reqChan req) acceptLoop sok reqChan
mainLoop reqChan quitVar liveOpCountVar = do action <- atomically (quitNow `orElse` getReq) case action of Nothing -> return () Just (reqSok,reqAddr) -> do atomically $ do liveOpCount <- readTVar liveOpCountVar writeTVar liveOpCountVar (liveOpCount + 1) forkIO (doSession reqSok reqAddr quitVar liveOpCountVar) mainLoop reqChan quitVar liveOpCountVar where quitNow = do q <- readTVar quitVar case q of True -> return Nothing False -> retry
getReq = do req <- readTChan reqChan return (Just req)
doit sok = do reqChan <- atomically newTChan quitVar <- atomically (newTVar False) liveOpCountVar <- atomically (newTVar 0) forkIO (acceptLoop sok reqChan) mainLoop reqChan quitVar liveOpCountVar atomically $ do liveOpCount <- readTVar liveOpCountVar if liveOpCount > 0 then retry else return ()
Although doSession is not included, obviously when you want to quit, something in doSession should set quitVar to True. Also, as suggested elsewhere, doSession should involve a "finally" clauses to make sure the live op count gets decremented.
I don't see how this solves the problem. AFAICS acceptLoop never returns and sok is never closed. On the other hand, my program doesn't need a liveOpCount because the subthreads take care of themselves. It's just the accept loop I need to break somehow. Lukas

On 7/6/07, Lukas Mai
I don't see how this solves the problem. AFAICS acceptLoop never returns and sok is never closed. On the other hand, my program doesn't need a liveOpCount because the subthreads take care of themselves. It's just the accept loop I need to break somehow.
Well, it works because the sub-thread dies when the program exits, so the socket gets closed then. T. -- Dr Thomas Conway drtomc@gmail.com Silence is the perfectest herald of joy: I were but little happy, if I could say how much.

Am Freitag, 6. Juli 2007 04:15 schrieb Thomas Conway:
On 7/6/07, Lukas Mai
wrote: I don't see how this solves the problem. AFAICS acceptLoop never returns and sok is never closed. On the other hand, my program doesn't need a liveOpCount because the subthreads take care of themselves. It's just the accept loop I need to break somehow.
Well, it works because the sub-thread dies when the program exits, so the socket gets closed then.
But I don't want to exit the program, I just want to close the socket. (There may be more than one such loop running, all independent from each other.) My current solution looks like this: killSocket :: Socket -> IO () killSocket s = do nul <- openFd "/dev/null" WriteOnly Nothing defaultFileFlags dupTo nul (Fd $ fdSocket s) closeFd nul This makes accept blow up because s no longer refers to a socket. The resulting exception is caught and the handler closes s, avoiding a file descriptor leak. While this works fine, it seems like a kludge. I'm open to suggestions on how to improve this. :-) Lukas

On 7/5/07, 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.
What about using "bracketOnError"? nextClient s = bracketOnError (fst . accept s) sClose spin = do nextClient s (\s' -> forkIO $ dealWith s') spin If "bracketOnError" leaks the resource in the event of an exception, then it needs to be fixed. -- Rich JID: rich@neswold.homeunix.net AIM: rnezzy

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

On 7/7/07, Lukas Mai
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
I think the `finally` portion should be done in the forked process context. Otherwise once the process is forked, the socket gets closed by the parent process. Something more along the lines of: unblock (forkIO $ doStuff t `finally` sClose t) -- Rich JID: rich@neswold.homeunix.net AIM: rnezzy
participants (4)
-
dons@cse.unsw.edu.au
-
Lukas Mai
-
Rich Neswold
-
Thomas Conway