diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,8 @@
+## 3.3.7
+
+* InvalidArgument (Bad file descriptor) is ignored in `receive`.
+  [#787](https://github.com/yesodweb/wai/pull/787)
+
 ## 3.3.6
 
 * Fixing a bug of thread killed in the case of event source with
diff --git a/Network/Wai/Handler/Warp/Recv.hs b/Network/Wai/Handler/Warp/Recv.hs
--- a/Network/Wai/Handler/Warp/Recv.hs
+++ b/Network/Wai/Handler/Warp/Recv.hs
@@ -17,7 +17,9 @@
 import Foreign.ForeignPtr (withForeignPtr)
 import Foreign.Ptr (Ptr, castPtr, plusPtr)
 import GHC.Conc (threadWaitRead)
+import qualified GHC.IO.Exception as E
 import Network.Socket (Socket)
+import qualified System.IO.Error as E
 #if MIN_VERSION_network(3,1,0)
 import Network.Socket (withFdSocket)
 #else
@@ -94,8 +96,11 @@
                 siz' = siz - len
             loop bss' siz'
 
+-- The timeout manager may close the socket.
+-- In that case, an error of "Bad file descriptor" occurs.
+-- We ignores it because we expect TimeoutThread.
 receive :: Socket -> BufferPool -> Recv
-receive sock pool = withBufferPool pool $ \ (ptr, size) -> do
+receive sock pool = E.handle handler $ withBufferPool pool $ \ (ptr, size) -> do
 #if MIN_VERSION_network(3,1,0)
   withFdSocket sock $ \fd -> do
 #elif MIN_VERSION_network(3,0,0)
@@ -105,6 +110,11 @@
 #endif
     let size' = fromIntegral size
     fromIntegral <$> receiveloop fd ptr size'
+  where
+    handler :: E.IOException -> IO ByteString
+    handler e
+      | E.ioeGetErrorType e == E.InvalidArgument = return ""
+      | otherwise                                = E.throwIO e
 
 receiveBuf :: Socket -> RecvBuf
 receiveBuf sock buf0 siz0 = do
diff --git a/Network/Wai/Handler/Warp/Run.hs b/Network/Wai/Handler/Warp/Run.hs
--- a/Network/Wai/Handler/Warp/Run.hs
+++ b/Network/Wai/Handler/Warp/Run.hs
@@ -17,7 +17,7 @@
 import Control.Exception as E
 import qualified Data.ByteString as S
 import Data.Char (chr)
-import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef')
+import Data.IORef (IORef, newIORef, readIORef, writeIORef)
 import Data.Streaming.Network (bindPortTCP)
 import Foreign.C.Error (Errno(..), eCONNABORTED)
 import GHC.IO.Exception (IOException(..))
@@ -282,10 +282,7 @@
 fork set mkConn addr app counter ii = settingsFork set $ \unmask ->
     -- Call the user-supplied on exception code if any
     -- exceptions are thrown.
-    handle (settingsOnException set Nothing) .
-    -- Allocate a new IORef indicating whether the connection has been
-    -- closed, to avoid double-freeing a connection
-    withClosedRef $ \ref ->
+    handle (settingsOnException set Nothing) $
         -- Run the connection maker to get a new connection, and ensure
         -- that the connection is closed. If the mkConn call throws an
         -- exception, we will leak the connection. If the mkConn call is
@@ -296,21 +293,13 @@
         -- We grab the connection before registering timeouts since the
         -- timeouts will be useless during connection creation, due to the
         -- fact that async exceptions are still masked.
-        bracket mkConn (cleanUp ref) (serve unmask ref)
+        bracket mkConn cleanUp (serve unmask)
   where
-    withClosedRef inner = newIORef False >>= inner
-
-    closeConn ref conn = do
-        isClosed <- atomicModifyIORef' ref $ \x -> (True, x)
-        unless isClosed $ connClose conn
-
-    cleanUp ref (conn, _) = closeConn ref conn `finally` connFree conn
+    cleanUp (conn, _) = connClose conn `finally` connFree conn
 
     -- We need to register a timeout handler for this thread, and
-    -- cancel that handler as soon as we exit. We additionally close
-    -- the connection immediately in case the child thread catches the
-    -- async exception or performs some long-running cleanup action.
-    serve unmask ref (conn, transport) = bracket register cancel $ \th -> do
+    -- cancel that handler as soon as we exit.
+    serve unmask (conn, transport) = bracket register cancel $ \th -> do
         -- We now have fully registered a connection close handler in
         -- the case of all exceptions, so it is safe to once again
         -- allow async exceptions.
@@ -322,8 +311,7 @@
            -- above ensures the connection is closed.
            when goingon $ serveConnection conn ii th addr transport set app
       where
-        register = T.registerKillThread (timeoutManager ii)
-                                        (closeConn ref conn)
+        register = T.registerKillThread (timeoutManager ii) (connClose conn)
         cancel   = T.cancel
 
     onOpen adr    = increase counter >> settingsOnOpen  set adr
diff --git a/warp.cabal b/warp.cabal
--- a/warp.cabal
+++ b/warp.cabal
@@ -1,5 +1,5 @@
 Name:                warp
-Version:             3.3.6
+Version:             3.3.7
 Synopsis:            A fast, light-weight web server for WAI applications.
 License:             MIT
 License-file:        LICENSE
