diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,12 @@
 # Revision history for http3
 
+## 0.1.9
+
+* A connection's threads are waited for, up to five seconds, before `run`
+  returns and quic tears the connection down.
+  [#55](https://github.com/kazu-yamamoto/http3/pull/55)
+* Requires `time-manager` 0.5, `http2` 5.4.10 and `quic` 0.3.18.
+
 ## 0.1.8
 
 * Allow time-manager 0.4.
diff --git a/Network/HTTP3/Context.hs b/Network/HTTP3/Context.hs
--- a/Network/HTTP3/Context.hs
+++ b/Network/HTTP3/Context.hs
@@ -36,6 +36,8 @@
 import Network.QUIC.Internal (isClient, isServer)
 import Network.Socket (SockAddr)
 import qualified System.ThreadManager as T
+import qualified System.TimeManager as TM
+import System.Timeout (timeout)
 
 import Network.HTTP3.Config
 import Network.HTTP3.Control
@@ -52,6 +54,9 @@
     , ctxUniSwitch :: H3StreamType -> InstructionHandler
     , ctxPReadMaker :: PositionReadMaker
     , ctxThreadManager :: T.ThreadManager
+    , ctxTimeoutManager :: TM.Manager
+    -- ^ Owning a thread and timing it out are separate, so the
+    --   thread manager carries no timer and this is kept here.
     , ctxHooks :: Hooks
     , ctxMySockAddr :: SockAddr
     , ctxPeerSockAddr :: SockAddr
@@ -65,11 +70,27 @@
     -- ^ Sending a Stream Cancellation on our QPACK decoder stream
     }
 
+-- | Running an action with a context, and seeing this connection's own
+--   threads out before returning: they are using the connection, which
+--   the caller tears down once this returns.
+--
+-- The wait is capped because one of the threads waited for runs the
+-- application's handler: an application which swallows the asynchronous
+-- exception would otherwise keep this from ever returning.  quic waits
+-- for its own threads with no cap, because those are all its own and are
+-- known to end.
 withContext :: Connection -> Config -> (Context -> IO a) -> IO a
 withContext conn conf action = do
     ctx <- newContext conn conf
-    T.stopAfter (ctxThreadManager ctx) (action ctx) (\_ -> return ())
+    let mgr = ctxThreadManager ctx
+    T.stopAfter mgr (action ctx) $ \_ ->
+        void $ timeout stopTimeout $ T.waitUntilAllGone mgr
 
+-- | How long the threads are given to end after they have been told to.
+--   Unwinding takes milliseconds; this is for one that does not stop.
+stopTimeout :: Int
+stopTimeout = 5000000
+
 newContext :: Connection -> Config -> IO Context
 newContext conn conf = do
     (sendEI, sendDI) <- setupUnidirectional conn conf
@@ -98,7 +119,8 @@
                 -- the connection; there is nobody to report a failure to.
                 (encodeDecoderInstructions [StreamCancellation sid] >>= sendDI)
                     `E.catch` ignoreSync
-    ctxThreadManager <- T.newThreadManager $ confTimeoutManager conf
+    ctxThreadManager <- T.newThreadManager
+    let ctxTimeoutManager = confTimeoutManager conf
     let ctxConnection = conn
     return Context{..}
   where
@@ -206,8 +228,8 @@
                 closeStream strm
             styp -> ctxUniSwitch styp (recvStream strm)
 
-withHandle :: Context -> (T.Handle -> IO ()) -> IO ()
-withHandle Context{..} action = void $ T.withHandle ctxThreadManager (return ()) action
+withHandle :: Context -> (TM.Handle -> IO ()) -> IO ()
+withHandle Context{..} action = void $ TM.withHandle ctxTimeoutManager (return ()) action
 
 newStream :: Context -> IO Stream
 newStream Context{..} = stream ctxConnection
@@ -218,14 +240,14 @@
 forkManaged :: Context -> String -> IO () -> IO ()
 forkManaged Context{..} = T.forkManaged ctxThreadManager
 
-forkManagedTimeout :: Context -> String -> (T.Handle -> IO ()) -> IO ()
+forkManagedTimeout :: Context -> String -> (TM.Handle -> IO ()) -> IO ()
 forkManagedTimeout Context{..} =
-    T.forkManagedTimeout ctxThreadManager
+    T.forkManagedTimeout ctxThreadManager ctxTimeoutManager
 
 forkManagedTimeoutFinally
-    :: Context -> String -> (T.Handle -> IO ()) -> IO () -> IO ()
+    :: Context -> String -> (TM.Handle -> IO ()) -> IO () -> IO ()
 forkManagedTimeoutFinally Context{..} =
-    T.forkManagedTimeoutFinally ctxThreadManager
+    T.forkManagedTimeoutFinally ctxThreadManager ctxTimeoutManager
 
 abort :: Context -> ApplicationProtocolError -> IO ()
 abort ctx aerr = abortConnection (ctxConnection ctx) aerr ""
diff --git a/Network/HTTP3/Server.hs b/Network/HTTP3/Server.hs
--- a/Network/HTTP3/Server.hs
+++ b/Network/HTTP3/Server.hs
@@ -57,6 +57,8 @@
 run conn conf server = withContext conn conf $ \ctx -> do
     myThreadId >>= \t -> labelThread t "H3 server: run"
     readerServer ctx $ \strm ->
+        -- 'processRequest' handles its own exceptions; only asynchronous ones
+        -- leave it, and the thread manager takes those that it threw.
         forkManagedTimeoutFinally
             ctx
             "H3 server: processRequest"
diff --git a/http3.cabal b/http3.cabal
--- a/http3.cabal
+++ b/http3.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               http3
-version:            0.1.8
+version:            0.1.9
 license:            BSD-3-Clause
 license-file:       LICENSE
 maintainer:         Kazu Yamamoto <kazu@iij.ad.jp>
@@ -83,16 +83,16 @@
         containers,
         http-semantics >= 0.4 && <0.5,
         http-types,
-        http2 >=5.4.6 && <5.5,
+        http2 >=5.4.10 && <5.5,
         iproute >= 1.7 && < 1.8,
         network,
         network-byte-order,
         network-control >= 0.1.7 && <0.2,
         psqueues,
-        quic >= 0.3.10 && < 0.4,
+        quic >= 0.3.18 && < 0.4,
         sockaddr,
         stm,
-        time-manager >= 0.2.3 && <0.5,
+        time-manager >= 0.5 && <0.6,
         utf8-string >=1.0 && <1.1
 
 executable h3-server
