diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,7 @@
+## 0.1.6
+
+- Fixing the race condition of `timeout`.
+
 ## 0.1.5
 
 - Catching up "tls" v1.9.0.
diff --git a/Network/QUIC/Client/Reader.hs b/Network/QUIC/Client/Reader.hs
--- a/Network/QUIC/Client/Reader.hs
+++ b/Network/QUIC/Client/Reader.hs
@@ -44,7 +44,7 @@
             wait
     loop = do
         ito <- readMinIdleTimeout conn
-        mbs <- timeout ito $
+        mbs <- timeout ito "readeClient" $
 #if defined(mingw32_HOST_OS)
           windowsThreadBlockHack $
 #endif
@@ -118,7 +118,7 @@
 
 controlConnection' :: Connection -> ConnectionControl -> IO Bool
 controlConnection' conn ChangeServerCID = do
-    mn <- timeout (Microseconds 1000000) $ waitPeerCID conn -- fixme
+    mn <- timeout (Microseconds 1000000) "controlConnection' 1" $ waitPeerCID conn -- fixme
     case mn of
       Nothing              -> return False
       Just (CIDInfo n _ _) -> do
@@ -133,7 +133,7 @@
     rebind conn $ Microseconds 5000 -- nearly 0
     return True
 controlConnection' conn ActiveMigration = do
-    mn <- timeout (Microseconds 1000000) $ waitPeerCID conn -- fixme
+    mn <- timeout (Microseconds 1000000) "controlConnection' 2" $ waitPeerCID conn -- fixme
     case mn of
       Nothing  -> return False
       mcidinfo -> do
diff --git a/Network/QUIC/Client/Run.hs b/Network/QUIC/Client/Run.hs
--- a/Network/QUIC/Client/Run.hs
+++ b/Network/QUIC/Client/Run.hs
@@ -58,8 +58,7 @@
 runClient :: ClientConfig -> (Connection -> IO a) -> Bool -> VersionInfo -> IO a
 runClient conf client0 isICVN verInfo = do
     E.bracket open clse $ \(ConnRes conn myAuthCIDs reader) -> do
-        forkIO reader    >>= addReader conn
-        forkIO timeouter >>= addTimeouter conn
+        forkIO reader >>= addReader conn
         let conf' = conf {
                 ccParameters = (ccParameters conf) {
                       versionInformation = Just verInfo
@@ -96,7 +95,6 @@
         setDead conn
         freeResources conn
         killReaders conn
-        join $ replaceKillTimeouter conn
 
 createClientConnection :: ClientConfig -> VersionInfo -> IO ConnRes
 createClientConnection conf@ClientConfig{..} verInfo = do
diff --git a/Network/QUIC/Closer.hs b/Network/QUIC/Closer.hs
--- a/Network/QUIC/Closer.hs
+++ b/Network/QUIC/Closer.hs
@@ -40,7 +40,6 @@
 closure' :: Connection -> LDCC -> Frame -> IO ()
 closure' conn ldcc frame = do
     killReaders conn
-    killTimeouter <- replaceKillTimeouter conn
     let bufsiz = maximumUdpPayloadSize
     sendBuf <- mallocBytes bufsiz
     recvBuf <- mallocBytes bufsiz
@@ -62,7 +61,6 @@
         free sendBuf
         free recvBuf
         clos
-        killTimeouter
 
 encodeCC :: Connection -> SizedBuffer -> Frame -> IO Int
 encodeCC conn res0@(SizedBuffer sendBuf0 bufsiz0) frame = do
@@ -103,13 +101,13 @@
     loop n = do
         send
         getTimeMicrosecond >>= skip (Microseconds pto)
-        mx <- timeout (Microseconds (pto !>>. 1)) recv
+        mx <- timeout (Microseconds (pto !>>. 1)) "closer 1" recv
         case mx of
           Nothing -> hook
           Just 0  -> return ()
           Just _  -> loop (n - 1)
     skip tmo@(Microseconds duration) base = do
-        mx <- timeout tmo recv
+        mx <- timeout tmo "closer 2" recv
         case mx of
           Nothing -> return ()
           Just 0  -> return ()
diff --git a/Network/QUIC/Connection/Misc.hs b/Network/QUIC/Connection/Misc.hs
--- a/Network/QUIC/Connection/Misc.hs
+++ b/Network/QUIC/Connection/Misc.hs
@@ -21,8 +21,6 @@
   , setMaxPacketSize
   , addReader
   , killReaders
-  , addTimeouter
-  , replaceKillTimeouter
   , addResource
   , freeResources
   , readMinIdleTimeout
@@ -159,16 +157,6 @@
 
 killReaders :: Connection -> IO ()
 killReaders Connection{..} = join $ readIORef readers
-
-----------------------------------------------------------------
-
-addTimeouter :: Connection -> ThreadId -> IO ()
-addTimeouter Connection{..} tid = do
-    wtid <- mkWeakThreadId tid
-    writeIORef tmouter (deRefWeak wtid >>= mapM_ killThread)
-
-replaceKillTimeouter :: Connection -> IO (IO ())
-replaceKillTimeouter Connection{..} = atomicModifyIORef' tmouter (return (),)
 
 ----------------------------------------------------------------
 
diff --git a/Network/QUIC/Connection/Timeout.hs b/Network/QUIC/Connection/Timeout.hs
--- a/Network/QUIC/Connection/Timeout.hs
+++ b/Network/QUIC/Connection/Timeout.hs
@@ -1,8 +1,8 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 
 module Network.QUIC.Connection.Timeout (
-    timeouter
-  , timeout
+    timeout
   , fire
   , cfire
   , delay
@@ -10,38 +10,32 @@
 
 import Data.Typeable
 import Network.QUIC.Event
-import System.IO.Unsafe (unsafePerformIO)
 import UnliftIO.Concurrent
 import qualified UnliftIO.Exception as E
-import UnliftIO.STM
 
 import Network.QUIC.Connection.Types
 import Network.QUIC.Connector
 import Network.QUIC.Imports
 import Network.QUIC.Types
 
-data TimeoutException = TimeoutException deriving (Show, Typeable)
+data TimeoutException = TimeoutException String deriving (Show, Typeable)
 
 instance E.Exception TimeoutException where
   fromException = E.asyncExceptionFromException
   toException = E.asyncExceptionToException
 
-globalTimeoutQ :: TQueue (IO ())
-globalTimeoutQ = unsafePerformIO newTQueueIO
-{-# NOINLINE globalTimeoutQ #-}
-
-timeouter :: IO ()
-timeouter = forever $ join $ atomically (readTQueue globalTimeoutQ)
-
-timeout :: Microseconds -> IO a -> IO (Maybe a)
-timeout (Microseconds ms) action = do
+timeout :: Microseconds -> String -> IO a -> IO (Maybe a)
+timeout (Microseconds ms) dmsg action = do
     tid <- myThreadId
     timmgr <- getSystemTimerManager
-    let killMe = E.throwTo tid TimeoutException
-        onTimeout = atomically $ writeTQueue globalTimeoutQ killMe
-        setup = registerTimeout timmgr ms onTimeout
+#if defined(mingw32_HOST_OS)
+    let killMe = void $ forkIO $ E.throwTo tid $ TimeoutException dmsg
+#else
+    let killMe = E.throwTo tid $ TimeoutException dmsg
+#endif
+        setup = registerTimeout timmgr ms killMe
         cleanup key = unregisterTimeout timmgr key
-    E.handleSyncOrAsync (\TimeoutException -> return Nothing) $
+    E.handleSyncOrAsync (\(TimeoutException _) -> return Nothing) $
         E.bracket setup cleanup $ \_ -> Just <$> action
 
 fire :: Connection -> Microseconds -> TimeoutCallback -> IO ()
diff --git a/Network/QUIC/Connection/Types.hs b/Network/QUIC/Connection/Types.hs
--- a/Network/QUIC/Connection/Types.hs
+++ b/Network/QUIC/Connection/Types.hs
@@ -184,7 +184,6 @@
   , connRecvQ         :: RecvQ
   , udpSocket         :: ~(IORef UDPSocket)
   , readers           :: IORef (IO ())
-  , tmouter           :: IORef (IO ())
   , mainThreadId      :: ThreadId
   -- Info
   , roleInfo          :: IORef RoleInfo
@@ -278,7 +277,6 @@
     dcrptBuf <- mallocBytes bufsiz
     Connection connstate debugLog qLog hooks send recv recvQ sref
         <$> newIORef (return ())
-        <*> newIORef (return ())
         <*> myThreadId
         -- Info
         <*> newIORef initialRoleInfo
diff --git a/Network/QUIC/Receiver.hs b/Network/QUIC/Receiver.hs
--- a/Network/QUIC/Receiver.hs
+++ b/Network/QUIC/Receiver.hs
@@ -35,7 +35,7 @@
         -- The spec says that CC is not sent when timeout.
         -- But we intentionally sends CC when timeout.
         ito <- readMinIdleTimeout conn
-        mx <- timeout ito $ connRecv conn -- fixme: taking minimum with peer's one
+        mx <- timeout ito "recvTimeout" $ connRecv conn -- fixme: taking minimum with peer's one
         case mx of
           Nothing -> do
               st <- getConnectionState conn
@@ -77,7 +77,8 @@
 processReceivedPacketHandshake conn rpkt = do
     let CryptPacket hdr _ = rpCryptPacket rpkt
         lvl = rpEncryptionLevel rpkt
-    mx <- timeout (Microseconds 10000) $ waitEncryptionLevel conn lvl
+        msg = "processReceivedPacketHandshake " ++ if isServer conn then "Server" else "Client"
+    mx <- timeout (Microseconds 10000) msg $ waitEncryptionLevel conn lvl
     case mx of
       Nothing -> do
           putOffCrypto conn lvl rpkt
diff --git a/Network/QUIC/Sender.hs b/Network/QUIC/Sender.hs
--- a/Network/QUIC/Sender.hs
+++ b/Network/QUIC/Sender.hs
@@ -224,10 +224,11 @@
 sendFinal :: Connection -> IO ()
 sendFinal conn = loop 30
   where
+    msg = "sendFinal " ++ if isServer conn then "Server" else "Client"
     loop :: Int -> IO ()
     loop 0 = return ()
     loop n = do
-        mx <- timeout (Microseconds 10) $ sendP conn
+        mx <- timeout (Microseconds 10) msg $ sendP conn
         case mx of
           Nothing -> return ()
           Just () -> loop (n - 1)
diff --git a/Network/QUIC/Server/Reader.hs b/Network/QUIC/Server/Reader.hs
--- a/Network/QUIC/Server/Reader.hs
+++ b/Network/QUIC/Server/Reader.hs
@@ -314,7 +314,7 @@
     sendRetry = do
         newdCID <- newCID
         retryToken <- generateRetryToken peerVer newdCID sCID dCID
-        mnewtoken <- timeout (Microseconds 100000) $ encryptToken tokenMgr retryToken
+        mnewtoken <- timeout (Microseconds 100000) "sendRetry" $ encryptToken tokenMgr retryToken
         case mnewtoken of
           Nothing       -> logAction "retry token stacked"
           Just newtoken -> do
@@ -367,7 +367,7 @@
   where
     loop = do
         ito <- readMinIdleTimeout conn
-        mbs <- timeout ito $ UDP.recv us
+        mbs <- timeout ito "readerServer" $ UDP.recv us
         case mbs of
           Nothing -> UDP.close us
           Just bs -> do
@@ -388,7 +388,7 @@
     unless migrating $ do
         setMigrationStarted conn
         -- fixme: should not block
-        mcidinfo <- timeout (Microseconds 100000) $ waitPeerCID conn
+        mcidinfo <- timeout (Microseconds 100000) "runNewServerReader" $ waitPeerCID conn
         let msg = "Migration: " <> bhow peersa <> " (" <> bhow dCID <> ")"
         qlogDebug conn $ Debug $ toLogStr msg
         connDebugLog conn $ "debug: runNewServerReader: " <> msg
diff --git a/Network/QUIC/Server/Run.hs b/Network/QUIC/Server/Run.hs
--- a/Network/QUIC/Server/Run.hs
+++ b/Network/QUIC/Server/Run.hs
@@ -57,8 +57,7 @@
         -- fixme: the case where sockets cannot be created.
         ssas <- mapM UDP.serverSocket $ scAddresses conf
         tids <- mapM (runDispatcher dispatch conf) ssas
-        ttid <- forkIO timeouter -- fixme
-        return (dispatch, ttid:tids)
+        return (dispatch, tids)
     teardown (dispatch, tids) = do
         clearDispatch dispatch
         mapM_ killThread tids
diff --git a/Network/QUIC/Stream/Misc.hs b/Network/QUIC/Stream/Misc.hs
--- a/Network/QUIC/Stream/Misc.hs
+++ b/Network/QUIC/Stream/Misc.hs
@@ -58,6 +58,8 @@
 setRxStreamClosed :: Stream -> IO ()
 setRxStreamClosed strm@Stream{..} = do
     atomicModifyIORef'' streamStateRx set
+    -- Sending a pseudo FIN so that recvStream doesn't block.
+    -- See https://github.com/kazu-yamamoto/quic/pull/54
     putRecvStreamQ strm ""
   where
     set (StreamState off _) = StreamState off True
diff --git a/quic.cabal b/quic.cabal
--- a/quic.cabal
+++ b/quic.cabal
@@ -1,6 +1,6 @@
 cabal-version:      >=1.10
 name:               quic
-version:            0.1.5
+version:            0.1.6
 license:            BSD3
 license-file:       LICENSE
 maintainer:         kazu@iij.ad.jp
diff --git a/test/IOSpec.hs b/test/IOSpec.hs
--- a/test/IOSpec.hs
+++ b/test/IOSpec.hs
@@ -77,6 +77,7 @@
         it "can exchange data on client 11" $ do
             withPipe (DropClientPacket [11]) $ testSendRecv cc sc waitS 20
     describe "recvStream" $ do
+        -- https://github.com/kazu-yamamoto/quic/pull/54
         it "don't block if client stop sending first" $ do
             withPipe (Randomly 20) $ testRecvStreamClientStopFirst cc sc waitS
         it "don't block if server stop sending first" $ do
@@ -115,7 +116,7 @@
         consumeBytes strm 10000 `shouldReturn` ()
         -- notify client to stop stream after all bytes are received.
         putMVar mvar ()
-        -- verify that client has stopped sending.
+        -- verify that recvStream does not block
         assertEndOfStream strm
         putMVar mvar ()
 
@@ -137,7 +138,7 @@
         consumeBytes strm 10000 `shouldReturn` ()
         -- ask client to stop sending.
         stopStream strm aerr
-        -- verify that client has stopped sending.
+        -- verify that recvStream does not block
         assertEndOfStream strm
         resetStream strm aerr
         putMVar mvar ()
