diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,20 @@
 # CHANGELOG
 
+- 0.13.0.0 (2023-12-30)
+    * **BREAKING**: Remove `serverRequirePong` option in favor of the new
+      implementation.
+    * **BREAKING**: Client: Rejecting request raises
+      `RequestRejected RequestHead ResponseHead`
+    * Timeout initial socket connection after 30s.
+    * If the socket is closed unexpectedly, raise `ConnectionClosed`.
+    * Added a way to manually send a Pong message.
+    * `runServer` now cleans up threads correctly.
+    * Remove redundant bytestring-builder dependency.
+    * Introduce `Network.WebSockets.Connection.PingPong` to
+      handle ping pong for any Connection, be it Client or Server.
+    * Bump `text `dependency upper bound to 2.2
+    * Bump `random `dependency lower bound to 1.0.1
+
 - 0.12.7.3 (2021-10-26)
     * Bump `attoparsec` dependency upper bound to 0.15
 
diff --git a/example/client.hs b/example/client.hs
new file mode 100644
--- /dev/null
+++ b/example/client.hs
@@ -0,0 +1,40 @@
+--------------------------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Main
+    ( main
+    ) where
+
+
+--------------------------------------------------------------------------------
+import           Control.Concurrent  (forkIO)
+import           Control.Monad       (forever, unless)
+import           Control.Monad.Trans (liftIO)
+import           Network.Socket      (withSocketsDo)
+import           Data.Text           (Text)
+import qualified Data.Text           as T
+import qualified Data.Text.IO        as T
+import qualified Network.WebSockets  as WS
+
+
+--------------------------------------------------------------------------------
+app :: WS.ClientApp ()
+app conn = do
+    putStrLn "Connected!"
+
+    -- Fork a thread that writes WS data to stdout
+    _ <- forkIO $ forever $ do
+        msg <- WS.receiveData conn
+        liftIO $ T.putStrLn msg
+
+    -- Read from stdin and write to WS
+    let loop = do
+            line <- T.getLine
+            unless (T.null line) $ WS.sendTextData conn line >> loop
+
+    loop
+    WS.sendClose conn ("Bye!" :: Text)
+
+
+--------------------------------------------------------------------------------
+main :: IO ()
+main = withSocketsDo $ WS.runClient "echo.websocket.org" 80 "/" app
diff --git a/src/Network/WebSockets.hs b/src/Network/WebSockets.hs
--- a/src/Network/WebSockets.hs
+++ b/src/Network/WebSockets.hs
@@ -83,6 +83,9 @@
     , newClientConnection
 
       -- * Utilities
+    , PingPongOptions(..)
+    , defaultPingPongOptions
+    , withPingPong
     , withPingThread
     , forkPingThread
     ) where
@@ -91,6 +94,7 @@
 --------------------------------------------------------------------------------
 import           Network.WebSockets.Client
 import           Network.WebSockets.Connection
+import           Network.WebSockets.Connection.PingPong
 import           Network.WebSockets.Http
 import           Network.WebSockets.Server
 import           Network.WebSockets.Types
diff --git a/src/Network/WebSockets/Client.hs b/src/Network/WebSockets/Client.hs
--- a/src/Network/WebSockets/Client.hs
+++ b/src/Network/WebSockets/Client.hs
@@ -20,11 +20,13 @@
 --------------------------------------------------------------------------------
 import qualified Data.ByteString.Builder       as Builder
 import           Control.Exception             (bracket, finally, throwIO)
+import           Control.Concurrent.MVar       (newEmptyMVar)
 import           Control.Monad                 (void)
 import           Data.IORef                    (newIORef)
 import qualified Data.Text                     as T
 import qualified Data.Text.Encoding            as T
 import qualified Network.Socket                as S
+import           System.Timeout                (timeout)
 
 
 --------------------------------------------------------------------------------
@@ -74,11 +76,13 @@
     S.setSocketOption sock S.NoDelay 1
 
     -- Connect WebSocket and run client
-    res <- finally
-        (S.connect sock (S.addrAddress addr) >>
-         runClientWithSocket sock fullHost path opts customHeaders app)
-        (S.close sock)
+    res <- bracket
+        (timeout (connectionTimeout opts * 1000 * 1000) $ S.connect sock (S.addrAddress addr))
+        (const $ S.close sock) $ \maybeConnected -> case maybeConnected of
+            Nothing -> throwIO $ ConnectionTimeout
+            Just () -> runClientWithSocket sock fullHost path opts customHeaders app
 
+
     -- Clean up
     return res
 
@@ -157,12 +161,14 @@
                 (connectionMessageDataSizeLimit opts) stream
     write   <- encodeMessages protocol ClientConnection stream
     sentRef <- newIORef False
+    heartbeat <- newEmptyMVar
     return $ Connection
         { connectionOptions   = opts
         , connectionType      = ClientConnection
         , connectionProtocol  = protocol
         , connectionParse     = parse
         , connectionWrite     = write
+        , connectionHeartbeat = heartbeat
         , connectionSentClose = sentRef
         }
   where
diff --git a/src/Network/WebSockets/Connection.hs b/src/Network/WebSockets/Connection.hs
--- a/src/Network/WebSockets/Connection.hs
+++ b/src/Network/WebSockets/Connection.hs
@@ -1,6 +1,5 @@
 --------------------------------------------------------------------------------
--- | This module exposes connection internals and should only be used if you
--- really know what you are doing.
+-- | This module exposes connection internals
 {-# LANGUAGE OverloadedStrings #-}
 module Network.WebSockets.Connection
     ( PendingConnection (..)
@@ -31,6 +30,7 @@
     , sendClose
     , sendCloseCode
     , sendPing
+    , sendPong
 
     , withPingThread
     , forkPingThread
@@ -49,6 +49,7 @@
 import           Control.Concurrent                              (forkIO,
                                                                   threadDelay)
 import qualified Control.Concurrent.Async                        as Async
+import           Control.Concurrent.MVar                         (MVar, newEmptyMVar, tryPutMVar)
 import           Control.Exception                               (AsyncException,
                                                                   fromException,
                                                                   handle,
@@ -178,13 +179,15 @@
         write <- foldM (\x ext -> extWrite ext x) writeRaw exts
         parse <- foldM (\x ext -> extParse ext x) parseRaw exts
 
-        sentRef    <- newIORef False
+        sentRef <- newIORef False
+        heartbeat <- newEmptyMVar
         let connection = Connection
                 { connectionOptions   = options
                 , connectionType      = ServerConnection
                 , connectionProtocol  = protocol
                 , connectionParse     = parse
                 , connectionWrite     = write
+                , connectionHeartbeat = heartbeat
                 , connectionSentClose = sentRef
                 }
 
@@ -238,6 +241,7 @@
 
 
 --------------------------------------------------------------------------------
+-- | Requires calling 'pendingStream' and 'Stream.close'.
 rejectRequest
     :: PendingConnection  -- ^ Connection to reject
     -> B.ByteString       -- ^ Rejection response body
@@ -251,6 +255,9 @@
     { connectionOptions   :: !ConnectionOptions
     , connectionType      :: !ConnectionType
     , connectionProtocol  :: !Protocol
+    , connectionHeartbeat :: !(MVar ())
+    -- ^ This MVar is filled whenever a pong is received.  This is used by
+    -- 'withPingPong' to timeout the connection if a pong is not received.
     , connectionParse     :: !(IO (Maybe Message))
     , connectionWrite     :: !([Message] -> IO ())
     , connectionSentClose :: !(IORef Bool)
@@ -293,6 +300,7 @@
                 unless hasSentClose $ send conn msg
                 throwIO $ CloseRequest i closeMsg
             Pong _    -> do
+                _ <- tryPutMVar (connectionHeartbeat conn) ()
                 connectionOnPong (connectionOptions conn)
                 receiveDataMessage conn
             Ping pl   -> do
@@ -388,6 +396,10 @@
 sendPing :: WebSocketsData a => Connection -> a -> IO ()
 sendPing conn = send conn . ControlMessage . Ping . toLazyByteString
 
+--------------------------------------------------------------------------------
+-- | Send a pong
+sendPong :: WebSocketsData a => Connection -> a -> IO ()
+sendPong conn = send conn . ControlMessage . Pong . toLazyByteString
 
 --------------------------------------------------------------------------------
 -- | Forks a ping thread, sending a ping message every @n@ seconds over the
@@ -396,6 +408,9 @@
 -- This is useful to keep idle connections open through proxies and whatnot.
 -- Many (but not all) proxies have a 60 second default timeout, so based on that
 -- sending a ping every 30 seconds is a good idea.
+--
+-- Note that usually you want to use 'Network.WebSockets.Connection.PingPong.withPingPong'
+-- to timeout the connection if a pong is not received.
 withPingThread
     :: Connection
     -> Int    -- ^ Second interval in which pings should be sent.
@@ -404,7 +419,6 @@
     -> IO a   -- ^ Executes application and kills ping thread when done.
 withPingThread conn n action app =
     Async.withAsync (pingThread conn n action) (\_ -> app)
-
 
 --------------------------------------------------------------------------------
 -- | DEPRECATED: Use 'withPingThread' instead.
diff --git a/src/Network/WebSockets/Connection/Options.hs b/src/Network/WebSockets/Connection/Options.hs
--- a/src/Network/WebSockets/Connection/Options.hs
+++ b/src/Network/WebSockets/Connection/Options.hs
@@ -31,6 +31,8 @@
     { connectionOnPong                :: !(IO ())
       -- ^ Whenever a 'pong' is received, this IO action is executed. It can be
       -- used to tickle connections or fire missiles.
+    , connectionTimeout               :: !Int
+      -- ^ Timeout for connection establishment in seconds. Only used in the client.
     , connectionCompressionOptions    :: !CompressionOptions
       -- ^ Enable 'PermessageDeflate'.
     , connectionStrictUnicode         :: !Bool
@@ -59,9 +61,11 @@
 -- * Nothing happens when a pong is received.
 -- * Compression is disabled.
 -- * Lenient unicode decoding.
+-- * 30 second timeout for connection establishment.
 defaultConnectionOptions :: ConnectionOptions
 defaultConnectionOptions = ConnectionOptions
     { connectionOnPong                = return ()
+    , connectionTimeout               = 30
     , connectionCompressionOptions    = NoCompression
     , connectionStrictUnicode         = False
     , connectionFramePayloadSizeLimit = mempty
diff --git a/src/Network/WebSockets/Connection/PingPong.hs b/src/Network/WebSockets/Connection/PingPong.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/WebSockets/Connection/PingPong.hs
@@ -0,0 +1,62 @@
+module Network.WebSockets.Connection.PingPong
+    ( withPingPong
+    , PingPongOptions(..)
+    , PongTimeout(..)
+    , defaultPingPongOptions
+    ) where 
+
+import Control.Concurrent.Async as Async
+import Control.Exception
+import Control.Monad (void)
+import Network.WebSockets.Connection (Connection, connectionHeartbeat, pingThread)
+import Control.Concurrent.MVar (takeMVar)
+import System.Timeout (timeout)
+
+
+-- | Exception type used to kill connections if there
+-- is a pong timeout.
+data PongTimeout = PongTimeout deriving Show
+
+instance Exception PongTimeout
+
+
+-- | Options for ping-pong
+-- 
+-- Make sure that the ping interval is less than the pong timeout,
+-- for example N/2.
+data PingPongOptions = PingPongOptions {
+    pingInterval :: Int, -- ^ Interval in seconds
+    pongTimeout :: Int, -- ^ Timeout in seconds
+    pingAction :: IO () -- ^ Action to perform after sending a ping
+}
+
+-- | Default options for ping-pong
+-- 
+--   Ping every 15 seconds, timeout after 30 seconds
+defaultPingPongOptions :: PingPongOptions
+defaultPingPongOptions = PingPongOptions {
+    pingInterval = 15,
+    pongTimeout = 30,
+    pingAction = return ()
+}
+
+-- | Run an application with ping-pong enabled. Raises PongTimeout if a pong is not received.
+-- 
+-- Can used with Client and Server connections.
+withPingPong :: PingPongOptions -> Connection -> (Connection -> IO ()) -> IO ()
+withPingPong options connection app = void $ 
+    withAsync (app connection) $ \appAsync -> do
+        withAsync (pingThread connection (pingInterval options) (pingAction options)) $ \pingAsync -> do
+            withAsync (heartbeat >> throwIO PongTimeout) $ \heartbeatAsync -> do
+                waitAnyCancel [appAsync, pingAsync, heartbeatAsync]
+    where
+        heartbeat = whileJust $ timeout (pongTimeout options * 1000 * 1000) 
+           $ takeMVar (connectionHeartbeat connection)
+
+        -- Loop until action returns Nothing
+        whileJust :: IO (Maybe a) -> IO ()
+        whileJust action = do
+            result <- action
+            case result of
+                Nothing -> return ()
+                Just _ -> whileJust action
diff --git a/src/Network/WebSockets/Http.hs b/src/Network/WebSockets/Http.hs
--- a/src/Network/WebSockets/Http.hs
+++ b/src/Network/WebSockets/Http.hs
@@ -100,7 +100,9 @@
     | MalformedResponse ResponseHead String
     -- | The request was well-formed, but the library user rejected it.
     -- (e.g. "unknown path")
-    | RequestRejected Request String
+    | RequestRejected RequestHead ResponseHead
+    -- | The connection timed out
+    | ConnectionTimeout
     -- | for example "EOF came too early" (which is actually a parse error)
     -- or for your own errors. (like "unknown path"?)
     | OtherHandshakeException String
diff --git a/src/Network/WebSockets/Hybi13.hs b/src/Network/WebSockets/Hybi13.hs
--- a/src/Network/WebSockets/Hybi13.hs
+++ b/src/Network/WebSockets/Hybi13.hs
@@ -78,6 +78,8 @@
     -- - Switching Protocols
     --
     -- But we don't check it for now
+    when (responseCode response == 400) $ Left $
+        RequestRejected request response 
     when (responseCode response /= 101) $ Left $
         MalformedResponse response "Wrong response status or message."
 
diff --git a/src/Network/WebSockets/Server.hs b/src/Network/WebSockets/Server.hs
--- a/src/Network/WebSockets/Server.hs
+++ b/src/Network/WebSockets/Server.hs
@@ -19,21 +19,17 @@
 
 
 --------------------------------------------------------------------------------
-import           Control.Concurrent            (threadDelay)
 import qualified Control.Concurrent.Async      as Async
-import           Control.Exception             (Exception, allowInterrupt,
-                                                bracket, bracketOnError,
-                                                finally, mask_, throwIO)
-import           Control.Monad                 (forever, void, when)
-import qualified Data.IORef                    as IORef
-import           Data.Maybe                    (isJust)
+import           Control.Exception             (bracket,
+                                                bracketOnError, finally, mask_,
+                                                throwIO)
 import           Network.Socket                (Socket)
 import qualified Network.Socket                as S
-import qualified System.Clock                  as Clock
 
 
 --------------------------------------------------------------------------------
 import           Network.WebSockets.Connection
+import           Network.WebSockets.Connection.PingPong (PongTimeout(..))
 import           Network.WebSockets.Http
 import qualified Network.WebSockets.Stream     as Stream
 import           Network.WebSockets.Types
@@ -85,10 +81,6 @@
     { serverHost              :: String
     , serverPort              :: Int
     , serverConnectionOptions :: ConnectionOptions
-    -- | Require a pong from the client every N seconds; otherwise kill the
-    -- connection.  If you use this, you should also use 'withPingThread' to
-    -- send a ping at a smaller interval; for example N/2.
-    , serverRequirePong       :: Maybe Int
     }
 
 
@@ -98,7 +90,6 @@
     { serverHost              = "127.0.0.1"
     , serverPort              = 8080
     , serverConnectionOptions = defaultConnectionOptions
-    , serverRequirePong       = Nothing
     }
 
 
@@ -110,59 +101,17 @@
 runServerWithOptions :: ServerOptions -> ServerApp -> IO a
 runServerWithOptions opts app = S.withSocketsDo $
     bracket
-    (makeListenSocket host port)
-    S.close $ \sock -> mask_ $ forever $ do
-        allowInterrupt
-        (conn, _) <- S.accept sock
-
-        -- This IORef holds a time at which the thread may be killed.  This time
-        -- can be extended by calling 'tickle'.
-        killRef <- IORef.newIORef =<< (+ killDelay) <$> getSecs
-        let tickle = IORef.writeIORef killRef =<< (+ killDelay) <$> getSecs
-
-        -- Update the connection options to call 'tickle' whenever a pong is
-        -- received.
-        let connOpts'
-                | not useKiller = connOpts
-                | otherwise     = connOpts
-                    { connectionOnPong = tickle >> connectionOnPong connOpts
-                    }
-
-        -- Run the application.
-        appAsync  <- Async.asyncWithUnmask $ \unmask ->
-            (unmask $ do
-                runApp conn connOpts' app) `finally`
-            (S.close conn)
-
-        -- Install the killer if required.
-        when useKiller $ void $ Async.async (killer killRef appAsync)
-  where
-    host     = serverHost opts
-    port     = serverPort opts
-    connOpts = serverConnectionOptions opts
-
-    -- Get the current number of seconds on some clock.
-    getSecs = Clock.sec <$> Clock.getTime Clock.Monotonic
-
-    -- Parse the 'serverRequirePong' options.
-    useKiller = isJust $ serverRequirePong opts
-    killDelay = maybe 0 fromIntegral (serverRequirePong opts)
-
-    -- Thread that reads the killRef, and kills the application if enough time
-    -- has passed.
-    killer killRef appAsync = do
-        killAt   <- IORef.readIORef killRef
-        now      <- getSecs
-        appState <- Async.poll appAsync
-        case appState of
-            -- Already finished/killed/crashed, we can give up.
-            Just _ -> return ()
-            -- Should not be killed yet.  Wait and try again.
-            Nothing | now < killAt -> do
-                threadDelay (fromIntegral killDelay * 1000 * 1000)
-                killer killRef appAsync
-            -- Time to kill.
-            _ -> Async.cancelWith appAsync PongTimeout
+    (makeListenSocket (serverHost opts) (serverPort opts))
+    S.close
+    (\sock ->
+        let
+            mainThread = do
+                (conn, _) <- S.accept sock
+                Async.withAsyncWithUnmask
+                    (\unmask -> unmask (runApp conn (serverConnectionOptions opts) app) `finally` S.close conn)
+                    (\_ -> mainThread)
+        in mask_ mainThread
+    )
 
 
 --------------------------------------------------------------------------------
@@ -200,7 +149,7 @@
 
 --------------------------------------------------------------------------------
 -- | Turns a socket, connected to some client, into a 'PendingConnection'. The
--- 'PendingConnection' should be closed using 'Stream.close' later.
+-- 'PendingConnection' should be closed using 'pendingStream' and 'Stream.close' later.
 makePendingConnection
     :: Socket -> ConnectionOptions -> IO PendingConnection
 makePendingConnection socket opts = do
@@ -223,13 +172,3 @@
             , pendingOnAccept = \_ -> return ()
             , pendingStream   = stream
             }
-
-
---------------------------------------------------------------------------------
--- | Internally used exception type used to kill connections if there
--- is a pong timeout.
-data PongTimeout = PongTimeout deriving Show
-
-
---------------------------------------------------------------------------------
-instance Exception PongTimeout
diff --git a/src/Network/WebSockets/Stream.hs b/src/Network/WebSockets/Stream.hs
--- a/src/Network/WebSockets/Stream.hs
+++ b/src/Network/WebSockets/Stream.hs
@@ -14,7 +14,7 @@
 
 import           Control.Concurrent.MVar        (MVar, newEmptyMVar, newMVar,
                                                  putMVar, takeMVar, withMVar)
-import           Control.Exception              (SomeException, SomeAsyncException, throwIO, catch, fromException)
+import           Control.Exception              (SomeException, SomeAsyncException, throwIO, catch, try, fromException)
 import           Control.Monad                  (forM_)
 import qualified Data.Attoparsec.ByteString     as Atto
 import qualified Data.Binary.Get                as BIN
@@ -31,6 +31,7 @@
 #else
 import qualified Network.Socket.ByteString      as SB (sendAll)
 #endif
+import           System.IO.Error                (isResourceVanishedError)
 
 import           Network.WebSockets.Types
 
@@ -59,7 +60,7 @@
 --   writing from the stream will be thread-safe, i.e. this function will create
 --   a receive and write lock to be used internally.
 --
--- - Reading from or writing or to a closed 'Stream' will always throw an
+-- - Reading from or writing to a closed 'Stream' will always throw an
 --   exception, even if the underlying "receive" and "send" functions do not
 --   (we do the bookkeeping).
 --
@@ -116,8 +117,13 @@
 makeSocketStream socket = makeStream receive send
   where
     receive = do
-        bs <- SB.recv socket 8192
-        return $ if B.null bs then Nothing else Just bs
+        bs <- try $ SB.recv socket 8192
+        case bs of
+            -- If the resource vanished, the socket was closed
+            Left e | isResourceVanishedError e -> return Nothing
+                   | otherwise                 -> throwIO e
+            Right bs' | B.null bs'             -> return Nothing
+                      | otherwise              -> return $ Just bs'
 
     send Nothing   = return ()
     send (Just bs) = do
diff --git a/tests/haskell/Network/WebSockets/Server/Tests.hs b/tests/haskell/Network/WebSockets/Server/Tests.hs
--- a/tests/haskell/Network/WebSockets/Server/Tests.hs
+++ b/tests/haskell/Network/WebSockets/Server/Tests.hs
@@ -10,6 +10,7 @@
 import           Control.Applicative            ((<$>), (<|>))
 import           Control.Concurrent             (forkIO, killThread,
                                                  threadDelay)
+import           Control.Concurrent.Async       (Async, async, cancel)
 import           Control.Exception              (SomeException, catch, handle)
 import           Control.Monad                  (forever, replicateM, unless)
 import           Data.IORef                     (IORef, newIORef, readIORef,
@@ -39,6 +40,7 @@
     , testCase "bulk server/client"   testBulkServerClient
     , testCase "onPong"               testOnPong
     , testCase "ipv6 server"          testIpv6Server
+    , testCase "reject request"       testRejectRequest 
     ]
 
 
@@ -69,7 +71,7 @@
 testServerClient :: String -> (Connection -> [BL.ByteString] -> IO ()) -> Assertion
 testServerClient host sendMessages = withEchoServer host 42940 "Bye" $ do
     texts  <- map unArbitraryUtf8 <$> sample
-    texts' <- retry $ runClient host 42940 "/chat" $ client texts
+    texts' <- runClient host 42940 "/chat" $ client texts
     texts @=? texts'
   where
     client :: [BL.ByteString] -> ClientApp [BL.ByteString]
@@ -80,8 +82,30 @@
         expectCloseException conn "Bye"
         return texts'
 
+--------------------------------------------------------------------------------
+testRejectRequest :: Assertion
+testRejectRequest = withRejectingServer
+  where
+    client :: ClientApp ()
+    client _ = error "Client should not be able to connect"
 
+    server :: ServerApp
+    server pendingConnection = rejectRequest pendingConnection "Bye"
 
+    withRejectingServer :: IO ()
+    withRejectingServer = do
+        serverThread <- async $ runServer "127.0.0.1" 42940 server
+        waitSome
+        () <- runClient "127.0.0.1" 42940 "/chat" client `catch` handler
+        waitSome
+        cancel serverThread
+        return ()
+
+    handler :: HandshakeException -> IO ()
+    handler (RequestRejected _ response) = do
+        responseCode response @=? 400
+    handler exc  = error $ "Unexpected exception " ++ show exc
+
 --------------------------------------------------------------------------------
 testOnPong :: Assertion
 testOnPong = withEchoServer "127.0.0.1" 42941 "Bye" $ do
@@ -115,29 +139,15 @@
 waitSome :: IO ()
 waitSome = threadDelay $ 200 * 1000
 
-
 --------------------------------------------------------------------------------
--- HOLY SHIT WHAT SORT OF ATROCITY IS THIS?!?!?!
---
--- The problem is that sometimes, the server hasn't been brought down yet
--- before the next test, which will cause it not to be able to bind to the
--- same port again. In this case, we just retry.
---
--- The same is true for our client: possibly, the server is not up yet
--- before we run the client. We also want to retry in that case.
-retry :: IO a -> IO a
-retry action = (\(_ :: SomeException) -> waitSome >> action) `handle` action
-
-
---------------------------------------------------------------------------------
 withEchoServer :: String -> Int -> BL.ByteString -> IO a -> IO a
 withEchoServer host port expectedClose action = do
     cRef <- newIORef False
-    serverThread <- forkIO $ retry $ runServer host port (\c -> server c `catch` handleClose cRef)
+    serverThread <- async $ runServer host port (\c -> server c `catch` handleClose cRef)
     waitSome
     result <- action
     waitSome
-    killThread serverThread
+    cancel serverThread
     closeCalled <- readIORef cRef
     unless closeCalled $ error "Expecting the CloseRequest exception"
     return result
diff --git a/websockets.cabal b/websockets.cabal
--- a/websockets.cabal
+++ b/websockets.cabal
@@ -1,5 +1,5 @@
 Name:    websockets
-Version: 0.12.7.3
+Version: 0.13.0.0
 
 Synopsis:
   A sensible and clean way to write WebSocket-capable servers in Haskell.
@@ -31,6 +31,7 @@
                Jasper Van der Jeugt <m@jaspervdj.be>
                Steffen Schuldenzucker <steffen.schuldenzucker@googlemail.com>
                Alex Lang <lang@tsurucapital.com>
+               Domen Kožar
 Maintainer:    Jasper Van der Jeugt <m@jaspervdj.be>
 Stability:     experimental
 Category:      Network
@@ -62,6 +63,7 @@
     Network.WebSockets
     Network.WebSockets.Client
     Network.WebSockets.Connection
+    Network.WebSockets.Connection.PingPong
     Network.WebSockets.Extensions
     Network.WebSockets.Stream
     -- Network.WebSockets.Util.PubSub TODO
@@ -82,19 +84,17 @@
   Build-depends:
     async             >= 2.2    && < 2.3,
     attoparsec        >= 0.10   && < 0.15,
-    base              >= 4.8    && < 5,
+    base              >= 4.14   && < 5,
     base64-bytestring >= 0.1    && < 1.3,
     binary            >= 0.8.1  && < 0.11,
-    bytestring        >= 0.9    && < 0.12,
-    bytestring-builder             < 0.11,
+    bytestring        >= 0.9    && < 0.13,
     case-insensitive  >= 0.3    && < 1.3,
-    clock             >= 0.8    && < 0.9,
     containers        >= 0.3    && < 0.7,
     network           >= 2.3    && < 3.2,
-    random            >= 1.0    && < 1.3,
+    random            >= 1.0.1  && < 1.3,
     SHA               >= 1.5    && < 1.7,
     streaming-commons >= 0.1    && < 0.3,
-    text              >= 0.10   && < 1.3,
+    text              >= 0.10   && < 2.2,
     entropy           >= 0.2.1  && < 0.5
 
 Test-suite websockets-tests
@@ -110,6 +110,7 @@
     Network.WebSockets.Client
     Network.WebSockets.Connection
     Network.WebSockets.Connection.Options
+    Network.WebSockets.Connection.PingPong
     Network.WebSockets.Extensions
     Network.WebSockets.Extensions.Description
     Network.WebSockets.Extensions.PermessageDeflate
@@ -142,22 +143,20 @@
     -- Copied from regular dependencies...
     async             >= 2.2    && < 2.3,
     attoparsec        >= 0.10   && < 0.15,
-    base              >= 4      && < 5,
+    base              >= 4.14   && < 5,
     base64-bytestring >= 0.1    && < 1.3,
     binary            >= 0.8.1  && < 0.11,
-    bytestring        >= 0.9    && < 0.12,
-    bytestring-builder             < 0.11,
+    bytestring        >= 0.9    && < 0.13,
     case-insensitive  >= 0.3    && < 1.3,
-    clock             >= 0.8    && < 0.9,
     containers        >= 0.3    && < 0.7,
     network           >= 2.3    && < 3.2,
     random            >= 1.0    && < 1.3,
     SHA               >= 1.5    && < 1.7,
     streaming-commons >= 0.1    && < 0.3,
-    text              >= 0.10   && < 1.3,
+    text              >= 0.10   && < 2.2,
     entropy           >= 0.2.1  && < 0.5
 
-Executable websockets-example
+Executable websockets-server-example
   If !flag(Example)
     Buildable: False
 
@@ -167,24 +166,26 @@
   Default-language: Haskell2010
 
   Build-depends:
+    base,
     websockets,
-    -- Copied from regular dependencies...
-    async             >= 2.2    && < 2.3,
-    attoparsec        >= 0.10   && < 0.15,
-    base              >= 4      && < 5,
-    base64-bytestring >= 0.1    && < 1.3,
-    binary            >= 0.8.1  && < 0.11,
-    bytestring        >= 0.9    && < 0.12,
-    bytestring-builder             < 0.11,
-    case-insensitive  >= 0.3    && < 1.3,
-    clock             >= 0.8    && < 0.9,
-    containers        >= 0.3    && < 0.7,
-    network           >= 2.3    && < 3.2,
-    random            >= 1.0    && < 1.3,
-    SHA               >= 1.5    && < 1.7,
-    text              >= 0.10   && < 1.3,
-    entropy           >= 0.2.1  && < 0.5
+    text
 
+Executable websockets-client-example
+  If !flag(Example)
+    Buildable: False
+
+  Hs-source-dirs:   example
+  Main-is:          client.hs
+  Ghc-options:      -Wall
+  Default-language: Haskell2010
+
+  Build-depends:
+    base,
+    websockets,
+    text,
+    network,
+    mtl
+
 Executable websockets-autobahn
   If !flag(Example)
     Buildable: False
@@ -202,18 +203,16 @@
     -- Copied from regular dependencies...
     async             >= 2.2    && < 2.3,
     attoparsec        >= 0.10   && < 0.15,
-    base              >= 4      && < 5,
+    base              >= 4.14     && < 5,
     base64-bytestring >= 0.1    && < 1.3,
     binary            >= 0.8.1  && < 0.11,
-    bytestring        >= 0.9    && < 0.12,
-    bytestring-builder             < 0.11,
+    bytestring        >= 0.9    && < 0.13,
     case-insensitive  >= 0.3    && < 1.3,
-    clock             >= 0.8    && < 0.9,
     containers        >= 0.3    && < 0.7,
     network           >= 2.3    && < 3.2,
     random            >= 1.0    && < 1.3,
     SHA               >= 1.5    && < 1.7,
-    text              >= 0.10   && < 1.3,
+    text              >= 0.10   && < 2.2,
     entropy           >= 0.2.1  && < 0.5
 
 Benchmark bench-mask
@@ -231,16 +230,14 @@
     -- Copied from regular dependencies...
     async             >= 2.2    && < 2.3,
     attoparsec        >= 0.10   && < 0.15,
-    base              >= 4      && < 5,
+    base              >= 4.14   && < 5,
     base64-bytestring >= 0.1    && < 1.3,
     binary            >= 0.8.1  && < 0.11,
-    bytestring        >= 0.9    && < 0.12,
-    bytestring-builder             < 0.11,
+    bytestring        >= 0.9    && < 0.13,
     case-insensitive  >= 0.3    && < 1.3,
-    clock             >= 0.8    && < 0.9,
     containers        >= 0.3    && < 0.7,
     network           >= 2.3    && < 3.2,
     random            >= 1.0    && < 1.3,
     SHA               >= 1.5    && < 1.7,
-    text              >= 0.10   && < 1.3,
+    text              >= 0.10   && < 2.2,
     entropy           >= 0.2.1  && < 0.5
