Concurrency problems with Handle
Arie Peterson
ariep at xs4all.nl
Sun Dec 21 01:33:08 EST 2003
Hello,
After many hours of vain debugging, I suspect that GHC might not do what I
think it should do.
I made a module 'Telnet', exporting one function 'telnet', which takes a
handle (an established connection) and returns an input channel, an output
channel and a function to close the connection and clean up.
The 'telnet' function creates two channels (input and output), one MVar and
two threads:
* One thread takes chars from the output channel and writes them into
the handle.
* The other thread reads chars from the handle and puts them in the
input channel.
* The MVar regulates writing access to the handle: the reader thread
sometimes has to respond to telnet commands, but those responses must not
be interspersed with ordinary data.
Now, if I let one side of the connection send some characters:
sequence_ $ take 10 $ repeat $ (writeChan outputChan 's')
and I let the other side consume them:
sequence_ $ take 10 $ repeat $ (putChar =<< readChan inputChan)
then the characters are sent and received correctly, but the receiving side
shows the characters only after the connection is closed.
If I let both sides send and consume characters in turn, like this:
sequence_ $ take 10 $ repeat $ threadDelay 250 >> (writeChan outputChan
's') >> (putChar =<< readChan inputChan)
and
sequence_ $ take 10 $ repeat $ (putChar =<< readChan inputChan) >>
threadDelay 250 >> (writeChan outputChan 'c')
, one character is sent, and the other end waits indefinitely before
sending his character.
Maybe some buffering mechanism is holding my characters?
I already tried hFlush and setting buffering to NoBuffering on all handles.
Could it have anything to with this?
"One final note: the aaaa bbbb example may not work too well on GHC (see
Scheduling, above), due to the locking on a Handle. Only one thread may
hold the lock on a Handle at any one time, so if a reschedule happens while
a thread is holding the lock, the other thread won't be able to run. The
upshot is that the switch from aaaa to bbbbb happens infrequently. It can
be improved by lowering the reschedule tick period. We also have a patch
that causes a reschedule whenever a thread waiting on a lock is woken up,
but haven't found it to be useful for anything other than this example :-)"
(http://haskell.org/ghc/docs/latest/html/libraries/base/Control.Concurrent.html#11)
I tried all this both in compiled and interactive mode with GHC 6.0 and 6.2
on Windows 2000.
Thanks for your time.
Arie Peterson
Telnet.hs, with many superfluous debugging output:
###
module Telnet (telnet) where
import Control.Concurrent (forkIO,killThread)
import Control.Concurrent.Chan (Chan,newChan,readChan,writeChan)
import Control.Concurrent.MVar (MVar,newMVar,takeMVar,putMVar)
import System.IO (Handle,hGetChar,hPutChar)
telnet :: Handle -> IO (Chan Char,Chan Char,IO ())
telnet handle = do
inputChan <- newChan
outputChan <- newChan
writing <- newMVar ()
readerId <- forkIO $ reader handle inputChan writing
writerId <- forkIO $ writer handle outputChan writing
return
(
inputChan,
outputChan,
do -- function to close connection
takeMVar writing
hPutChar handle '\255' -- IAC
hPutChar handle '\244' -- IP (Interrupt Process)
killThread readerId
killThread writerId
)
reader :: Handle -> Chan Char -> MVar () -> IO ()
reader handle inputChan writing = sequence_ . repeat $ do
putStrLn "Telnet.reader: waiting for char in handle"
c <- hGetChar handle
putStrLn ("Telnet.reader: received " ++ show c)
case c of
'\255' -> hGetChar handle >>= \c -> putStrLn ("Telnet.reader: received
" ++ show c) >> case c of
'\255' -> writeChan inputChan '\255' -- escaped IAC
'\254' -> respond '\253' -- received DONT, send WONT
'\253' -> respond '\254' -- received WONT, send DONT
'\252' -> respond '\253' -- received DO, send WONT
'\251' -> respond '\254' -- received WILL, send DONT
_ -> return () -- received unknown command, ignore
c -> writeChan inputChan c
where
respond c = do
d <- hGetChar handle
putStrLn ("Telnet.reader: received " ++ show d ++ ", responding")
takeMVar writing
hPutChar handle '\255'
hPutChar handle c
hPutChar handle d
putMVar writing ()
writer :: Handle -> Chan Char -> MVar () -> IO ()
writer handle outputChan writing = sequence_ . repeat $ do
putStrLn "Telnet.writer: waiting for character in chan"
c <- readChan outputChan
putStrLn ("Telnet.writer: going to write: " ++ show c)
takeMVar writing
case c of
'\255' -> hPutChar handle '\255' >> hPutChar handle '\255' -- escaped IAC
_ -> hPutChar handle c
putMVar writing ()
putStrLn ("Telnet.writer: written: " ++ show c)
More information about the Glasgow-haskell-users
mailing list