Using TChan with Timeout

前端 未结 3 665
谎友^
谎友^ 2021-02-14 19:49

I have a TChan as input for a thread which should behave like this:

If sombody writes to the TChan within a specific time, the content should be retrieved. If there is n

相关标签:
3条回答
  • 2021-02-14 20:07

    The thumb rule of concurrency is: if adding a sleep in some point inside an IO action matters, your program is not safe.

    To understand why the code timeout 1000000 $ atomically $ readTChan pktChannel does not work, consider the following alternative implementation of atomically:

    atomically' :: STM a -> IO a
    atomically' action = do
      result <- atomically action
      threadDelay someTimeAmount
      return result
    

    The above is equal to atomically, but for an extra innocent delay. Now it is easy to see that if timeout kills the thread during the threadDelay, the atomic action has completed (consuming a message from the channel), yet timeout will return Nothing.

    A simple fix to timeout n $ atomically ... could be the following

    smartTimeout :: Int -> STM a -> IO (Maybe a)
    smartTimeout n action = do
       v <- atomically $ newEmptyTMvar
       _ <- timeout n $ atomically $ do
              result <- action
              putTMvar v result
       atomically $ tryTakeTMvar v
    

    The above uses an extra transactional variable v to do the trick. The result value of the action is stored into v inside the same atomic block in which the action is run. The return value of timeout is not trusted, since it does not tell us if action was run or not. After that, we check the TMVar v, which will be full if and only if action was run.

    0 讨论(0)
  • 2021-02-14 20:08

    Instead of TChan a, use TChan (Maybe a) . Your normal producer (of x) now writes Just x. Fork an extra "ticking" process that writes Nothing to the channel (every x seconds). Then have a reader for the channel, and abort if you get two successive Nothing. This way, you avoid exceptions, which may cause data to get lost in your case (but I am not sure).

    0 讨论(0)
  • 2021-02-14 20:26

    Use registerDelay, an STM function, to signal a TVar when the timeout is reached. You can then use the orElse function or the Alternative operator <|> to select between the next TChan value or the timeout.

    import Control.Applicative
    import Control.Monad
    import Control.Concurrent
    import Control.Concurrent.STM
    import System.Random
    
    -- write random values after a random delay
    packetWriter :: Int -> TChan Int -> IO ()
    packetWriter maxDelay chan = do
      let xs = randomRs (10000 :: Int, maxDelay + 50000) (mkStdGen 24036583)
      forM_ xs $ \ x -> do
        threadDelay x
        atomically $ writeTChan chan x
    
    -- block (retry) until the delay TVar is set to True
    fini :: TVar Bool -> STM ()
    fini = check <=< readTVar
    
    -- Read the next value from a TChan or timeout
    readTChanTimeout :: Int -> TChan a -> IO (Maybe a)
    readTChanTimeout timeoutAfter pktChannel = do
      delay <- registerDelay timeoutAfter
      atomically $
            Just <$> readTChan pktChannel
        <|> Nothing <$ fini delay
    
    -- | Print packets until a timeout is reached
    readLoop :: Show a => Int -> TChan a -> IO ()
    readLoop timeoutAfter pktChannel = do
      res <- readTChanTimeout timeoutAfter pktChannel
      case res of
        Nothing -> putStrLn "timeout"
        Just val -> do
          putStrLn $ "packet: " ++ show val
          readLoop timeoutAfter pktChannel
    
    main :: IO ()
    main = do
      let timeoutAfter = 1000000
    
      -- spin up a packet writer simulation
      pktChannel <- newTChanIO
      tid <- forkIO $ packetWriter timeoutAfter pktChannel
    
      readLoop timeoutAfter pktChannel
    
      killThread tid
    
    0 讨论(0)
提交回复
热议问题