Why does my MVAR freeze my code?
I am trying to create a simple server using Haskell. When clients connect to the server, the server records their address. Every n microseconds, the server sends out a broadcast.
Here is the server
data Server = Server {
sSocket :: Socket,
sPort :: Port,
sClients :: MVar [ClientAddress]
}
(Note the MVar, which allows clients from multiple threads.)
This is how the server is created
startServer port = withSocketsDo $ do
socket <- listenOn $ PortNumber $ fromIntegral port
clients <- newEmptyMVar
let server = Server socket port clients
forkIO $ forever $ accept socket >>= forkIO . (handleClientRequest server)
forever $ updateClients server 1000000
The server is using its own thread and the other is using another. The forked stream handles any incoming client requests
handleClientRequest server client = do
clients <- takeMVar $ sClients server
putMVar (sClients server) (client : clients)
and the broadcast is sent using the function updateClients
updateClients server frequency = do
putStrLn "1"
clients <- (takeMVar $ sClients server)
putStrLn "2"
putStrLn $ show $ length clients
threadDelay frequency
The problem I am running into is that "2" never prints to the screen. I believe this is because the line takeMVar
in updateClients
never ends.
Why freeze it?
source to share
You start with an empty MVar, so the takeMVar
blocks are forever. Try using newMVar []
instead newEmptyMVar
, maybe like this:
startServer port = withSocketsDo $ do
socket <- listenOn $ PortNumber $ fromIntegral port
clients <- newMVar []
let server = Server socket port clients
forkIO $ forever $ accept socket >>= forkIO . (handleClientRequest server)
forever $ updateClients server 1000000
Now the MVar is always full, except when actually changed by the client.
When using MVar to protect a critical section, it is helpful to think about what a normal state is; in this case it is a list of clients. The only time the MVar should be empty is when the client actually modifies the state, so if you can use modifyMVar
and customize the initial state, your code should be light-hearted.
Also, if you can use withMVar
instead takeMVar/putMVar
, you should do so because it leaves the MVar in a consistent state if an asynchronous exception is thrown.
source to share