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?

+3


source to share


1 answer


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.

+5


source







All Articles