diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,13 @@
+## 1.2.8
+
+* Fixed a bug in the heartbeat monitor. The heartbeat thread exists to ensure
+  that all connections are still active, so we can detect when a long-polling
+  connection is closed. However, older versions of `engine-io`, the timer would
+  be reset whenever *we* sent the client a message, rather than only when we
+  *received* a message from the client. This meant that for high-traffic
+  applications, old connections might never be removed, resulting in a memory
+  leak.
+
 ## 1.2.7
 
 * Support decoding payloads coming through XHR polling transport.
diff --git a/engine-io.cabal b/engine-io.cabal
--- a/engine-io.cabal
+++ b/engine-io.cabal
@@ -1,5 +1,5 @@
 name: engine-io
-version: 1.2.7
+version: 1.2.8
 synopsis: A Haskell implementation of Engine.IO
 homepage: http://github.com/ocharles/engine.io
 license: BSD3
diff --git a/src/Network/EngineIO.hs b/src/Network/EngineIO.hs
--- a/src/Network/EngineIO.hs
+++ b/src/Network/EngineIO.hs
@@ -511,13 +511,13 @@
              return Nothing
         ]
 
-    case mMessage of
-      Just (Packet Close _) ->
-        return ()
+    -- If we *received* a message, then we can reset the ping timer.
+    for_ mMessage (const (STMDelay.updateDelay pingTimeoutDelay (pingTimeout * 1000000)))
 
-      _ -> do
-        STMDelay.updateDelay pingTimeoutDelay (pingTimeout * 1000000)
-        loop
+    -- If we received a close message, terminate. Otherwise, loop.
+    case mMessage of
+      Just (Packet Close _) -> return ()
+      _ -> loop
 
   _ <- liftIO $ Async.async $ do
     _ <- Async.waitAnyCatchCancel [ userSpace, brain, heartbeat ]
@@ -586,16 +586,10 @@
         p <- STM.atomically (STM.readTChan wsOut)
         sendPacket conn p
 
-      fix $ \loop -> do
-        e <- try (receivePacket conn >>= STM.atomically . STM.writeTChan wsIn)
-        case e of
-          Left (SomeException _) ->
-            return ()
-
-          Right _ -> loop
-
-      Async.cancel reader
-      STM.atomically (STM.writeTChan wsIn (Packet Close (TextPacket Text.empty)))
+      finally (do Async.link reader
+                  forever (receivePacket conn >>= STM.atomically . STM.writeTChan wsIn))
+              (do Async.cancel reader
+                  STM.atomically (STM.writeTChan wsIn (Packet Close (TextPacket Text.empty))))
 
   receivePacket conn = do
     msg <- WebSockets.receiveDataMessage conn
